mirror of
https://github.com/layue13/MoonBridge.git
synced 2026-10-25 18:46:18 +00:00
Revert "Merge pull request 'Exercise Uranium authoritative loading on real transfers' (#21) from test/uranium-player-data-authority into main"
This reverts commita2382b2bd5, reversing changes made to08eb4454f4.
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user