fix: answer eth_chainId and net_version from loaded state (closes #317)
Both methods answered from the module-level state singleton, which the MV3
worker never populates, so a cold worker reported mainnet 0x1 to a page whose
user was on Sepolia.
They now answer from getState(), the per-call detached storage read the other
read handlers already use. An earlier revision of this fix used loadState()
instead and was rejected in review: it replaces the whole singleton, and these
methods are page-callable with no connection gate (inpage.js sends eth_chainId
on every page load), so a load landing inside backgroundRefresh()'s network
round trip detached the address objects being mutated in place — persisting
pre-refresh balances while still stamping lastBalanceRefresh, letting a polling
page suppress background refreshes indefinitely.
The test stub now structured-clones on get and set, as chrome.storage.local
does. The aliasing stub it replaces was independently measured to hide this
defect class entirely: with the aliasing get restored and the defective handler
in place, the suite passes 794/794.
Verified failing first three ways: a plain singleton read fails the three
cold-worker cases; the rejected loadState() revision fails only the new
mid-refresh case ("1.5" expected, "0" received); moving saveState() ahead of
refreshBalances() fails that case and only it.
This commit was merged in pull request #319.
This commit is contained in:
@@ -3,7 +3,11 @@
|
||||
// non-sensitive calls to the configured Ethereum JSON-RPC endpoint.
|
||||
|
||||
const { DEFAULT_RPC_URL } = require("../shared/constants");
|
||||
const { SUPPORTED_CHAIN_IDS, networkByChainId } = require("../shared/networks");
|
||||
const {
|
||||
SUPPORTED_CHAIN_IDS,
|
||||
networkById,
|
||||
networkByChainId,
|
||||
} = require("../shared/networks");
|
||||
const { onChainSwitch } = require("../shared/chainSwitch");
|
||||
const {
|
||||
state,
|
||||
@@ -663,12 +667,28 @@ async function handleRpc(method, params, origin) {
|
||||
return { result: [] };
|
||||
}
|
||||
|
||||
if (method === "eth_chainId") {
|
||||
return { result: currentNetwork().chainId };
|
||||
}
|
||||
|
||||
if (method === "net_version") {
|
||||
return { result: currentNetwork().networkVersion };
|
||||
// Both answered from currentNetwork(), which reads the module-level state
|
||||
// singleton, and nothing populates that at module scope. A worker revived
|
||||
// by the page's own message therefore held DEFAULT_STATE and told a page
|
||||
// it was on mainnet while the user was on Sepolia
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/317).
|
||||
//
|
||||
// Answered from getState() rather than by loading the singleton. Any page
|
||||
// reaches these two — neither is gated on a connection, and the injected
|
||||
// provider sends eth_chainId on every page load — and loadState() replaces
|
||||
// state.wallets wholesale, which would detach the address objects an
|
||||
// in-flight backgroundRefresh() is mutating across its network round trip,
|
||||
// so its saveState() would persist the pre-refresh balances while still
|
||||
// stamping lastBalanceRefresh. getState() is the detached per-call storage
|
||||
// read the other read handlers here already use.
|
||||
// networkById(undefined) falls back to mainnet, matching the default for a
|
||||
// profile with no stored networkId.
|
||||
if (method === "eth_chainId" || method === "net_version") {
|
||||
const s = await getState();
|
||||
const net = networkById(s.networkId);
|
||||
return {
|
||||
result: method === "eth_chainId" ? net.chainId : net.networkVersion,
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "wallet_switchEthereumChain") {
|
||||
|
||||
Reference in New Issue
Block a user