diff --git a/CHANGELOG.md b/CHANGELOG.md
index d763700..1e775ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## [Unreleased]
### Added
+- Streaming object replication op (#418)
### Changed
diff --git a/object/service.proto b/object/service.proto
index 7c52c64..b275fbf 100644
--- a/object/service.proto
+++ b/object/service.proto
@@ -264,6 +264,31 @@ service ObjectService {
// - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
// the container to which the replicated object is associated was not found.
rpc Replicate(ReplicateRequest) returns (ReplicateResponse);
+
+ // Save replica of the object on the NeoFS storage node. Both client and
+ // server must be authenticated NeoFS storage nodes matching storage policy
+ // of the container referenced by the replicated object. Thus, this operation
+ // is purely system: regular users should not pay attention to it but use
+ // Put.
+ //
+ // First message is required and MUST contain `init` field only. `object_id`,
+ // `signature`, `header` and `node_signature` fields are required for it.
+ // Following messages must contain `payload_chunk` field only.
+ //
+ // Server may interrupt the stream with `OK` status if object already exists
+ // on it.
+ //
+ // Statuses:
+ // - **OK** (0, SECTION_SUCCESS): \
+ // the object has been successfully replicated;
+ // - **INTERNAL_SERVER_ERROR** (1024, SECTION_FAILURE_COMMON): \
+ // internal server error described in the text message;
+ // - **ACCESS_DENIED** (2048, SECTION_OBJECT): \
+ // the client does not authenticate any NeoFS storage node matching storage
+ // policy of the container referenced by the replicated object
+ // - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
+ // the container to which the replicated object is associated was not found.
+ rpc ReplicateV2(stream ReplicateV2Request) returns (ReplicateV2Response);
}
// GET object request.
@@ -872,3 +897,41 @@ message ReplicateResponse {
// `sign_object` flag set.
bytes object_signature = 2;
}
+
+// ReplicateV2 RPC request
+message ReplicateV2Request {
+ // Stream initialization data.
+ message Init {
+ // Object to be replicated without payload.
+ neo.fs.v2.object.Object object = 1;
+
+ // Signature of `object.object_id.value` field.
+ neo.fs.v2.refs.Signature signature = 2;
+
+ // Optional flag that requires server side to attach signature of just
+ // replicated object to ensure it has been received correctly. Signature
+ // must be calculated with a key that corresponds to an exposed to the
+ // network map public key of the object receiver.
+ bool sign_object = 3;
+ }
+
+ // Single message in the request stream.
+ oneof stream_part {
+ // Initial stream part.
+ Init init = 1;
+
+ // Payload chunk of the replicated object.
+ bytes payload_chunk = 2;
+ }
+}
+
+// ReplicateV2 RPC response
+message ReplicateV2Response {
+ // Operation execution status with one of the enumerated codes.
+ neo.fs.v2.status.Status status = 1;
+
+ // Deterministic ECDSA with SHA-256 hashing (RFC 6979) signature of
+ // replicated object. Must be attached if request was made with
+ // `init.sign_object` flag set.
+ bytes object_signature = 2;
+}
diff --git a/proto-docs/object.md b/proto-docs/object.md
index f9b96f3..d6e5376 100644
--- a/proto-docs/object.md
+++ b/proto-docs/object.md
@@ -39,6 +39,9 @@
- [Range](#neo.fs.v2.object.Range)
- [ReplicateRequest](#neo.fs.v2.object.ReplicateRequest)
- [ReplicateResponse](#neo.fs.v2.object.ReplicateResponse)
+ - [ReplicateV2Request](#neo.fs.v2.object.ReplicateV2Request)
+ - [ReplicateV2Request.Init](#neo.fs.v2.object.ReplicateV2Request.Init)
+ - [ReplicateV2Response](#neo.fs.v2.object.ReplicateV2Response)
- [SearchRequest](#neo.fs.v2.object.SearchRequest)
- [SearchRequest.Body](#neo.fs.v2.object.SearchRequest.Body)
- [SearchResponse](#neo.fs.v2.object.SearchResponse)
@@ -90,6 +93,7 @@ rpc SearchV2(SearchV2Request) returns (SearchV2Response);
rpc GetRange(GetRangeRequest) returns (stream GetRangeResponse);
rpc GetRangeHash(GetRangeHashRequest) returns (GetRangeHashResponse);
rpc Replicate(ReplicateRequest) returns (ReplicateResponse);
+rpc ReplicateV2(stream ReplicateV2Request) returns (ReplicateV2Response);
```
@@ -381,6 +385,35 @@ Statuses:
| Name | Input | Output |
| ---- | ----- | ------ |
| Replicate | [ReplicateRequest](#neo.fs.v2.object.ReplicateRequest) | [ReplicateResponse](#neo.fs.v2.object.ReplicateResponse) |
+#### Method ReplicateV2
+
+Save replica of the object on the NeoFS storage node. Both client and
+server must be authenticated NeoFS storage nodes matching storage policy
+of the container referenced by the replicated object. Thus, this operation
+is purely system: regular users should not pay attention to it but use
+Put.
+
+First message is required and MUST contain `init` field only. `object_id`,
+`signature`, `header` and `node_signature` fields are required for it.
+Following messages must contain `payload_chunk` field only.
+
+Server may interrupt the stream with `OK` status if object already exists
+on it.
+
+Statuses:
+- **OK** (0, SECTION_SUCCESS): \
+ the object has been successfully replicated;
+- **INTERNAL_SERVER_ERROR** (1024, SECTION_FAILURE_COMMON): \
+ internal server error described in the text message;
+- **ACCESS_DENIED** (2048, SECTION_OBJECT): \
+ the client does not authenticate any NeoFS storage node matching storage
+ policy of the container referenced by the replicated object
+- **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \
+ the container to which the replicated object is associated was not found.
+
+| Name | Input | Output |
+| ---- | ----- | ------ |
+| ReplicateV2 | [ReplicateV2Request](#neo.fs.v2.object.ReplicateV2Request) | [ReplicateV2Response](#neo.fs.v2.object.ReplicateV2Response) |
@@ -838,6 +871,43 @@ Replicate RPC response
| object_signature | [bytes](#bytes) | | Deterministic ECDSA with SHA-256 hashing (RFC 6979) signature of replicated object. Must be attached if request was made with `sign_object` flag set. |
+
+
+### Message ReplicateV2Request
+ReplicateV2 RPC request
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| init | [ReplicateV2Request.Init](#neo.fs.v2.object.ReplicateV2Request.Init) | | Initial stream part. |
+| payload_chunk | [bytes](#bytes) | | Payload chunk of the replicated object. |
+
+
+
+
+### Message ReplicateV2Request.Init
+Stream initialization data.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| object | [Object](#neo.fs.v2.object.Object) | | Object to be replicated without payload. |
+| signature | [neo.fs.v2.refs.Signature](#neo.fs.v2.refs.Signature) | | Signature of `object.object_id.value` field. |
+| sign_object | [bool](#bool) | | Optional flag that requires server side to attach signature of just replicated object to ensure it has been received correctly. Signature must be calculated with a key that corresponds to an exposed to the network map public key of the object receiver. |
+
+
+
+
+### Message ReplicateV2Response
+ReplicateV2 RPC response
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| status | [neo.fs.v2.status.Status](#neo.fs.v2.status.Status) | | Operation execution status with one of the enumerated codes. |
+| object_signature | [bytes](#bytes) | | Deterministic ECDSA with SHA-256 hashing (RFC 6979) signature of replicated object. Must be attached if request was made with `init.sign_object` flag set. |
+
+
### Message SearchRequest