Revert "Merge pull request 'Exercise Uranium authoritative loading on real transfers' (#21) from test/uranium-player-data-authority into main"

This reverts commit a2382b2bd5, reversing
changes made to 08eb4454f4.
This commit is contained in:
layue13 committed 2026-10-01 06:27:29 +08:00
1 parent a2382b2bd5
commit 631b623a3f
3 files changed
-320

No files matched your search

@@ -1,268 +0,0 @@
package dev.moonbridge.smoke;
import cc.uraniummc.api.playerdata.PlayerDataAuthority;
import cc.uraniummc.api.playerdata.PlayerDataAuthorityProvider;
import cc.uraniummc.api.playerdata.PlayerDataLoadContext;
import cc.uraniummc.api.playerdata.Position;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import org.bukkit.BanList;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
/** Real Uranium managed-login acceptance probe; test-only, no persistence service. */
public final class PlayerDataAuthorityAcceptancePlugin extends JavaPlugin implements Listener {
private static final String PROBE_NAME = "NettyProbe";
private final AtomicInteger logins = new AtomicInteger();
private final AtomicInteger restores = new AtomicInteger();
private final AtomicInteger loginChecks = new AtomicInteger();
private final AtomicInteger postJoinChecks = new AtomicInteger();
private final AtomicInteger suppressedLoads = new AtomicInteger();
private final AtomicInteger quitSaves = new AtomicInteger();
private final Map<UUID, State> states = new ConcurrentHashMap<>();
private final Set<UUID> quitting = ConcurrentHashMap.newKeySet();
private volatile PlayerDataAuthority.Registration registration;
private String node;
private volatile boolean failed;
@Override public void onEnable() {
node = getConfig().getString("backendName", "").trim().toLowerCase();
if (!node.equals("old") && !node.equals("new")) {
throw new IllegalStateException("backendName must be old or new");
}
try {
registration = PlayerDataAuthority.register(new ProbeProvider());
} catch (RuntimeException conflict) {
throw new IllegalStateException("Could not register the exclusive player-data probe", conflict);
}
// Force SCM to construct this login's PlayerLoginEvent with a native denial.
Bukkit.getBanList(BanList.Type.NAME).addBan(PROBE_NAME,
"Player-data authority acceptance probe", (Date) null, getName());
if (!Bukkit.getBanList(BanList.Type.NAME).isBanned(PROBE_NAME)) {
throw new IllegalStateException("Could not install the isolated name ban");
}
getServer().getPluginManager().registerEvents(this, this);
MinecraftForge.EVENT_BUS.register(this);
getLogger().info("PDA_PROBE_READY node=" + node + " nativeNameBan=true");
}
private final class ProbeProvider implements PlayerDataAuthorityProvider {
@Override public PlayerDataLoadContext begin(Player player) throws Exception {
if (!PROBE_NAME.equals(player.getName())) return null;
if (!Bukkit.isPrimaryThread()) throw new IllegalStateException("begin was not on the server thread");
final int ordinal = logins.incrementAndGet();
final int amount = (node.equals("old") ? 10 : 20) + ordinal;
final int staleAmount = 40 + amount;
if (node.equals("old") && ordinal == 2) {
int savedAmount = readLocalDiamondCount(player.getUniqueId());
if (savedAmount != 51) {
fail("returning A did not have first-exit local inventory marker 51; found " + savedAmount);
throw new IOException("Expected an existing stale A player file with 51 diamonds");
}
getLogger().info("PDA_PROBE_OLD_FILE_PASS node=old previousAmount=" + savedAmount);
}
State state = new State(player, player.getUniqueId(), ordinal, amount, staleAmount);
states.put(player.getUniqueId(), state);
Position position = node.equals("old") && ordinal == 2
? new Position(9999, 0.5D, 70.0D, 0.5D, 0.0F, 0.0F)
: new Position(0, 0.5D, 70.0D, 0.5D, 0.0F, 0.0F);
return new PlayerDataLoadContext() {
@Override public Position position() { return position; }
@Override public void restore(Player target) {
if (!Bukkit.isPrimaryThread()) throw new IllegalStateException("restore was not on the server thread");
if (!state.uuid.equals(target.getUniqueId())) throw new IllegalStateException("wrong player context");
if (node.equals("old") && ordinal == 2) {
if (!target.getWorld().getName().equals(Bukkit.getWorlds().get(0).getName())) {
throw new IllegalStateException("invalid dimension did not fall back to overworld");
}
getLogger().info("PDA_PROBE_FALLBACK_PASS node=old login=2 world=" + target.getWorld().getName());
}
target.getInventory().clear();
target.getInventory().setItem(0, new ItemStack(Material.DIAMOND, amount));
target.loadData();
requireDiamondAmount(target, amount, "CraftPlayer.loadData inside restore");
state.restored = true;
restores.incrementAndGet();
getLogger().info("PDA_PROBE_RESTORE_PASS node=" + node + " login=" + ordinal
+ " amount=" + amount + " loadData=retained");
}
@Override public void abort(Player target, Throwable cause) {
states.remove(state.uuid, state);
quitting.remove(state.uuid);
getLogger().warning("PDA_PROBE_ABORT node=" + node + " login=" + ordinal
+ " reason=" + (cause == null ? "login-denied" : cause.getClass().getSimpleName()));
}
};
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void login(PlayerLoginEvent event) {
if (!PROBE_NAME.equals(event.getPlayer().getName())) return;
Player player = event.getPlayer();
State state = states.get(player.getUniqueId());
try {
if (state == null || !state.restored) throw new AssertionError("login event ran before restore");
requireDiamondAmount(player, state.amount, "PlayerLoginEvent");
loginChecks.incrementAndGet();
if (event.getResult() == PlayerLoginEvent.Result.ALLOWED) {
throw new AssertionError("native name ban did not pre-deny PlayerLoginEvent");
}
PlayerLoginEvent.Result initial = event.getResult();
event.setResult(PlayerLoginEvent.Result.ALLOWED);
event.setKickMessage("");
getLogger().info("PDA_PROBE_NATIVE_DENIAL_OVERRIDE_PASS node=" + node + " login=" + state.ordinal
+ " initial=" + initial + " restoredBeforeEvent=true");
} catch (Throwable failure) {
fail("PlayerLoginEvent acceptance failed: " + failure);
event.disallow(PlayerLoginEvent.Result.KICK_OTHER, "Player-data authority probe failed");
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void join(PlayerJoinEvent event) {
Player player = event.getPlayer();
State state = states.get(player.getUniqueId());
if (state == null) return;
Bukkit.getScheduler().runTask(this, () -> {
try {
requireDiamondAmount(player, state.amount, "post-Join next tick");
postJoinChecks.incrementAndGet();
if (state.ordinal == 1 && node.equals("old")) {
// Persist a deliberately different local value before the transfer away from A.
player.getInventory().setItem(0, new ItemStack(Material.DIAMOND, state.staleAmount));
player.saveData();
getLogger().info("PDA_PROBE_STALE_LOCAL_SEED_PASS node=old amount=" + state.staleAmount);
} else {
player.getInventory().setItem(0, new ItemStack(Material.DIAMOND, state.staleAmount));
player.saveData();
}
getLogger().info("PDA_PROBE_POST_JOIN_PASS node=" + node + " login=" + state.ordinal
+ " authoritativeAmount=" + state.amount + " savedLocalMarker=" + state.staleAmount);
} catch (Throwable failure) {
fail("post-Join check failed: " + failure);
player.kickPlayer("Player-data authority probe failed");
}
});
}
@EventHandler(priority = EventPriority.MONITOR)
public void quit(PlayerQuitEvent event) {
State state = states.get(event.getPlayer().getUniqueId());
if (state == null) return;
quitting.add(state.uuid);
try {
requireDiamondAmount(event.getPlayer(), state.staleAmount, "Bukkit Quit before Forge save");
} catch (AssertionError failure) {
fail(failure.getMessage());
}
}
@SubscribeEvent
public void loadFromFile(PlayerEvent.LoadFromFile event) {
State state = states.get(event.entityPlayer.func_110124_au());
if (state != null) {
suppressedLoads.incrementAndGet();
fail("Forge LoadFromFile fired for managed player " + state.uuid);
}
}
@SubscribeEvent
public void saveToFile(PlayerEvent.SaveToFile event) {
UUID uuid = event.entityPlayer.func_110124_au();
State state = states.get(uuid);
if (state == null || !quitting.remove(uuid)) return;
try {
requireDiamondAmount(state.player, state.staleAmount, "Forge SaveToFile");
quitSaves.incrementAndGet();
getLogger().info("PDA_PROBE_QUIT_SAVE_PASS node=" + node + " login=" + state.ordinal
+ " savedLocalMarker=" + state.staleAmount);
} catch (AssertionError failure) {
fail(failure.getMessage());
}
}
private int readLocalDiamondCount(UUID uuid) throws IOException {
File file = new File(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "playerdata"), uuid + ".dat");
if (!file.isFile()) return -1;
NBTTagCompound data;
try (FileInputStream input = new FileInputStream(file)) {
data = CompressedStreamTools.func_74796_a(input);
}
NBTTagList inventory = data.func_150295_c("Inventory", 10);
for (int i = 0; i < inventory.func_74745_c(); i++) {
NBTTagCompound stack = inventory.func_150305_b(i);
if (stack.func_74765_d("id") == (short) Item.func_150891_b(Items.field_151045_i)) {
return stack.func_74771_c("Count") & 255;
}
}
return 0;
}
private void requireDiamondAmount(Player player, int expected, String checkpoint) {
ItemStack stack = player.getInventory().getItem(0);
if (stack == null || stack.getType() != Material.DIAMOND || stack.getAmount() != expected) {
throw new AssertionError(checkpoint + " expected " + expected + " diamonds, found "
+ (stack == null ? "empty" : stack.getType() + "x" + stack.getAmount()));
}
}
private void fail(String reason) {
failed = true;
getLogger().severe("PDA_PROBE_FAIL node=" + node + " reason=" + reason);
}
@Override public void onDisable() {
PlayerDataAuthority.Registration handle = registration;
if (handle != null) handle.close();
MinecraftForge.EVENT_BUS.unregister(this);
getLogger().info("PDA_PROBE_SUMMARY node=" + node + " logins=" + logins.get()
+ " restores=" + restores.get() + " loginChecks=" + loginChecks.get()
+ " postJoinChecks=" + postJoinChecks.get() + " LoadFromFile=" + suppressedLoads.get()
+ " quitSaveEvents=" + quitSaves.get() + " failed=" + failed);
}
private static final class State {
private final Player player;
private final UUID uuid;
private final int ordinal;
private final int amount;
private final int staleAmount;
private volatile boolean restored;
private State(Player player, UUID uuid, int ordinal, int amount, int staleAmount) {
this.player = player;
this.uuid = uuid;
this.ordinal = ordinal;
this.amount = amount;
this.staleAmount = staleAmount;
}
}
}
-39
View File
@@ -6,8 +6,6 @@ param(
[switch]$InstalledPlugin,
[switch]$ReleaseSource,
[switch]$AuthenticateEarly,
[switch]$ManagedPlayerData,
[string]$UraniumApiJar,
[string]$BukkitApiJar,
[switch]$DebugSession,
[switch]$TraceBackend,
@@ -23,9 +21,6 @@ if ($ReleaseSource -and -not $InstalledPlugin) { throw '-ReleaseSource requires
if ($AuthenticateEarly -and (-not $InstalledPlugin -or $TraceBackend -or -not $BukkitApiJar)) {
throw '-AuthenticateEarly requires -InstalledPlugin and -BukkitApiJar, and cannot use -TraceBackend'
}
if ($ManagedPlayerData -and (-not $AuthenticateEarly -or -not $ReturnToOld -or -not $UraniumApiJar -or $PrismClient)) {
throw '-ManagedPlayerData requires -AuthenticateEarly, -ReturnToOld, and -UraniumApiJar, with the Netty probe'
}
if ($BackendJavaAgent) { $BackendJavaAgent = (Resolve-Path -LiteralPath $BackendJavaAgent).Path }
if ($PrismClient -and -not $InstalledPlugin) { throw '-PrismClient requires -InstalledPlugin' }
if ($DebugSession -and -not $InstalledPlugin) { throw '-DebugSession requires -InstalledPlugin' }
@@ -115,12 +110,6 @@ function StartUranium([string]$directory, [int]$port) {
New-Item -ItemType Directory -Force $plugins, $hostConfig | Out-Null
Copy-Item -LiteralPath $hostJar.FullName -Destination $plugins
Copy-Item -LiteralPath $earlyProbeJar -Destination $plugins
if ($ManagedPlayerData) {
Copy-Item -LiteralPath $authorityProbeJar -Destination $plugins
$authorityConfig = Join-Path $plugins 'PlayerDataAuthorityAcceptance'
New-Item -ItemType Directory -Force $authorityConfig | Out-Null
"backendName: $node" | Set-Content -LiteralPath (Join-Path $authorityConfig 'config.yml') -Encoding utf8
}
"settings:`n bungeecord: true`n" |
Set-Content -LiteralPath (Join-Path $directory 'spigot.yml') -Encoding utf8
@"
@@ -213,18 +202,6 @@ try {
$earlyProbeJar = Join-Path $runDir 'early-session-acceptance.jar'
& jar cf $earlyProbeJar -C $earlyClasses .
if ($LASTEXITCODE -ne 0) { throw 'Early session probe packaging failed' }
if ($ManagedPlayerData) {
$authorityClasses = Join-Path $runDir 'authority-classes'
New-Item -ItemType Directory -Force $authorityClasses | Out-Null
$libraryClasspath = Join-Path $bundle 'libraries/*'
& $javac8 -cp "$UraniumApiJar;$BukkitApiJar;$libraryClasspath" -d $authorityClasses (Join-Path $PSScriptRoot 'PlayerDataAuthorityAcceptancePlugin.java')
if ($LASTEXITCODE -ne 0) { throw 'Player data authority probe compilation failed' }
"name: PlayerDataAuthorityAcceptance`nmain: dev.moonbridge.smoke.PlayerDataAuthorityAcceptancePlugin`nversion: 1.0`ndepend: [MoonBridgeBackend, EarlySessionAcceptance]`n" |
Set-Content -LiteralPath (Join-Path $authorityClasses 'plugin.yml') -Encoding utf8
$authorityProbeJar = Join-Path $runDir 'player-data-authority-acceptance.jar'
& jar cf $authorityProbeJar -C $authorityClasses .
if ($LASTEXITCODE -ne 0) { throw 'Player data authority probe packaging failed' }
}
}
$old = StartUranium (Join-Path $runDir 'old') $oldPort
$servers.Add($old)
@@ -504,19 +481,3 @@ if ($AuthenticateEarly) {
}
Write-Output 'REAL_URANIUM_EARLY_SESSION_PASS beforeJoin=true sameBinding=true privateProofRemoved=true'
}
if ($ManagedPlayerData) {
foreach ($item in @(@{ Server = $old; Expected = 2 }, @{ Server = $new; Expected = 1 })) {
$log = Join-Path $item.Server.Directory 'server.stdout.log'
foreach ($marker in @('PDA_PROBE_RESTORE_PASS', 'PDA_PROBE_NATIVE_DENIAL_OVERRIDE_PASS', 'PDA_PROBE_POST_JOIN_PASS')) {
if (@(Select-String -LiteralPath $log -Pattern $marker).Count -ne $item.Expected) {
throw "Expected $($item.Expected) $marker markers in $log"
}
}
if (Select-String -LiteralPath $log -Pattern 'PDA_PROBE_FAIL|failed=true|Exception caught during firing event|NoSuchMethodError|NoClassDefFoundError' -Quiet) { throw "Player authority probe failed: $log" }
}
$oldLog = Join-Path $old.Directory 'server.stdout.log'
foreach ($marker in @('PDA_PROBE_OLD_FILE_PASS', 'PDA_PROBE_FALLBACK_PASS', 'PDA_PROBE_QUIT_SAVE_PASS')) {
if (-not (Select-String -LiteralPath $oldLog -Pattern $marker -Quiet)) { throw "Missing $marker in $oldLog" }
}
Write-Output 'REAL_URANIUM_PLAYER_AUTHORITY_PASS beforeLogin=true staleFileSkipped=true loadDataGuarded=true invalidDimensionFallback=true normalQuitSave=true'
}
@@ -1,13 +0,0 @@
# Uranium player-data authority boundary
Scope: an optional public-API acceptance probe on the new Uranium authority implementation. This does not load PlayerDataSQL or a modpack, and its source-release barrier does not confirm SQL persistence.
Harness: `local-uranium-transfer.ps1 -InstalledPlugin -ReleaseSource -ReturnToOld -DebugSession -AuthenticateEarly -ManagedPlayerData -UraniumApiJar <matching development jar> -BukkitApiJar <Spigot API>`.
Local run: `build/local-uranium-transfer-7237a6cadf8e45378742e866d5039d4f`. Server artifact SHA-256: `EBCC88FE572D4CC448C86D1619C1B01762BE1D69282016EA90EFB494737F703E`. Its source was the dirty Uranium `evo@325e7de5ee` authority implementation later committed as `13855d1`; this was not a published artifact.
Result: exit 0; real protocol A→B→A and both source-release barriers passed. A had two restored/logged-in admissions, B one. All three login events initially denied a native name ban and observed restored inventory before the probe overrode that denial. All three next-tick checks retained the restored inventory.
A's first exit persisted a deliberately stale local marker of 51 diamonds. Returning A read that existing file, restored 12, called `Player.loadData()`, and still observed 12 before login and after Join. The supplied invalid dimension 9999 fell back to the overworld. Managed Forge `LoadFromFile` events: 0. Normal quit `SaveToFile` events: A 2, B 1. Probe failures and event/linkage errors: 0.
An earlier run exposed `NoSuchMethodError` in the probe's direct NMS `getBukkitEntity` return descriptor (development Craft class package differs from runtime). The probe now retains its exact Bukkit player for validation, and the harness rejects event/linkage errors in addition to explicit assertion markers. Compilation by itself did not detect that runtime failure.