diff --git a/CHANGELOG.md b/CHANGELOG.md index 482bc5dae0..78d54718c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Changelog for NeoFS Node - SNs no longer sign TTL=1 requests over mutually authenticated inter-node connections (#4100) - Optimized GRPC read/write bufferring (#4130) - TLS key of SN is now always read from the node wallet instead of configuration (#4131) +- SN now allocates less to forward SEARCH requests (#4116) ### Removed - Session token storage migration (#4124) @@ -32,7 +33,7 @@ Changelog for NeoFS Node ### Updated - `github.com/nspcc-dev/neo-go` module to `v0.122.1-0.20260807115931-cfee8827ddfd` (#4123) -- `github.com/nspcc-dev/neofs-sdk-go` module to `v1.0.0-rc.21.0.20260824193430-0f1faf21eb76` (#4123, #4144) +- `github.com/nspcc-dev/neofs-sdk-go` module to `v1.0.0-rc.21.0.20260826165415-15812e94109e` (#4123, #4144, #4116) - Go 1.26+ is required to build now (#3816) - github.com/cheggaaa/pb dependency from v1.0.29 to v3.2.1 (#4135) - `github.com/klauspost/compress` dependency from v1.18.4 to v1.19.2 (#4135) diff --git a/go.mod b/go.mod index 9f794f2ac1..c1906a4ef8 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/nspcc-dev/neo-go v0.122.1-0.20260807115931-cfee8827ddfd github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea github.com/nspcc-dev/neofs-contract v0.26.1 - github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.21.0.20260824193430-0f1faf21eb76 + github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.21.0.20260826165415-15812e94109e github.com/nspcc-dev/tzhash v1.8.4 github.com/panjf2000/ants/v2 v2.12.1 github.com/prometheus/client_golang v1.24.1 diff --git a/go.sum b/go.sum index e6770dd219..26fc33564e 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea h1:mK github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea/go.mod h1:YzhD4EZmC9Z/PNyd7ysC7WXgIgURc9uCG1UWDeV027Y= github.com/nspcc-dev/neofs-contract v0.26.1 h1:7Ii7Q4L3au408LOsIWKiSgfnT1g8G9jo3W7381d41T8= github.com/nspcc-dev/neofs-contract v0.26.1/go.mod h1:pevVF9OWdEN5bweKxOu6ryZv9muCEtS1ppzYM4RfBIo= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.21.0.20260824193430-0f1faf21eb76 h1:mWHwhsGMfxr0KKW+0q2kSZrWJ23lxl8Yi5M0w9ZJv6M= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.21.0.20260824193430-0f1faf21eb76/go.mod h1:cFjepLvmVg4DKWgdo3LTsSkemtW0GLzKobOVpsbBGTg= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.21.0.20260826165415-15812e94109e h1:63pBaB1crRFkI9+9hK8NIvHqXGlUA7ar7huhzHTclt0= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.21.0.20260826165415-15812e94109e/go.mod h1:cFjepLvmVg4DKWgdo3LTsSkemtW0GLzKobOVpsbBGTg= github.com/nspcc-dev/rfc6979 v0.2.4 h1:NBgsdCjhLpEPJZqmC9rciMZDcSY297po2smeaRjw57k= github.com/nspcc-dev/rfc6979 v0.2.4/go.mod h1:86ylDw6Kss+P6v4QAJqo1Sp3mC0/Zr9G97xSjQ9TuFg= github.com/nspcc-dev/tzhash v1.8.4 h1:lvuPGWsqEo9dVEvo/kdNLKv/Cy0yxRs9z5hJp8VcBuo= diff --git a/internal/crypto/requests.go b/internal/crypto/requests.go index 431a33afc5..feba7c4902 100644 --- a/internal/crypto/requests.go +++ b/internal/crypto/requests.go @@ -10,6 +10,7 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/network/peerauth" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + protoencoding "github.com/nspcc-dev/neofs-sdk-go/proto/encoding" "github.com/nspcc-dev/neofs-sdk-go/proto/refs" protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/user" @@ -17,20 +18,20 @@ import ( // VerifyRequestSignatures checks whether all request signatures are set and // valid. Returns [apistatus.SignatureVerification] otherwise. -func VerifyRequestSignatures[B neofscrypto.ProtoMessage](req neofscrypto.SignedRequest[B]) error { +func VerifyRequestSignatures[B protoencoding.Message](req neofscrypto.SignedRequest[B]) error { return verifyRequestSignatures(req, nil) } // VerifyRequestSignaturesWithContext is same as [VerifyRequestSignatures], but // skips verification for an authenticated inter-node request with TTL equal to one. -func VerifyRequestSignaturesWithContext[B neofscrypto.ProtoMessage](ctx context.Context, req neofscrypto.SignedRequest[B]) error { +func VerifyRequestSignaturesWithContext[B protoencoding.Message](ctx context.Context, req neofscrypto.SignedRequest[B]) error { if !requestNeedsSignature(ctx, req) { return nil } return verifyRequestSignatures(req, nil) } -func verifyRequestSignatures[B neofscrypto.ProtoMessage](req neofscrypto.SignedRequest[B], verifyN3 func(data, invocScript, verifScript []byte) error) error { +func verifyRequestSignatures[B protoencoding.Message](req neofscrypto.SignedRequest[B], verifyN3 func(data, invocScript, verifScript []byte) error) error { err := neofscrypto.VerifyRequestWithBufferN3(req, nil, verifyN3) if err != nil { var st apistatus.SignatureVerification @@ -42,7 +43,7 @@ func verifyRequestSignatures[B neofscrypto.ProtoMessage](req neofscrypto.SignedR // VerifyRequestSignaturesN3 is same as [VerifyRequestSignatures] but supports // [neofscrypto.N3] scheme. -func VerifyRequestSignaturesN3[B neofscrypto.ProtoMessage](ctx context.Context, req neofscrypto.SignedRequest[B], fsChain N3ScriptRunner) error { +func VerifyRequestSignaturesN3[B protoencoding.Message](ctx context.Context, req neofscrypto.SignedRequest[B], fsChain N3ScriptRunner) error { if !requestNeedsSignature(ctx, req) { return nil } @@ -54,7 +55,7 @@ func VerifyRequestSignaturesN3[B neofscrypto.ProtoMessage](ctx context.Context, }) } -func requestNeedsSignature[B neofscrypto.ProtoMessage](ctx context.Context, req neofscrypto.SignedRequest[B]) bool { +func requestNeedsSignature[B protoencoding.Message](ctx context.Context, req neofscrypto.SignedRequest[B]) bool { if req.GetVerifyHeader() != nil { return true } diff --git a/pkg/services/object/common.go b/pkg/services/object/common.go index 38520683a4..0f1f75e163 100644 --- a/pkg/services/object/common.go +++ b/pkg/services/object/common.go @@ -122,13 +122,17 @@ func forwardServerStreamRequestGRPC(ctx context.Context, req any, respStream grp } } -func callUnary(ctx context.Context, conn *grpc.ClientConn, method string, req any) (mem.BufferSlice, error) { - var respBuf mem.BufferSlice - - err := conn.Invoke(ctx, method, req, &respBuf, +func callUnaryWithCustomResponse(ctx context.Context, conn *grpc.ClientConn, method string, req any, resp any) error { + return conn.Invoke(ctx, method, req, resp, grpc.StaticMethod(), grpc.ForceCodecV2(iprotobuf.BufferedCodec{}), ) +} + +func callUnary(ctx context.Context, conn *grpc.ClientConn, method string, req any) (mem.BufferSlice, error) { + var respBuf mem.BufferSlice + + err := callUnaryWithCustomResponse(ctx, conn, method, req, &respBuf) if err != nil { return nil, fmt.Errorf("sending the request failed: %w", err) } diff --git a/pkg/services/object/get.go b/pkg/services/object/get.go index b70bf7a120..0a13da8d33 100644 --- a/pkg/services/object/get.go +++ b/pkg/services/object/get.go @@ -19,14 +19,13 @@ import ( getsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/get" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" - neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" iprotobuf "github.com/nspcc-dev/neofs-sdk-go/proto/protobuf" "github.com/nspcc-dev/neofs-sdk-go/proto/protobuf/protoscan" - protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" + "github.com/nspcc-dev/neofs-sdk-go/version" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/mem" @@ -480,7 +479,7 @@ func (x *getECTransport) CopyLocalECPartRange(ctx context.Context, storage *engi return ln, nil } -func (x *getECTransport) initGetPartRequest(remoteServerAPIVersion *protorefs.Version, partInfo iec.PartInfo) error { +func (x *getECTransport) initGetPartRequest(remoteServerAPIVersion version.Version, partInfo iec.PartInfo) error { if x.getPartRequestInfo == partInfo && x.getPartRequest != nil { return nil } @@ -503,7 +502,8 @@ func (x *getECTransport) CopyRemoteECPartParentHeaderAndPayload(ctx context.Cont var partPldLen uint64 var copiedPartPld uint64 - connAPIVersion := conn.APIVersion() + connAPIVersionMsg := conn.APIVersion() + connAPIVersion := version.New(connAPIVersionMsg.GetMajor(), connAPIVersionMsg.GetMinor()) err := conn.ForAnyGRPCConn(ctx, func(ctx context.Context, conn *grpc.ClientConn) error { if !copiedHdr { @@ -651,7 +651,7 @@ func (x *getECTransport) copyRemotePart(ctx context.Context, conn *grpc.ClientCo return copiedHdr, parentPldLen, partPldLen, copiedPartPldLen, nil } -func (x *getECTransport) copyRemotePartRange(ctx context.Context, conn *grpc.ClientConn, connAPIVersion *protorefs.Version, partInfo iec.PartInfo, off, ln uint64, controlCh <-chan bool) (uint64, error) { +func (x *getECTransport) copyRemotePartRange(ctx context.Context, conn *grpc.ClientConn, connAPIVersion version.Version, partInfo iec.PartInfo, off, ln uint64, controlCh <-chan bool) (uint64, error) { request, err := x.makeGetECPartRangeRequest(connAPIVersion, partInfo, off, ln) if err != nil { return 0, fmt.Errorf("make request: %w", err) @@ -835,7 +835,8 @@ func handleGetECPartResponseInit(buffers iprotobuf.BuffersSlice) (iprotobuf.Buff func (x *getECTransport) CopyRemoteECPartRange(ctx context.Context, conn clientcore.MultiAddressClient, partInfo iec.PartInfo, off uint64, ln uint64, full bool, controlCh <-chan bool) (uint64, error) { var copiedPld uint64 - connAPIVersion := conn.APIVersion() + connAPIVersionMsg := conn.APIVersion() + connAPIVersion := version.New(connAPIVersionMsg.GetMajor(), connAPIVersionMsg.GetMinor()) err := conn.ForAnyGRPCConn(ctx, func(ctx context.Context, conn *grpc.ClientConn) error { var reqLen uint64 @@ -866,7 +867,7 @@ func (x *getECTransport) CopyRemoteECPartRange(ctx context.Context, conn clientc return copiedPld, nil } -func (s *Server) makeGetECPartRequest(remoteServerAPIVersion *protorefs.Version, cnr cid.ID, parent oid.ID, partInfo iec.PartInfo) (mem.Buffer, error) { +func (s *Server) makeGetECPartRequest(remoteServerAPIVersion version.Version, cnr cid.ID, parent oid.ID, partInfo iec.PartInfo) (mem.Buffer, error) { ruleIdxStr := strconv.Itoa(partInfo.RuleIndex) partIdxStr := strconv.Itoa(partInfo.Index) @@ -875,29 +876,24 @@ func (s *Server) makeGetECPartRequest(remoteServerAPIVersion *protorefs.Version, metaHdrLen := calculateGetECPartRequestMetaHeaderLength(ruleIdxHdrLen, partIdxHdrLen) - verifHdrSigCount := getRequestVerificationSignaturesCount(remoteServerAPIVersion) - - verifHdrLen := calculateRequestVerificationHeaderLen(verifHdrSigCount) + verifHdrFldLen := calculateRequestVerificationHeaderFieldLen(remoteServerAPIVersion) reqLen := 1 + 1 + getByAddressRequestBodyLen + // first 1 for iprotobuf.TagBytes1 1 + protowire.SizeBytes(metaHdrLen) + // 1 for iprotobuf.TagBytes2 - 1 + protowire.SizeBytes(verifHdrLen) // 1 for iprotobuf.TagBytes3 + verifHdrFldLen // TODO: try with sync.Pool buf := make([]byte, reqLen) - n, err := s.writeGetECPartRequest(buf, cnr, parent, metaHdrLen, ruleIdxHdrLen, ruleIdxStr, partIdxHdrLen, partIdxStr, verifHdrSigCount) + err := s.writeGetECPartRequest(buf, cnr, parent, metaHdrLen, ruleIdxHdrLen, ruleIdxStr, partIdxHdrLen, partIdxStr, remoteServerAPIVersion) if err != nil { return nil, err } - if n != len(buf) { - return nil, fmt.Errorf("got wrong request length: expected %d, got %d", n, len(buf)) - } return mem.SliceBuffer(buf), nil } -func (x *getECTransport) makeGetECPartRangeRequest(remoteServerAPIVersion *protorefs.Version, partInfo iec.PartInfo, off, ln uint64) (mem.Buffer, error) { +func (x *getECTransport) makeGetECPartRangeRequest(remoteServerAPIVersion version.Version, partInfo iec.PartInfo, off, ln uint64) (mem.Buffer, error) { x.getPartRangeRequestsMtx.RLock() req := x.getPartRangeRequests[partInfo] x.getPartRangeRequestsMtx.RUnlock() @@ -934,7 +930,7 @@ func (x *getECTransport) makeGetECPartRangeRequest(remoteServerAPIVersion *proto return reqBuf, nil } -func (s *Server) makeGetECPartRangeRequest(remoteServerAPIVersion *protorefs.Version, cnr cid.ID, parent oid.ID, partInfo iec.PartInfo, off, ln uint64) (mem.Buffer, error) { +func (s *Server) makeGetECPartRangeRequest(remoteServerAPIVersion version.Version, cnr cid.ID, parent oid.ID, partInfo iec.PartInfo, off, ln uint64) (mem.Buffer, error) { ruleIdxStr := strconv.Itoa(partInfo.RuleIndex) partIdxStr := strconv.Itoa(partInfo.Index) @@ -960,39 +956,25 @@ func (s *Server) makeGetECPartRangeRequest(remoteServerAPIVersion *protorefs.Ver // payload_only flag bodyLen += 1 + 1 // 1 for iprotobuf.TagVarint4, 1 for true - verifHdrSigCount := getRequestVerificationSignaturesCount(remoteServerAPIVersion) - - verifHdrLen := calculateRequestVerificationHeaderLen(verifHdrSigCount) + verifHdrFldLen := calculateRequestVerificationHeaderFieldLen(remoteServerAPIVersion) reqLen := 1 + protowire.SizeBytes(bodyLen) + // 1 for iprotobuf.TagBytes1 1 + protowire.SizeBytes(metaHdrLen) + // 1 for iprotobuf.TagBytes2 - 1 + protowire.SizeBytes(verifHdrLen) // 1 for iprotobuf.TagBytes3 + verifHdrFldLen // TODO: try with sync.Pool buf := make([]byte, reqLen) - n, err := s.writeGetECPartRangeRequest(buf, bodyLen, cnr, parent, rngLen, off, ln, - metaHdrLen, ruleIdxHdrLen, ruleIdxStr, partIdxHdrLen, partIdxStr, verifHdrSigCount) + err := s.writeGetECPartRangeRequest(buf, bodyLen, cnr, parent, rngLen, off, ln, + metaHdrLen, ruleIdxHdrLen, ruleIdxStr, partIdxHdrLen, partIdxStr, remoteServerAPIVersion) if err != nil { return nil, err } - if n != len(buf) { - return nil, fmt.Errorf("got wrong request length: expected %d, got %d", n, len(buf)) - } return mem.SliceBuffer(buf), nil } -func (s *Server) writeGetECPartRequest(buf []byte, cnr cid.ID, parent oid.ID, metaHdrLen int, ruleIdxHdrLen int, ruleIdxHdr string, partIdxHdrLen int, partIdxHdr string, verifHdrSigCount int) (int, error) { - var originSig []byte - var err error - if verifHdrSigCount == 3 { - originSig, err = neofsecdsa.Signer(s.signer).Sign(nil) - if err != nil { - return 0, fmt.Errorf("sign empty data: %w", err) - } - } - +func (s *Server) writeGetECPartRequest(buf []byte, cnr cid.ID, parent oid.ID, metaHdrLen int, ruleIdxHdrLen int, ruleIdxHdr string, partIdxHdrLen int, partIdxHdr string, apiVersion version.Version) error { // body buf[0] = iprotobuf.TagBytes1 buf[1] = getByAddressRequestBodyLen @@ -1009,14 +991,6 @@ func (s *Server) writeGetECPartRequest(buf []byte, cnr cid.ID, parent oid.ID, me buf[43] = oid.Size copy(buf[44:], parent[:]) - var bodySig []byte - if verifHdrSigCount > 1 { - bodySig, err = signECDSAWithSHA512(s.signer, buf[2:76]) - if err != nil { - return 0, fmt.Errorf("sign body: %w", err) - } - } - // meta header buf[76] = iprotobuf.TagBytes2 off := 77 + binary.PutUvarint(buf[77:], uint64(metaHdrLen)) @@ -1027,37 +1001,12 @@ func (s *Server) writeGetECPartRequest(buf []byte, cnr cid.ID, parent oid.ID, me off += writeRequestMetaXHeader(buf[off:], ruleIdxHdrLen, iec.AttributeRuleIdx, ruleIdxHdr) off += writeRequestMetaXHeader(buf[off:], partIdxHdrLen, iec.AttributePartIdx, partIdxHdr) - var metaHdrSig []byte - var reqSig []byte - if verifHdrSigCount > 1 { - metaHdrSig, err = signECDSAWithSHA512(s.signer, buf[from:off]) - if err != nil { - return 0, fmt.Errorf("sign meta header: %w", err) - } - } else { - reqSig, err = signECDSAWithSHA512(s.signer, buf[:off]) - if err != nil { - return 0, fmt.Errorf("sign body and meta header: %w", err) - } - } - // verification header - off += writeRequestVerificationHeader(buf[off:], verifHdrSigCount, s.pubKeyBytes, bodySig, metaHdrSig, originSig, reqSig) - - return off, nil + return s.writeRequestSignatures(buf, off, buf[2:76], buf[from:off], apiVersion) } func (s *Server) writeGetECPartRangeRequest(buf []byte, bodyLen int, cnr cid.ID, parent oid.ID, rngLen int, off uint64, ln uint64, - metaHdrLen int, ruleIdxHdrLen int, ruleIdxHdr string, partIdxHdrLen int, partIdxHdr string, verifHdrSigCount int) (int, error) { - var originSig []byte - var err error - if verifHdrSigCount == 3 { - originSig, err = neofsecdsa.Signer(s.signer).Sign(nil) - if err != nil { - return 0, fmt.Errorf("sign empty data: %w", err) - } - } - + metaHdrLen int, ruleIdxHdrLen int, ruleIdxHdr string, partIdxHdrLen int, partIdxHdr string, apiVersion version.Version) error { // body buf[0] = iprotobuf.TagBytes1 n := 1 + binary.PutUvarint(buf[1:], uint64(bodyLen)) @@ -1104,13 +1053,7 @@ func (s *Server) writeGetECPartRangeRequest(buf []byte, bodyLen int, cnr cid.ID, buf[n] = 1 // true n++ - var bodySig []byte - if verifHdrSigCount > 1 { - bodySig, err = signECDSAWithSHA512(s.signer, buf[from:n]) - if err != nil { - return 0, fmt.Errorf("sign body: %w", err) - } - } + body := buf[from:n] // meta header buf[n] = iprotobuf.TagBytes2 @@ -1123,24 +1066,8 @@ func (s *Server) writeGetECPartRangeRequest(buf []byte, bodyLen int, cnr cid.ID, n += writeRequestMetaXHeader(buf[n:], ruleIdxHdrLen, iec.AttributeRuleIdx, ruleIdxHdr) n += writeRequestMetaXHeader(buf[n:], partIdxHdrLen, iec.AttributePartIdx, partIdxHdr) - var metaHdrSig []byte - var reqSig []byte - if verifHdrSigCount > 1 { - metaHdrSig, err = signECDSAWithSHA512(s.signer, buf[from:off]) - if err != nil { - return 0, fmt.Errorf("sign meta header: %w", err) - } - } else { - reqSig, err = signECDSAWithSHA512(s.signer, buf[:n]) - if err != nil { - return 0, fmt.Errorf("sign body and meta header: %w", err) - } - } - // verification header - n += writeRequestVerificationHeader(buf[n:], verifHdrSigCount, s.pubKeyBytes, bodySig, metaHdrSig, originSig, reqSig) - - return n, nil + return s.writeRequestSignatures(buf, n, body, buf[from:n], apiVersion) } func (s *Server) writeInitGetResponseBuffers(respStream grpc.ServerStream, id, sig, hdr iprotobuf.BuffersSlice, signResponse bool) error { diff --git a/pkg/services/object/proto.go b/pkg/services/object/proto.go index 338556c337..86adebe4cd 100644 --- a/pkg/services/object/proto.go +++ b/pkg/services/object/proto.go @@ -11,8 +11,9 @@ import ( iobject "github.com/nspcc-dev/neofs-node/internal/object" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" + protoencoding "github.com/nspcc-dev/neofs-sdk-go/proto/encoding" iprotobuf "github.com/nspcc-dev/neofs-sdk-go/proto/protobuf" - protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/version" "google.golang.org/protobuf/encoding/protowire" ) @@ -307,50 +308,51 @@ func signECDSAWithSHA512(privKey ecdsa.PrivateKey, data []byte) ([]byte, error) return sig, nil } -func getRequestVerificationSignaturesCount(remoteServerAPIVersion *protorefs.Version) int { - v := version.New(remoteServerAPIVersion.GetMajor(), remoteServerAPIVersion.GetMinor()) - switch v.Compare(version.New(2, 25)) { +func calculateRequestVerificationHeaderFieldLen(apiVersion version.Version) int { + var sigCount int + + switch apiVersion.Compare(version.New(2, 25)) { default: - return 1 + sigCount = 1 case -1: - return 3 + sigCount = 3 case 0: - return 2 + sigCount = 2 } -} -func calculateRequestVerificationHeaderLen(sigCount int) int { - return verificationHeaderECDSAWithSHA512SignatureLen * sigCount + return protoencoding.CalculateRequestVerificationHeaderFieldLength(sigCount * verificationHeaderECDSAWithSHA512SignatureLen) } -func writeRequestVerificationHeader(buf []byte, sigCount int, pubKey, bodySIg, metaSig, originSig, reqSig []byte) int { - buf[0] = iprotobuf.TagBytes3 - off := 1 + binary.PutUvarint(buf[1:], uint64(calculateRequestVerificationHeaderLen(sigCount))) - off += writeRequestVerificationSignature(buf[off:], iprotobuf.TagBytes1, pubKey, bodySIg) - off += writeRequestVerificationSignature(buf[off:], iprotobuf.TagBytes2, pubKey, metaSig) - off += writeRequestVerificationSignature(buf[off:], iprotobuf.TagBytes3, pubKey, originSig) - off += writeRequestVerificationSignature(buf[off:], iprotobuf.TagBytes5, pubKey, reqSig) - return off -} +func (s *Server) writeRequestSignatures(reqBuf []byte, bodyWithMetaLen int, body []byte, metaHdr []byte, apiVersion version.Version) error { + verCmp := apiVersion.Compare(version.New(2, 25)) + if verCmp > 0 { + reqSig, err := signECDSAWithSHA512(s.signer, reqBuf[:bodyWithMetaLen]) + if err != nil { + return fmt.Errorf("sign request body + meta header: %w", err) + } + protosession.WriteSingleSignatureRequestVerificationHeaderToRequest(reqBuf[bodyWithMetaLen:], s.pubKeyBytes, neofscrypto.ECDSA_SHA512, reqSig) + return nil + } -func writeRequestVerificationSignature(buf []byte, tag byte, pubKey, sig []byte) int { - if len(sig) == 0 { - return 0 + bodySig, err := signECDSAWithSHA512(s.signer, body) + if err != nil { + return fmt.Errorf("sign request body: %w", err) } - buf[0] = tag - buf[1] = ecdsaWithSHA512SignatureLen - return 2 + writeECDSAWithSHA512Signature(buf[2:], pubKey, sig) -} -func writeECDSAWithSHA512Signature(buf []byte, pubKey, sig []byte) int { - // key - buf[0] = iprotobuf.TagBytes1 // key - buf[1] = compressedECDSAPublicKeyLen - off := 2 + copy(buf[2:], pubKey) - // value - buf[off] = iprotobuf.TagBytes2 - off++ - buf[off] = ecdsaWithSHA256SignatureValueLen - off++ - return off + copy(buf[off:], sig) // scheme is 0 + metaSig, err := signECDSAWithSHA512(s.signer, metaHdr) + if err != nil { + return fmt.Errorf("sign request meta header: %w", err) + } + + var originSig []byte + if verCmp < 0 { + originSig, err = signECDSAWithSHA512(s.signer, nil) + if err != nil { + return fmt.Errorf("sign empty request verification header origin: %w", err) + } + } + + protosession.WriteMultiSignatureRequestVerificationHeaderToRequest(reqBuf[bodyWithMetaLen:], s.pubKeyBytes, neofscrypto.ECDSA_SHA512, bodySig, metaSig, originSig) + + return nil } diff --git a/pkg/services/object/proto_internal_test.go b/pkg/services/object/proto_internal_test.go deleted file mode 100644 index 924fe7ff6d..0000000000 --- a/pkg/services/object/proto_internal_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package object - -import ( - "testing" - - protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" - "github.com/stretchr/testify/require" -) - -func TestGetRequestVerificationSignaturesCount(t *testing.T) { - assert := func(t *testing.T, exp int, vers []*protorefs.Version) { - for _, v := range vers { - require.EqualValues(t, exp, getRequestVerificationSignaturesCount(v), v) - } - } - - t.Run("all", func(t *testing.T) { - assert(t, 3, []*protorefs.Version{ - nil, - new(protorefs.Version), - {Major: 1, Minor: 26}, - {Major: 2, Minor: 24}, - }) - }) - - t.Run("no origin", func(t *testing.T) { - assert(t, 2, []*protorefs.Version{ - {Major: 2, Minor: 25}, - }) - }) - - assert(t, 1, []*protorefs.Version{ - {Major: 2, Minor: 26}, - {Major: 3, Minor: 0}, - }) -} diff --git a/pkg/services/object/search.go b/pkg/services/object/search.go index a94c6a8384..5504f0f4f3 100644 --- a/pkg/services/object/search.go +++ b/pkg/services/object/search.go @@ -12,12 +12,17 @@ import ( islices "github.com/nspcc-dev/neofs-node/internal/slices" clientcore "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protoencoding "github.com/nspcc-dev/neofs-sdk-go/proto/encoding" protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" + "github.com/nspcc-dev/neofs-sdk-go/version" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/mem" ) +var searchRequestBufferPool = mem.DefaultBufferPool() + func iterateSearchableContainerNodes(nodeSets [][]netmap.NodeInfo, repRules []uint, ecRules []iec.Rule, allNodes bool, f func(netmap.NodeInfo) bool) { for i := range nodeSets { var ( @@ -48,7 +53,23 @@ func iterateSearchableContainerNodes(nodeSets [][]netmap.NodeInfo, repRules []ui } } -func (s *Server) forwardSearchRequest(ctx context.Context, req any, nodeSets [][]netmap.NodeInfo) (mem.BufferSlice, error) { +func (s *Server) forwardSearchRequest(ctx context.Context, req *protoobject.SearchV2Request, nodeSets [][]netmap.NodeInfo) (mem.BufferSlice, error) { + bodyLen := req.Body.MarshaledSize() + metaHdrLen := req.MetaHeader.MarshaledSize() + verifHdrLen := req.VerifyHeader.MarshaledSize() + + reqLen := protoencoding.CalculateRequestLength(bodyLen, metaHdrLen, verifHdrLen) + + butItem := searchRequestBufferPool.Get(reqLen) + defer searchRequestBufferPool.Put(butItem) + buf := *butItem + + off := protoencoding.WriteRequestBodyMessage(buf, req.Body) + off += protoencoding.WriteRequestMetaHeaderMessage(buf[off:], req.MetaHeader) + protoencoding.WriteRequestVerificationHeaderMessage(buf[off:], req.VerifyHeader) + + reqBuf := mem.SliceBuffer(buf) + for _, nodeSet := range nodeSets { for _, nodeIdx := range islices.ShuffleIndexes(len(nodeSet)) { node, err := s.nodeClients.Get(ctx, nodeSet[nodeIdx]) @@ -62,7 +83,7 @@ func (s *Server) forwardSearchRequest(ctx context.Context, req any, nodeSets [][ err = node.ForAnyGRPCConn(ctx, func(ctx context.Context, conn *grpc.ClientConn) error { var err error - respBuf, err = callUnary(ctx, conn, protoobject.ObjectService_SearchV2_FullMethodName, req) + respBuf, err = callUnary(ctx, conn, protoobject.ObjectService_SearchV2_FullMethodName, reqBuf) if err != nil { if igrpc.IsUnavailable(err) { return clientcore.ErrSkipConnection @@ -85,3 +106,11 @@ func (s *Server) forwardSearchRequest(ctx context.Context, req any, nodeSets [][ return nil, nil } + +func writeLocalSearchRequestMetaHeader(buf []byte, apiVersion version.Version) { + protosession.WriteRequestMetaHeaderToRequest(buf, apiVersion.Major(), apiVersion.Minor(), 1, 0, nil, nil, 0, nil, 0, nil, 0, 0, nil) +} + +func calculateLocalSearchRequestMetaHeaderLength(ver version.Version) int { + return protosession.CalculateRequestMetaHeaderLength(ver.Major(), ver.Minor(), 1, 0, nil, 0, 0, 0, 0) +} diff --git a/pkg/services/object/server.go b/pkg/services/object/server.go index 7d2a504459..143212fbe5 100644 --- a/pkg/services/object/server.go +++ b/pkg/services/object/server.go @@ -44,6 +44,7 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + protoencoding "github.com/nspcc-dev/neofs-sdk-go/proto/encoding" protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" iprotobuf "github.com/nspcc-dev/neofs-sdk-go/proto/protobuf" "github.com/nspcc-dev/neofs-sdk-go/proto/refs" @@ -2139,15 +2140,64 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req expectedRes int ) - req = &protoobject.SearchV2Request{ - Body: req.Body, - MetaHeader: &protosession.RequestMetaHeader{ - Version: version.Current().ProtoMessage(), // Should be client APIVersion(), but not possible now. - Ttl: 1, - }, - } + localVersion := version.Current() + + bodyLen := body.MarshaledSize() + bodyFldLen := protoencoding.CalculateRequestBodyFieldLength(bodyLen) + metaHdrLen := calculateLocalSearchRequestMetaHeaderLength(localVersion) + bodyWithMetaLen := bodyFldLen + protoencoding.CalculateRequestMetaHeaderFieldLength(metaHdrLen) + verifHdrFLdLen := calculateRequestVerificationHeaderFieldLen(localVersion) + reqLen := bodyWithMetaLen + verifHdrFLdLen + + reqBufItem := searchRequestBufferPool.Get(reqLen) + defer searchRequestBufferPool.Put(reqBufItem) + reqBuf := *reqBufItem + + off := protoencoding.WriteRequestBodyTagAndLength(reqBuf, bodyLen) + body.MarshalStable(reqBuf[off:]) + bodySlice := reqBuf[off : off+bodyLen] + off += bodyLen + + writeLocalSearchRequestMetaHeader(reqBuf[off:], localVersion) + metaHdrSlice := reqBuf[bodyWithMetaLen-metaHdrLen : bodyWithMetaLen] + var onceResign sync.Once var signingErr error + getRequestForConn := func(conn clientcore.MultiAddressClient) ([]byte, error) { + v := conn.APIVersion() + ver := version.New(v.GetMajor(), v.GetMinor()) + + if ver.Compare(localVersion) >= 0 { + if clientcore.IsMutuallyAuthenticated(conn) { // unsigned request + return reqBuf[:bodyWithMetaLen], nil + } + // 1 signature + onceResign.Do(func() { + signingErr = s.writeRequestSignatures(reqBuf, bodyWithMetaLen, bodySlice, metaHdrSlice, localVersion) + }) + if signingErr != nil { + return nil, fmt.Errorf("sign request: %w", signingErr) + } + return reqBuf, nil + } + + // need to lower API version + metaHdrLen := calculateLocalSearchRequestMetaHeaderLength(ver) + bodyWithMetaLen := bodyFldLen + protoencoding.CalculateRequestMetaHeaderFieldLength(metaHdrLen) + verifHdrFldLen := calculateRequestVerificationHeaderFieldLen(ver) + reqLen := bodyWithMetaLen + verifHdrFldLen + + buf := make([]byte, reqLen) + off := copy(buf, reqBuf[:bodyFldLen]) + writeLocalSearchRequestMetaHeader(buf[off:], ver) + + err = s.writeRequestSignatures(buf, bodyWithMetaLen, bodySlice, buf[bodyWithMetaLen-metaHdrLen:bodyWithMetaLen], ver) + if err != nil { + return nil, err + } + return buf, nil + } + var optimizedNodes = (len(body.Filters) != 0) && slices.ContainsFunc(body.Filters, func(filt *protoobject.SearchFilter) bool { return !strings.HasPrefix(filt.Key, "$Object:") && !strings.HasPrefix(filt.Key, "__NEOFS__") @@ -2169,7 +2219,7 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req return true } go func() { - set, more, err := s.searchOnRemoteNode(ctx, node, req, &onceResign, &signingErr) + set, more, err := s.searchOnRemoteNode(ctx, node, req.Body, getRequestForConn) resCh <- nodeSearchResult{set, more, err} }() return true @@ -2223,42 +2273,33 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req return res, newCursor, incomplete } -func (s *Server) searchOnRemoteNode(ctx context.Context, node netmap.NodeInfo, req *protoobject.SearchV2Request, onceResign *sync.Once, signingErr *error) ([]client.SearchResultItem, bool, error) { +func (s *Server) searchOnRemoteNode(ctx context.Context, node netmap.NodeInfo, reqBody *protoobject.SearchV2Request_Body, getRequestForConn func(clientcore.MultiAddressClient) ([]byte, error)) ([]client.SearchResultItem, bool, error) { c, err := s.nodeClients.Get(ctx, node) if err != nil { return nil, false, fmt.Errorf("get node client: %w", err) } - outReq := req - if shouldSignOutgoingRequest(c, req) { - onceResign.Do(func() { - req.VerifyHeader, *signingErr = neofscrypto.SignRequestWithBuffer[*protoobject.SearchV2Request_Body](neofsecdsa.Signer(s.signer), req, nil) - }) - if *signingErr != nil { - return nil, false, fmt.Errorf("sign request: %w", *signingErr) - } - } else { - outReq = &protoobject.SearchV2Request{ - Body: req.Body, - MetaHeader: req.MetaHeader, - } + req, err := getRequestForConn(c) + if err != nil { + return nil, false, err } var items []client.SearchResultItem var more bool return items, more, c.ForAnyGRPCConn(ctx, func(ctx context.Context, conn *grpc.ClientConn) error { var err error - items, more, err = searchOnRemoteAddress(ctx, conn, outReq) + items, more, err = searchOnRemoteAddress(ctx, conn, req, reqBody) if err != nil { - s.log.Debug("failed to search objects on remote node", zap.Error(err)) + s.log.Error("failed to search objects on remote node", zap.Error(err)) } return err }) } -func searchOnRemoteAddress(ctx context.Context, conn *grpc.ClientConn, - req *protoobject.SearchV2Request) ([]client.SearchResultItem, bool, error) { - resp, err := protoobject.NewObjectServiceClient(conn).SearchV2(ctx, req) +func searchOnRemoteAddress(ctx context.Context, conn *grpc.ClientConn, req []byte, reqBody *protoobject.SearchV2Request_Body) ([]client.SearchResultItem, bool, error) { + var resp protoobject.SearchV2Response + + err := callUnaryWithCustomResponse(ctx, conn, protoobject.ObjectService_SearchV2_FullMethodName, mem.SliceBuffer(req), &resp) if err != nil { return nil, false, fmt.Errorf("send request over gRPC: %w", err) } @@ -2278,27 +2319,27 @@ func searchOnRemoteAddress(ctx context.Context, conn *grpc.ClientConn, } return nil, false, nil } - if reqCursor := req.Body.Cursor; reqCursor != "" && resp.Body.Cursor == reqCursor { + if reqCursor := reqBody.Cursor; reqCursor != "" && resp.Body.Cursor == reqCursor { return nil, false, errors.New("invalid response body: cursor repeats the initial one") } - if n > req.Body.Count { + if n > reqBody.Count { return nil, false, errors.New("invalid response body: more items than requested") } - if resp.Body.Cursor != "" && n < req.Body.Count { - return nil, false, fmt.Errorf("invalid response body: cursor is set with less items than requested %d < %d", n, req.Body.Count) + if resp.Body.Cursor != "" && n < reqBody.Count { + return nil, false, fmt.Errorf("invalid response body: cursor is set with less items than requested %d < %d", n, reqBody.Count) } // TODO: we can theoretically do without type conversion, thus avoiding // additional allocation. At the same time, this will require generic code for merging. res := make([]client.SearchResultItem, n) - filteredAttributeless := len(req.Body.Attributes) == 0 && len(req.Body.Filters) > 0 + filteredAttributeless := len(reqBody.Attributes) == 0 && len(reqBody.Filters) > 0 for i, r := range resp.Body.Result { switch { case r == nil: return nil, false, fmt.Errorf("invalid response body: nil element #%d", i) case r.Id == nil: return nil, false, fmt.Errorf("invalid response body: invalid element #%d: missing ID", i) - case !filteredAttributeless && len(r.Attributes) != len(req.Body.Attributes) || filteredAttributeless && len(r.Attributes) > 1: + case !filteredAttributeless && len(r.Attributes) != len(reqBody.Attributes) || filteredAttributeless && len(r.Attributes) > 1: return nil, false, fmt.Errorf("invalid response body: invalid element #%d: wrong attribute count %d", i, len(r.Attributes)) } if err := res[i].ID.FromProtoMessage(r.Id); err != nil { diff --git a/pkg/services/util/sign.go b/pkg/services/util/sign.go index 68ba024b2f..e91609edf8 100644 --- a/pkg/services/util/sign.go +++ b/pkg/services/util/sign.go @@ -7,6 +7,7 @@ import ( apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" sdkcrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" sdkecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" + protoencoding "github.com/nspcc-dev/neofs-sdk-go/proto/encoding" protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) @@ -28,7 +29,7 @@ func VersionLE(req Request, mjr, mnr uint32) bool { // SignResponseIfNeeded checks whether response for the req should be signed. If // so, calculated verification header is returned. Otherwise, nil returns. -func SignResponseIfNeeded[R sdkcrypto.ProtoMessage](signer *ecdsa.PrivateKey, r sdkcrypto.SignedResponse[R], req Request) *protosession.ResponseVerificationHeader { +func SignResponseIfNeeded[R protoencoding.Message](signer *ecdsa.PrivateKey, r sdkcrypto.SignedResponse[R], req Request) *protosession.ResponseVerificationHeader { if VersionLE(req, 2, 21) { return SignResponse(signer, r) } @@ -36,7 +37,7 @@ func SignResponseIfNeeded[R sdkcrypto.ProtoMessage](signer *ecdsa.PrivateKey, r return nil } -func SignResponse[R sdkcrypto.ProtoMessage](signer *ecdsa.PrivateKey, r sdkcrypto.SignedResponse[R]) *protosession.ResponseVerificationHeader { +func SignResponse[R protoencoding.Message](signer *ecdsa.PrivateKey, r sdkcrypto.SignedResponse[R]) *protosession.ResponseVerificationHeader { verHeader, err := sdkcrypto.SignResponseWithBuffer(sdkecdsa.Signer(*signer), r, nil) if err != nil { // We can't pass this error as NeoFS status code since response will be unsigned.