diff --git a/satellite/metabase/commit.go b/satellite/metabase/commit.go index 027ea9c7e..14ed3cc5c 100644 --- a/satellite/metabase/commit.go +++ b/satellite/metabase/commit.go @@ -12,6 +12,7 @@ import ( pgxerrcode "github.com/jackc/pgerrcode" "github.com/zeebo/errs" "go.uber.org/zap" + "golang.org/x/exp/slices" "storj.io/common/memory" "storj.io/common/storj" @@ -616,6 +617,8 @@ type CommitObject struct { // Wil be only executed after succesfull commit + delete DB operation. // Error on this function won't revert back committed object. OnDelete func(segments []DeletedSegmentInfo) + + UsePendingObjectsTable bool } // Verify verifies reqest fields. @@ -696,20 +699,6 @@ func (db *DB) CommitObject(ctx context.Context, opts CommitObject) (object Objec encryptionParameters{&opts.Encryption}, } - metadataColumns := "" - if opts.OverrideEncryptedMetadata { - args = append(args, - opts.EncryptedMetadataNonce, - opts.EncryptedMetadata, - opts.EncryptedMetadataEncryptedKey, - ) - metadataColumns = `, - encrypted_metadata_nonce = $11, - encrypted_metadata = $12, - encrypted_metadata_encrypted_key = $13 - ` - } - versionsToDelete := []Version{} if err := withRows(tx.QueryContext(ctx, ` SELECT version @@ -745,39 +734,138 @@ func (db *DB) CommitObject(ctx context.Context, opts CommitObject) (object Objec return ErrPermissionDenied.New("no permissions to delete existing object") } - err = tx.QueryRowContext(ctx, ` - UPDATE objects SET - status =`+committedStatus+`, - segment_count = $6, + if opts.UsePendingObjectsTable { + opts.Version = 1 - total_plain_size = $7, - total_encrypted_size = $8, - fixed_segment_size = $9, - zombie_deletion_deadline = NULL, + // we remove from deletion list object with version 1 as we will + // override/update instead deleting and inserting new one. + index := slices.Index(versionsToDelete, opts.Version) + if index != -1 { + versionsToDelete = slices.Delete(versionsToDelete, index, index+1) + } - -- TODO should we allow to override existing encryption parameters or return error if don't match with opts? - encryption = CASE - WHEN objects.encryption = 0 AND $10 <> 0 THEN $10 - WHEN objects.encryption = 0 AND $10 = 0 THEN NULL - ELSE objects.encryption - END - `+metadataColumns+` - WHERE - project_id = $1 AND - bucket_name = $2 AND - object_key = $3 AND - version = $4 AND - stream_id = $5 AND - status = `+pendingStatus+` - RETURNING - created_at, expires_at, - encrypted_metadata, encrypted_metadata_encrypted_key, encrypted_metadata_nonce, - encryption + args = append(args, + opts.EncryptedMetadataNonce, + opts.EncryptedMetadata, + opts.EncryptedMetadataEncryptedKey, + opts.OverrideEncryptedMetadata, + ) + + err = tx.QueryRowContext(ctx, ` + WITH delete_pending_object AS ( + DELETE FROM pending_objects WHERE + project_id = $1 AND + bucket_name = $2 AND + object_key = $3 AND + stream_id = $5 + RETURNING + project_id, bucket_name, object_key, $4::INT as version, stream_id, + `+committedStatus+` as status, $6::INT as segment_count, $7::INT as total_plain_size, $8::INT as total_encrypted_size, $9::INT as fixed_segment_size, NULL as zombie_deletion_deadline, + CASE + WHEN encryption = 0 AND $10 <> 0 THEN $10 + WHEN encryption = 0 AND $10 = 0 THEN NULL + ELSE encryption + END as + encryption, + encrypted_metadata_nonce, encrypted_metadata, encrypted_metadata_encrypted_key + ), object_to_commit AS ( + SELECT + project_id, bucket_name, object_key, version, stream_id, + status, segment_count, total_plain_size, total_encrypted_size, fixed_segment_size, zombie_deletion_deadline, + encryption, + CASE + WHEN $14::BOOL = true THEN $11 + ELSE delete_pending_object.encrypted_metadata_nonce + END as + encrypted_metadata_nonce, + CASE + WHEN $14::BOOL = true THEN $12 + ELSE delete_pending_object.encrypted_metadata + END as + encrypted_metadata, + CASE + WHEN $14::BOOL = true THEN $13 + ELSE delete_pending_object.encrypted_metadata_encrypted_key + END as + encrypted_metadata_encrypted_key + FROM delete_pending_object + ) + INSERT INTO objects ( + project_id, bucket_name, object_key, version, stream_id, + status, segment_count, total_plain_size, total_encrypted_size, fixed_segment_size, zombie_deletion_deadline, + encryption, + encrypted_metadata_nonce, encrypted_metadata, encrypted_metadata_encrypted_key + ) + SELECT * FROM object_to_commit + ON CONFLICT (project_id, bucket_name, object_key, version) + DO UPDATE SET ( + stream_id, + status, segment_count, total_plain_size, total_encrypted_size, fixed_segment_size, zombie_deletion_deadline, + encryption, + encrypted_metadata_nonce, encrypted_metadata, encrypted_metadata_encrypted_key + ) = (SELECT + stream_id, + status, segment_count, total_plain_size, total_encrypted_size, fixed_segment_size, zombie_deletion_deadline, + encryption, + encrypted_metadata_nonce, encrypted_metadata, encrypted_metadata_encrypted_key + FROM object_to_commit) + RETURNING + created_at, expires_at, + encrypted_metadata, encrypted_metadata_encrypted_key, encrypted_metadata_nonce, + encryption `, args...).Scan( - &object.CreatedAt, &object.ExpiresAt, - &object.EncryptedMetadata, &object.EncryptedMetadataEncryptedKey, &object.EncryptedMetadataNonce, - encryptionParameters{&object.Encryption}, - ) + &object.CreatedAt, &object.ExpiresAt, + &object.EncryptedMetadata, &object.EncryptedMetadataEncryptedKey, &object.EncryptedMetadataNonce, + encryptionParameters{&object.Encryption}, + ) + } else { + metadataColumns := "" + if opts.OverrideEncryptedMetadata { + args = append(args, + opts.EncryptedMetadataNonce, + opts.EncryptedMetadata, + opts.EncryptedMetadataEncryptedKey, + ) + metadataColumns = `, + encrypted_metadata_nonce = $11, + encrypted_metadata = $12, + encrypted_metadata_encrypted_key = $13 + ` + } + err = tx.QueryRowContext(ctx, ` + UPDATE objects SET + status =`+committedStatus+`, + segment_count = $6, + + total_plain_size = $7, + total_encrypted_size = $8, + fixed_segment_size = $9, + zombie_deletion_deadline = NULL, + + -- TODO should we allow to override existing encryption parameters or return error if don't match with opts? + encryption = CASE + WHEN objects.encryption = 0 AND $10 <> 0 THEN $10 + WHEN objects.encryption = 0 AND $10 = 0 THEN NULL + ELSE objects.encryption + END + `+metadataColumns+` + WHERE + project_id = $1 AND + bucket_name = $2 AND + object_key = $3 AND + version = $4 AND + stream_id = $5 AND + status = `+pendingStatus+` + RETURNING + created_at, expires_at, + encrypted_metadata, encrypted_metadata_encrypted_key, encrypted_metadata_nonce, + encryption + `, args...).Scan( + &object.CreatedAt, &object.ExpiresAt, + &object.EncryptedMetadata, &object.EncryptedMetadataEncryptedKey, &object.EncryptedMetadataNonce, + encryptionParameters{&object.Encryption}, + ) + } if err != nil { if errors.Is(err, sql.ErrNoRows) { return ErrObjectNotFound.Wrap(Error.New("object with specified version and pending status is missing")) diff --git a/satellite/metabase/commit_test.go b/satellite/metabase/commit_test.go index 10bb53021..eb7a13ce5 100644 --- a/satellite/metabase/commit_test.go +++ b/satellite/metabase/commit_test.go @@ -3801,6 +3801,559 @@ func TestCommitObject(t *testing.T) { require.Equal(t, expectedDeletedSegments, deletedSegments) }) + + t.Run("use pending objects table", func(t *testing.T) { + obj.Version = metabase.NextVersion + t.Run("version", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: metabase.ObjectStream{ + ProjectID: obj.ProjectID, + BucketName: obj.BucketName, + ObjectKey: obj.ObjectKey, + StreamID: obj.StreamID, + }, + Encryption: metabasetest.DefaultEncryption, + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + now := time.Now() + + encryptedMetadata := testrand.Bytes(1024) + encryptedMetadataNonce := testrand.Nonce() + encryptedMetadataKey := testrand.Bytes(265) + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: metabase.ObjectStream{ + ProjectID: obj.ProjectID, + BucketName: obj.BucketName, + ObjectKey: obj.ObjectKey, + Version: 1, + StreamID: obj.StreamID, + }, + OverrideEncryptedMetadata: true, + EncryptedMetadataNonce: encryptedMetadataNonce[:], + EncryptedMetadata: encryptedMetadata, + EncryptedMetadataEncryptedKey: encryptedMetadataKey, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + // disallow for double commit + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: metabase.ObjectStream{ + ProjectID: obj.ProjectID, + BucketName: obj.BucketName, + ObjectKey: obj.ObjectKey, + Version: 1, + StreamID: obj.StreamID, + }, + UsePendingObjectsTable: true, + }, + ErrClass: &metabase.ErrObjectNotFound, + ErrText: "metabase: object with specified version and pending status is missing", // TODO: this error message could be better + }.Check(ctx, t, db) + + metabasetest.Verify{ + Objects: []metabase.RawObject{ + { + ObjectStream: metabase.ObjectStream{ + ProjectID: obj.ProjectID, + BucketName: obj.BucketName, + ObjectKey: obj.ObjectKey, + Version: 1, + StreamID: obj.StreamID, + }, + CreatedAt: now, + Status: metabase.Committed, + + EncryptedMetadataNonce: encryptedMetadataNonce[:], + EncryptedMetadata: encryptedMetadata, + EncryptedMetadataEncryptedKey: encryptedMetadataKey, + + Encryption: metabasetest.DefaultEncryption, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("assign plain_offset", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + obj.Version++ + now := time.Now() + + rootPieceID := testrand.PieceID() + pieces := metabase.Pieces{{Number: 0, StorageNode: testrand.NodeID()}} + encryptedKey := testrand.Bytes(32) + encryptedKeyNonce := testrand.Bytes(32) + + metabasetest.CommitSegment{ + Opts: metabase.CommitSegment{ + ObjectStream: obj, + Position: metabase.SegmentPosition{Index: 0}, + RootPieceID: rootPieceID, + Pieces: pieces, + + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: 1024, + PlainSize: 512, + PlainOffset: 999999, + + Redundancy: metabasetest.DefaultRedundancy, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.CommitSegment{ + Opts: metabase.CommitSegment{ + ObjectStream: obj, + Position: metabase.SegmentPosition{Index: 1}, + RootPieceID: rootPieceID, + Pieces: pieces, + + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: 1024, + PlainSize: 512, + PlainOffset: 999999, + + Redundancy: metabasetest.DefaultRedundancy, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Segments: []metabase.RawSegment{ + { + StreamID: obj.StreamID, + Position: metabase.SegmentPosition{Index: 0}, + CreatedAt: now, + + RootPieceID: rootPieceID, + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: 1024, + PlainSize: 512, + PlainOffset: 0, + + Redundancy: metabasetest.DefaultRedundancy, + + Pieces: pieces, + }, + { + StreamID: obj.StreamID, + Position: metabase.SegmentPosition{Index: 1}, + CreatedAt: now, + + RootPieceID: rootPieceID, + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: 1024, + PlainSize: 512, + PlainOffset: 512, + + Redundancy: metabasetest.DefaultRedundancy, + + Pieces: pieces, + }, + }, + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + SegmentCount: 2, + FixedSegmentSize: 512, + TotalPlainSize: 2 * 512, + TotalEncryptedSize: 2 * 1024, + + Encryption: metabasetest.DefaultEncryption, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("large object over 2 GB", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + obj.Version = metabase.NextVersion + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + obj.Version++ + now := time.Now() + + rootPieceID := testrand.PieceID() + pieces := metabase.Pieces{{Number: 0, StorageNode: testrand.NodeID()}} + encryptedKey := testrand.Bytes(32) + encryptedKeyNonce := testrand.Bytes(32) + + metabasetest.CommitSegment{ + Opts: metabase.CommitSegment{ + ObjectStream: obj, + Position: metabase.SegmentPosition{Index: 0}, + RootPieceID: rootPieceID, + Pieces: pieces, + + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: math.MaxInt32, + PlainSize: math.MaxInt32, + Redundancy: metabasetest.DefaultRedundancy, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.CommitSegment{ + Opts: metabase.CommitSegment{ + ObjectStream: obj, + Position: metabase.SegmentPosition{Index: 1}, + RootPieceID: rootPieceID, + Pieces: pieces, + + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: math.MaxInt32, + PlainSize: math.MaxInt32, + Redundancy: metabasetest.DefaultRedundancy, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Segments: []metabase.RawSegment{ + { + StreamID: obj.StreamID, + Position: metabase.SegmentPosition{Index: 0}, + CreatedAt: now, + + RootPieceID: rootPieceID, + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: math.MaxInt32, + PlainSize: math.MaxInt32, + + Redundancy: metabasetest.DefaultRedundancy, + + Pieces: pieces, + }, + { + StreamID: obj.StreamID, + Position: metabase.SegmentPosition{Index: 1}, + CreatedAt: now, + + RootPieceID: rootPieceID, + EncryptedKey: encryptedKey, + EncryptedKeyNonce: encryptedKeyNonce, + + EncryptedSize: math.MaxInt32, + PlainSize: math.MaxInt32, + PlainOffset: math.MaxInt32, + + Redundancy: metabasetest.DefaultRedundancy, + + Pieces: pieces, + }, + }, + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + SegmentCount: 2, + FixedSegmentSize: math.MaxInt32, + TotalPlainSize: 2 * math.MaxInt32, + TotalEncryptedSize: 2 * math.MaxInt32, + + Encryption: metabasetest.DefaultEncryption, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("commit with encryption (no override)", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + obj.Version = metabase.NextVersion + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + obj.Version++ + now := time.Now() + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + // set different encryption than with BeginObjectExactVersion + Encryption: storj.EncryptionParameters{ + CipherSuite: storj.EncNull, + BlockSize: 512, + }, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + SegmentCount: 0, + Encryption: metabasetest.DefaultEncryption, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("commit with metadata (no overwrite)", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + now := time.Now() + + expectedMetadata := testrand.Bytes(memory.KiB) + expectedMetadataKey := testrand.Bytes(32) + expectedMetadataNonce := testrand.Nonce().Bytes() + + obj.Version = metabase.NextVersion + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + EncryptedMetadata: expectedMetadata, + EncryptedMetadataEncryptedKey: expectedMetadataKey, + EncryptedMetadataNonce: expectedMetadataNonce, + UsePendingObjectsTable: true, + }, + Version: metabase.DefaultVersion, + }.Check(ctx, t, db) + obj.Version++ + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + Encryption: metabasetest.DefaultEncryption, + + EncryptedMetadata: expectedMetadata, + EncryptedMetadataEncryptedKey: expectedMetadataKey, + EncryptedMetadataNonce: expectedMetadataNonce, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("commit with metadata (overwrite)", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + now := time.Now() + + expectedMetadata := testrand.Bytes(memory.KiB) + expecedMetadataKey := testrand.Bytes(32) + expecedMetadataNonce := testrand.Nonce().Bytes() + + obj.Version = metabase.NextVersion + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + EncryptedMetadata: testrand.Bytes(memory.KiB), + EncryptedMetadataEncryptedKey: testrand.Bytes(32), + EncryptedMetadataNonce: testrand.Nonce().Bytes(), + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + obj.Version++ + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + OverrideEncryptedMetadata: true, + EncryptedMetadata: expectedMetadata, + EncryptedMetadataEncryptedKey: expecedMetadataKey, + EncryptedMetadataNonce: expecedMetadataNonce, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + Encryption: metabasetest.DefaultEncryption, + + EncryptedMetadata: expectedMetadata, + EncryptedMetadataEncryptedKey: expecedMetadataKey, + EncryptedMetadataNonce: expecedMetadataNonce, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("commit with empty metadata (overwrite)", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + now := time.Now() + + obj.Version = metabase.NextVersion + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + EncryptedMetadata: testrand.Bytes(memory.KiB), + EncryptedMetadataEncryptedKey: testrand.Bytes(32), + EncryptedMetadataNonce: testrand.Nonce().Bytes(), + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + obj.Version++ + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + OverrideEncryptedMetadata: true, + EncryptedMetadata: nil, + EncryptedMetadataEncryptedKey: nil, + EncryptedMetadataNonce: nil, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + Encryption: metabasetest.DefaultEncryption, + }, + }, + }.Check(ctx, t, db) + }) + + t.Run("overwrite object", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + now := time.Now() + + obj := metabasetest.RandObjectStream() + for i := 0; i < 10; i++ { + obj.Version = metabase.NextVersion + obj.StreamID = testrand.UUID() + metabasetest.BeginObjectNextVersion{ + Opts: metabase.BeginObjectNextVersion{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + EncryptedMetadata: testrand.Bytes(memory.KiB), + EncryptedMetadataEncryptedKey: testrand.Bytes(32), + EncryptedMetadataNonce: testrand.Nonce().Bytes(), + UsePendingObjectsTable: true, + }, + Version: 1, + }.Check(ctx, t, db) + obj.Version++ + + metabasetest.CommitObject{ + Opts: metabase.CommitObject{ + ObjectStream: obj, + Encryption: metabasetest.DefaultEncryption, + + OverrideEncryptedMetadata: true, + EncryptedMetadata: nil, + EncryptedMetadataEncryptedKey: nil, + EncryptedMetadataNonce: nil, + UsePendingObjectsTable: true, + }, + }.Check(ctx, t, db) + + metabasetest.Verify{ + Objects: []metabase.RawObject{ + { + ObjectStream: obj, + CreatedAt: now, + Status: metabase.Committed, + + Encryption: metabasetest.DefaultEncryption, + }, + }, + }.Check(ctx, t, db) + } + }) + }) }) } diff --git a/satellite/metainfo/endpoint_object.go b/satellite/metainfo/endpoint_object.go index b624cfc97..c96e35617 100644 --- a/satellite/metainfo/endpoint_object.go +++ b/satellite/metainfo/endpoint_object.go @@ -255,6 +255,8 @@ func (endpoint *Endpoint) CommitObject(ctx context.Context, req *pb.ObjectCommit Encryption: encryption, DisallowDelete: !allowDelete, + + UsePendingObjectsTable: streamID.UsePendingObjectsTable, } // uplink can send empty metadata with not empty key/nonce // we need to fix it on uplink side but that part will be