mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-08-04 21:05:42 +00:00
diag: probe both era encodings when building the multisig weight-estimation extrinsic
Approving a multisig operation on Pezkuwi Asset Hub fails with "Failed to encode extension CheckMortality". The on-device stack puts it in wrapInFakeExtrinsic, reached only from estimateCallWeight — asMulti needs a max_weight, so a throwaway signed extrinsic is built to measure the inner call. Plain transfers never reach that path, which is why sending HEZ works while approving does not. The same failure on Polkadot Asset Hub was fixed by gating the era encoding on chain identity, and Pezkuwi Asset Hub satisfies that gate: its genesis hash matches the constant, so PezkuwiCheckImmortal should already be in use. Reading the code cannot say which side actually fails. So build the extrinsic both ways and report each outcome with its full cause chain. One reproduction then names the encoding the chain accepts, and the permanent fix follows from that rather than from another guess. Diagnostic only — the probe and the DIAG constant come out with the real fix.
This commit is contained in:
+74
-24
@@ -47,6 +47,9 @@ private typealias CallWeightsByType = Map<String, Deferred<WeightV2>>
|
||||
|
||||
private const val LEAVE_SOME_SPACE_MULTIPLIER = 0.8
|
||||
|
||||
// DIAGNOSTIC BUILD — remove with the probe in wrapInFakeExtrinsic.
|
||||
private const val DIAG = "PezMsigDiag"
|
||||
|
||||
@FeatureScope
|
||||
internal class RealExtrinsicSplitter @Inject constructor(
|
||||
private val rpcCalls: RpcCalls,
|
||||
@@ -135,6 +138,19 @@ internal class RealExtrinsicSplitter @Inject constructor(
|
||||
return split
|
||||
}
|
||||
|
||||
/**
|
||||
* DIAGNOSTIC BUILD — not for release.
|
||||
*
|
||||
* "Failed to encode extension CheckMortality" reproduces here, and only here, when
|
||||
* approving a multisig operation on Pezkuwi Asset Hub. This path is multisig-only:
|
||||
* asMulti needs a max_weight, so a throwaway signed extrinsic is built to measure
|
||||
* the inner call. Plain transfers never reach it, which is why they succeed.
|
||||
*
|
||||
* Static reading could not tell which side of the isPezkuwiChain gate fails, so this
|
||||
* build tries BOTH era encodings and reports the outcome of each. Read the PezMsigDiag
|
||||
* lines to see which one the chain accepts, then wire that choice in permanently and
|
||||
* delete this.
|
||||
*/
|
||||
private suspend fun wrapInFakeExtrinsic(
|
||||
signer: NovaSigner,
|
||||
call: GenericCall.Instance,
|
||||
@@ -142,35 +158,69 @@ internal class RealExtrinsicSplitter @Inject constructor(
|
||||
chain: Chain
|
||||
): SendableExtrinsic {
|
||||
val genesisHash = chain.requireGenesisHash().fromHex()
|
||||
val isPezkuwi = chain.isPezkuwiChain
|
||||
|
||||
val builder = ExtrinsicBuilder(
|
||||
runtime = runtime,
|
||||
extrinsicVersion = ExtrinsicVersion.V4,
|
||||
batchMode = BatchMode.BATCH,
|
||||
).apply {
|
||||
// Use custom CheckMortality for Pezkuwi chains to avoid DictEnum type lookup issues.
|
||||
// Gated on chain identity (not signed-extension presence): both Pezkuwi and Polkadot
|
||||
// Asset Hub declare "AuthorizeCall", so that alone can't tell the chains apart, and
|
||||
// PezkuwiCheckImmortal's raw DictEnum value fails Polkadot's own Era type codec.
|
||||
if (chain.isPezkuwiChain) {
|
||||
setTransactionExtension(PezkuwiCheckImmortal(genesisHash))
|
||||
} else {
|
||||
setTransactionExtension(CheckMortality(Era.Immortal, genesisHash))
|
||||
}
|
||||
setTransactionExtension(CheckGenesis(chain.requireGenesisHash().fromHex()))
|
||||
setTransactionExtension(ChargeTransactionPayment(BigInteger.ZERO))
|
||||
setTransactionExtension(CheckMetadataHash(CheckMetadataHashMode.Disabled))
|
||||
setTransactionExtension(CheckSpecVersion(0))
|
||||
setTransactionExtension(CheckTxVersion(0))
|
||||
android.util.Log.e(
|
||||
DIAG,
|
||||
"chain='${chain.name}' id=${chain.id} isPezkuwiChain=$isPezkuwi " +
|
||||
"genesis=${chain.requireGenesisHash()}"
|
||||
)
|
||||
android.util.Log.e(
|
||||
DIAG,
|
||||
"signedExtensions=${runtime.metadata.extrinsic.signedExtensions.map { it.id }}"
|
||||
)
|
||||
|
||||
CustomTransactionExtensions.defaultValues(runtime).forEach(::setTransactionExtension)
|
||||
// Builds the fake extrinsic with one specific era encoding. Kept as a local so the
|
||||
// two attempts differ in exactly one thing and nothing else.
|
||||
suspend fun attempt(usePezkuwiEra: Boolean): Result<SendableExtrinsic> = runCatching {
|
||||
ExtrinsicBuilder(
|
||||
runtime = runtime,
|
||||
extrinsicVersion = ExtrinsicVersion.V4,
|
||||
batchMode = BatchMode.BATCH,
|
||||
).apply {
|
||||
if (usePezkuwiEra) {
|
||||
setTransactionExtension(PezkuwiCheckImmortal(genesisHash))
|
||||
} else {
|
||||
setTransactionExtension(CheckMortality(Era.Immortal, genesisHash))
|
||||
}
|
||||
setTransactionExtension(CheckGenesis(chain.requireGenesisHash().fromHex()))
|
||||
setTransactionExtension(ChargeTransactionPayment(BigInteger.ZERO))
|
||||
setTransactionExtension(CheckMetadataHash(CheckMetadataHashMode.Disabled))
|
||||
setTransactionExtension(CheckSpecVersion(0))
|
||||
setTransactionExtension(CheckTxVersion(0))
|
||||
|
||||
call(call)
|
||||
CustomTransactionExtensions.defaultValues(runtime).forEach(::setTransactionExtension)
|
||||
|
||||
val signingContext = signingContextFactory.default(chain)
|
||||
signer.setSignerDataForFee(signingContext)
|
||||
call(call)
|
||||
|
||||
val signingContext = signingContextFactory.default(chain)
|
||||
signer.setSignerDataForFee(signingContext)
|
||||
}.buildExtrinsic()
|
||||
}
|
||||
|
||||
return builder.buildExtrinsic()
|
||||
fun report(label: String, result: Result<SendableExtrinsic>) {
|
||||
result.fold(
|
||||
onSuccess = { android.util.Log.e(DIAG, "$label -> OK") },
|
||||
onFailure = { e ->
|
||||
// The message alone has been the whole diagnosis so far; the cause chain
|
||||
// is what actually names the failing type.
|
||||
val causes = generateSequence(e) { it.cause }.joinToString(" <- ") {
|
||||
"${it::class.java.simpleName}: ${it.message}"
|
||||
}
|
||||
android.util.Log.e(DIAG, "$label -> FAIL $causes", e)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Preferred first: whatever the current gate would have chosen on its own.
|
||||
val preferred = attempt(usePezkuwiEra = isPezkuwi)
|
||||
report(if (isPezkuwi) "PezkuwiCheckImmortal(gate choice)" else "CheckMortality(gate choice)", preferred)
|
||||
preferred.getOrNull()?.let { return it }
|
||||
|
||||
val alternative = attempt(usePezkuwiEra = !isPezkuwi)
|
||||
report(if (isPezkuwi) "CheckMortality(alternative)" else "PezkuwiCheckImmortal(alternative)", alternative)
|
||||
|
||||
return alternative.getOrElse { throw preferred.exceptionOrNull()!! }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user