package implementations import ( "context" sc "github.com/nspcc-dev/neo-go/pkg/smartcontract" libacl "github.com/nspcc-dev/neofs-api-go/acl" "github.com/nspcc-dev/neofs-node/internal" "github.com/nspcc-dev/neofs-node/lib/acl" "github.com/nspcc-dev/neofs-node/lib/blockchain/goclient" "github.com/nspcc-dev/neofs-node/lib/container" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/pkg/errors" ) // Consider moving ACLHelper implementation to the ACL library. type ( // ACLHelper is an interface, that provides useful functions // for ACL object pre-processor. ACLHelper interface { BasicACLGetter ContainerOwnerChecker } // BasicACLGetter helper provides function to return basic ACL value. BasicACLGetter interface { GetBasicACL(context.Context, CID) (uint32, error) } // ContainerOwnerChecker checks owner of the container. ContainerOwnerChecker interface { IsContainerOwner(context.Context, CID, refs.OwnerID) (bool, error) } aclHelper struct { cnr container.Storage } ) type binaryEACLSource struct { binaryStore acl.BinaryExtendedACLSource } // StaticContractClient is a wrapper over Neo:Morph client // that invokes single smart contract methods with fixed fee. type StaticContractClient struct { // neo-go client instance client *goclient.Client // contract script-hash scScriptHash util.Uint160 // invocation fee fee util.Fixed8 } // MorphContainerContract is a wrapper over StaticContractClient // for Container contract calls. type MorphContainerContract struct { // NeoFS Container smart-contract containerContract StaticContractClient // set EACL method name of container contract eaclSetMethodName string // get EACL method name of container contract eaclGetMethodName string // get container method name of container contract cnrGetMethodName string // put container method name of container contract cnrPutMethodName string // delete container method name of container contract cnrDelMethodName string // list containers method name of container contract cnrListMethodName string } const ( errNewACLHelper = internal.Error("cannot create ACLHelper instance") ) // GetBasicACL returns basic ACL of the container. func (h aclHelper) GetBasicACL(ctx context.Context, cid CID) (uint32, error) { gp := container.GetParams{} gp.SetContext(ctx) gp.SetCID(cid) gResp, err := h.cnr.GetContainer(gp) if err != nil { return 0, err } return gResp.Container().BasicACL, nil } // IsContainerOwner returns true if provided id is an owner container. func (h aclHelper) IsContainerOwner(ctx context.Context, cid CID, id refs.OwnerID) (bool, error) { gp := container.GetParams{} gp.SetContext(ctx) gp.SetCID(cid) gResp, err := h.cnr.GetContainer(gp) if err != nil { return false, err } return gResp.Container().OwnerID.Equal(id), nil } // NewACLHelper returns implementation of the ACLHelper interface. func NewACLHelper(cnr container.Storage) (ACLHelper, error) { if cnr == nil { return nil, errNewACLHelper } return aclHelper{cnr}, nil } // ExtendedACLSourceFromBinary wraps BinaryExtendedACLSource and returns ExtendedACLSource. // // If passed BinaryExtendedACLSource is nil, acl.ErrNilBinaryExtendedACLStore returns. func ExtendedACLSourceFromBinary(v acl.BinaryExtendedACLSource) (acl.ExtendedACLSource, error) { if v == nil { return nil, acl.ErrNilBinaryExtendedACLStore } return &binaryEACLSource{ binaryStore: v, }, nil } // GetExtendedACLTable receives eACL table in a binary representation from storage, // unmarshals it and returns ExtendedACLTable interface. func (s binaryEACLSource) GetExtendedACLTable(ctx context.Context, cid refs.CID) (libacl.ExtendedACLTable, error) { key := acl.BinaryEACLKey{} key.SetCID(cid) val, err := s.binaryStore.GetBinaryEACL(ctx, key) if err != nil { return nil, err } eacl := val.EACL() // TODO: verify signature res := libacl.WrapEACLTable(nil) return res, res.UnmarshalBinary(eacl) } // NewStaticContractClient initializes a new StaticContractClient. // // If passed Client is nil, goclient.ErrNilClient returns. func NewStaticContractClient(client *goclient.Client, scHash util.Uint160, fee util.Fixed8) (StaticContractClient, error) { res := StaticContractClient{ client: client, scScriptHash: scHash, fee: fee, } var err error if client == nil { err = goclient.ErrNilClient } return res, err } // Invoke calls Invoke method of goclient with predefined script hash and fee. // Supported args types are the same as in goclient. // // If Client is not initialized, goclient.ErrNilClient returns. func (s StaticContractClient) Invoke(method string, args ...interface{}) error { if s.client == nil { return goclient.ErrNilClient } return s.client.Invoke( s.scScriptHash, s.fee, method, args..., ) } // TestInvoke calls TestInvoke method of goclient with predefined script hash. // // If Client is not initialized, goclient.ErrNilClient returns. func (s StaticContractClient) TestInvoke(method string, args ...interface{}) ([]sc.Parameter, error) { if s.client == nil { return nil, goclient.ErrNilClient } return s.client.TestInvoke( s.scScriptHash, method, args..., ) } // SetContainerContractClient is a container contract client setter. func (s *MorphContainerContract) SetContainerContractClient(v StaticContractClient) { s.containerContract = v } // SetEACLGetMethodName is a container contract Get EACL method name setter. func (s *MorphContainerContract) SetEACLGetMethodName(v string) { s.eaclGetMethodName = v } // SetEACLSetMethodName is a container contract Set EACL method name setter. func (s *MorphContainerContract) SetEACLSetMethodName(v string) { s.eaclSetMethodName = v } // SetContainerGetMethodName is a container contract Get method name setter. func (s *MorphContainerContract) SetContainerGetMethodName(v string) { s.cnrGetMethodName = v } // SetContainerPutMethodName is a container contract Put method name setter. func (s *MorphContainerContract) SetContainerPutMethodName(v string) { s.cnrPutMethodName = v } // SetContainerDeleteMethodName is a container contract Delete method name setter. func (s *MorphContainerContract) SetContainerDeleteMethodName(v string) { s.cnrDelMethodName = v } // SetContainerListMethodName is a container contract List method name setter. func (s *MorphContainerContract) SetContainerListMethodName(v string) { s.cnrListMethodName = v } // GetBinaryEACL performs the test invocation call of GetEACL method of NeoFS Container contract. func (s *MorphContainerContract) GetBinaryEACL(_ context.Context, key acl.BinaryEACLKey) (acl.BinaryEACLValue, error) { res := acl.BinaryEACLValue{} prms, err := s.containerContract.TestInvoke( s.eaclGetMethodName, key.CID().Bytes(), ) if err != nil { return res, err } else if ln := len(prms); ln != 1 { return res, errors.Errorf("unexpected stack parameter count: %d", ln) } eacl, err := goclient.BytesFromStackParameter(prms[0]) if err == nil { res.SetEACL(eacl) } return res, err } // PutBinaryEACL invokes the call of SetEACL method of NeoFS Container contract. func (s *MorphContainerContract) PutBinaryEACL(_ context.Context, key acl.BinaryEACLKey, val acl.BinaryEACLValue) error { return s.containerContract.Invoke( s.eaclSetMethodName, key.CID().Bytes(), val.EACL(), val.Signature(), ) } // GetContainer performs the test invocation call of Get method of NeoFS Container contract. func (s *MorphContainerContract) GetContainer(p container.GetParams) (*container.GetResult, error) { prms, err := s.containerContract.TestInvoke( s.cnrGetMethodName, p.CID().Bytes(), ) if err != nil { return nil, errors.Wrap(err, "could not perform test invocation") } else if ln := len(prms); ln != 1 { return nil, errors.Errorf("unexpected stack item count: %d", ln) } cnrBytes, err := goclient.BytesFromStackParameter(prms[0]) if err != nil { return nil, errors.Wrap(err, "could not get byte array from stack item") } cnr := new(container.Container) if err := cnr.Unmarshal(cnrBytes); err != nil { return nil, errors.Wrap(err, "could not unmarshal container from bytes") } res := new(container.GetResult) res.SetContainer(cnr) return res, nil } // PutContainer invokes the call of Put method of NeoFS Container contract. func (s *MorphContainerContract) PutContainer(p container.PutParams) (*container.PutResult, error) { cnr := p.Container() cid, err := cnr.ID() if err != nil { return nil, errors.Wrap(err, "could not calculate container ID") } cnrBytes, err := cnr.Marshal() if err != nil { return nil, errors.Wrap(err, "could not marshal container") } if err := s.containerContract.Invoke( s.cnrPutMethodName, cnr.OwnerID.Bytes(), cnrBytes, []byte{}, ); err != nil { return nil, errors.Wrap(err, "could not invoke contract method") } res := new(container.PutResult) res.SetCID(cid) return res, nil } // DeleteContainer invokes the call of Delete method of NeoFS Container contract. func (s *MorphContainerContract) DeleteContainer(p container.DeleteParams) (*container.DeleteResult, error) { if err := s.containerContract.Invoke( s.cnrDelMethodName, p.CID().Bytes(), p.OwnerID().Bytes(), []byte{}, ); err != nil { return nil, errors.Wrap(err, "could not invoke contract method") } return new(container.DeleteResult), nil } // ListContainers performs the test invocation call of Get method of NeoFS Container contract. // // If owner ID list in parameters is non-empty, bytes of first owner are attached to call. func (s *MorphContainerContract) ListContainers(p container.ListParams) (*container.ListResult, error) { args := make([]interface{}, 0, 1) if ownerIDList := p.OwnerIDList(); len(ownerIDList) > 0 { args = append(args, ownerIDList[0].Bytes()) } prms, err := s.containerContract.TestInvoke( s.cnrListMethodName, args..., ) if err != nil { return nil, errors.Wrap(err, "could not perform test invocation") } else if ln := len(prms); ln != 1 { return nil, errors.Errorf("unexpected stack item count: %d", ln) } prms, err = goclient.ArrayFromStackParameter(prms[0]) if err != nil { return nil, errors.Wrap(err, "could not get stack item array from stack item") } cidList := make([]CID, 0, len(prms)) for i := range prms { cidBytes, err := goclient.BytesFromStackParameter(prms[i]) if err != nil { return nil, errors.Wrap(err, "could not get byte array from stack item") } cid, err := refs.CIDFromBytes(cidBytes) if err != nil { return nil, errors.Wrap(err, "could not get container ID from bytes") } cidList = append(cidList, cid) } res := new(container.ListResult) res.SetCIDList(cidList) return res, nil }