--- a/net/minecraft/world/level/block/ChestBlock.java
+++ b/net/minecraft/world/level/block/ChestBlock.java
@@ -78,11 +_,11 @@
             return Optional.empty();
         }
     };
-    private static final DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
+    public static DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
         @Override
         public Optional<MenuProvider> acceptDouble(final ChestBlockEntity first, final ChestBlockEntity second) {
             final Container container = new CompoundContainer(first, second);
-            return Optional.of(new MenuProvider() {
+            return Optional.of(DoubleInventory.wrap(new MenuProvider() { // CraftBukkit - wrap for identification
                 @Nullable
                 @Override
                 public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
@@ -100,10 +_,10 @@
                     if (first.hasCustomName()) {
                         return first.getDisplayName();
                     } else {
-                        return (Component)(second.hasCustomName() ? second.getDisplayName() : Component.translatable("container.chestDouble"));
+                        return (Component)(second.hasCustomName() ? second.getDisplayName() : Component.translatable("container.chestDouble")); // Paper - diff on change - CraftDoubleChestInventoryViewBuilder.defaultTitle
                     }
                 }
-            });
+            }, (CompoundContainer) container)); // CraftBukkit - wrap for identification
         }
 
         @Override
@@ -117,6 +_,34 @@
         }
     };
 
+    // CraftBukkit start
+    public static class DoubleInventory implements MenuProvider {
+
+        private final MenuProvider delegate;
+        public final CompoundContainer container; // expose to api
+
+        private DoubleInventory(MenuProvider delegate, CompoundContainer container) {
+            this.delegate = delegate;
+            this.container = container;
+        }
+
+        public static DoubleInventory wrap(MenuProvider delegate, CompoundContainer container) {
+            return new DoubleInventory(delegate, container);
+        }
+
+        @Nullable
+        @Override
+        public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
+            return this.delegate.createMenu(syncId, playerInventory, player);
+        }
+
+        @Override
+        public Component getDisplayName() {
+            return this.delegate.getDisplayName();
+        }
+    }
+    // CraftBukkit end
+
     @Override
     public MapCodec<? extends ChestBlock> codec() {
         return CODEC;
@@ -225,8 +_,7 @@
     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
         if (level instanceof ServerLevel serverLevel) {
             MenuProvider menuProvider = this.getMenuProvider(state, level, pos);
-            if (menuProvider != null) {
-                player.openMenu(menuProvider);
+            if (menuProvider != null && player.openMenu(menuProvider).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
                 player.awardStat(this.getOpenChestStat());
                 PiglinAi.angerNearbyPiglins(serverLevel, player, true);
             }
@@ -264,8 +_,15 @@
 
     @Nullable
     @Override
-    protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
-        return this.combine(state, level, pos, false).apply(MENU_PROVIDER_COMBINER).orElse(null);
+    public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
+    // CraftBukkit start
+        return this.getMenuProvider(state, level, pos, false);
+    }
+
+    @Nullable
+    public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos, boolean ignoreObstructions) {
+        return this.combine(state, level, pos, ignoreObstructions).apply(MENU_PROVIDER_COMBINER).orElse(null);
+    // CraftBukkit end
     }
 
     public static DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction> opennessCombiner(final LidBlockEntity lid) {
@@ -302,12 +_,17 @@
         return isBlockedChestByBlock(level, pos) || isCatSittingOnChest(level, pos);
     }
 
-    private static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) {
+    public static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) {
         BlockPos blockPos = pos.above();
         return level.getBlockState(blockPos).isRedstoneConductor(level, blockPos);
     }
 
     private static boolean isCatSittingOnChest(LevelAccessor level, BlockPos pos) {
+        // Paper start - Option to disable chest cat detection
+        if (level.getMinecraftWorld().paperConfig().entities.behavior.disableChestCatDetection) {
+            return false;
+        }
+        // Paper end - Option to disable chest cat detection
         List<Cat> entitiesOfClass = level.getEntitiesOfClass(
             Cat.class, new AABB(pos.getX(), pos.getY() + 1, pos.getZ(), pos.getX() + 1, pos.getY() + 2, pos.getZ() + 1)
         );
