Files
arkhe-origin-chain/crates/origin-chain-runtime/examples/firmar_como_wallet.rs
T
CharonandClaude Opus 5.5 22bab19932 feat(rp1): la cadena corre en la Pi 5 de la casa — binario aarch64, despliegue sin root y medida continua
David, 24-oct: «lo que podamos hacer en la rp1 hagamoslo no esperemos la VPS porfavor». Y ese
mismo día, por Ergon: la rp1 aloja el servidor de arranque de la red.

scripts/rp1/:
- construir-aarch64.sh + Dockerfile.aarch64: origin-chain-node release, con el WASM real, para
  linux/aarch64 en el Mac.
  - Imagen bookworm (glibc 2.36; la rp1 tiene 2.41).
  - Target en un volumen de Docker: la VM no ve el disco externo de compilación si se montó
    después de arrancar Docker, y reiniciarla tiraría los contenedores de otras hermanas.
  - Copia el binario con docker cp y avisa de cómo borrar el volumen.
- desplegar.sh: copia el binario con su sha256 (y deja el anterior para volver atrás), copia
  arrancar.sh y medir.sh, e instala tres líneas en el crontab del usuario: @reboot, vigilante
  cada 5 min y medida cada 5 min. Sin root y sin systemd.
- arrancar.sh: idempotente.
  - taskset -c 0-2: el nodo nunca toca el núcleo 3, el santuario de origin-banco de Laurha.
  - nice 10 + ionice 7: la CI de la casa va primero.
  - RPC sólo en 127.0.0.1; P2P en el 30333.
  - Cadena dev con Alice: es la testnet de la casa, con llaves públicas de desarrollo; ningún
    valor real.
- medir.sh: una fila CSV cada 5 min con best, finalizado, pares, RSS, CPU, MB de datos, load,
  temperatura y estado de la CI.

scripts/bench/max-de-n.py: funde N weights.rs del mismo pallet cobrando el MÁXIMO de cada
coeficiente.
- Por qué: Laurha midió el 24-oct en su santuario que 8 procesos idénticos de frame-omni-bencher
  separan el base weight hasta un 66 %.
- Se niega si los esqueletos difieren (otra lista de extrinsics o de storage) o si los conteos
  de reads/writes no coinciden. Avisa si N < 5.
- Controles: tres copias idénticas salen idénticas byte a byte; el máximo cae sólo donde se
  subió; un conteo de reads distinto se rechaza; un extrinsic renombrado se rechaza.

firmar_como_wallet: modo cuenta "<frase>" [//derivación], que da la llave sr25519 y la SS58 que
verá un wallet Substrate. Son los vectores para la migración de la wallet Origin! (Irhis).

fix(node): build-spec emite el chain spec REAL cuando el binario lleva el WASM.
- Antes, aunque el binario llevara el WASM real, emitía siempre el de mayo, con :code = «0x»
  simulado, y --raw no sacaba el génesis en crudo.
- Ahora usa el mismo spec con el que arranca run (node_chain_spec), y --raw lleva el génesis
  entero.
- Sin WASM sigue el simulado, pero avisa de que no sirve para unirse a una red. Mainnet sigue
  cerrada por la compuerta L-3.
- Por qué importa, medido hoy: el WASM del runtime NO es reproducible entre máquinas. El mismo
  commit compilado en el Mac y en Linux da génesis distintos (0xe80c… frente a 0x0926…), y el
  nodo del Mac baneó al de la rp1 con «Genesis mismatch». El génesis tiene que fijarlo un chain
  spec que se reparte, no quien compila.
- arrancar.sh usa ~/origin-chain/casa.json cuando existe (exportado en la propia rp1). Así un
  binario nuevo nunca crea otro génesis sobre los mismos datos.

Medido en vivo el 24-oct, todo por RPC firmado como un wallet:
- Nodo en la rp1 (Pi 5, Debian 13): bloques cada 6 s y finalidad a 2-3 bloques. RSS ~200 MB y
  afinidad 0-2 con nice 10.
- El vigilante de cron lo resucita con el mismo peer id
  (12D3KooWDWm5p3Z8Y5DddRki6M6nM1A2xUVQzPqNU1DW53uB6BVi) y sin regenerar la llave.
- E2E en la rp1: remark → ExtrinsicSuccess; finalize_override → ProposalNotFound; uphold0 →
  ZeroWeightWindowVote.
- casa.json exportado en la rp1 = estado del bloque 0 de la cadena viva: 46 de 46 claves
  idénticas. El nodo reinició sobre él con el mismo génesis y siguió desde el #155.
- Red de dos máquinas por la LAN: el nodo del Mac, con casa.json y la rp1 como bootnode,
  sincronizó (mismo best, mismo finalizado). Una transacción enviada al nodo del Mac la incluyó
  la rp1 en el bloque #167, con el mismo hash blake2-256 que devolvió author_submitExtrinsic.

Defectos encontrados en mis propios scripts antes de dejarlos publicados:
- desplegar.sh instalaba un crontab VACÍO: con set -e, el grep -v sobre un crontab vacío
  abortaba la subshell antes de los echo.
- medir.sh dejaba best/finalizado en NA: set -u con $2 sin definir, y stderr se iba con cron.
- max-de-n.py reescribía 16416 como 16_416 (la prueba de identidad lo cazó).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:19:52 -05:00

161 lines
7.6 KiB
Rust

//! Firma extrinsics como los firma un wallet, para el E2E por RPC contra un nodo vivo.
//!
//! Existe porque un E2E que solo mira bloques no ve lo que ve un usuario: el 2026-10-07 el
//! nodo producía y finalizaba bloques mientras rechazaba con «bad signature» toda firma de
//! wallet (el runtime se creía `spec 0`, ver `type Version` en `configs.rs`). Esta
//! herramienta firma igual que polkadot.js: la versión que anuncia el runtime, el hash del
//! génesis, era inmortal, sin metadata hash.
//!
//! ```text
//! cargo build -p origin-chain-runtime --example firmar_como_wallet # con SKIP_WASM_BUILD=1
//! F=target/debug/examples/firmar_como_wallet
//!
//! firmar <llamada> <nonce> <genesis_hex> [spec tx] extrinsic firmado por //Alice, en hex
//! llamadas: remark · finalize_override (propuesta 0x09…09) · uphold0 (peso 0)
//! set_code:<ruta.wasm> (sudo de //Alice, ensayo de upgrade sin fork)
//! spec y tx: los que da `state_getRuntimeVersion`. Por defecto, los de `VERSION`
//! compilado. Tras un `set_code` hay que pasarlos: el wallet firma con la versión
//! nueva que anuncia el nodo, no con la que se compiló aquí.
//! eventos <hex de System::Events> decodifica los eventos de un bloque
//! cuenta "<frase BIP39>" [//derivación] llave sr25519 y dirección SS58 (42) que verá un wallet
//! Substrate (substrate-bip39 → mini-secret → sr25519): vectores para la wallet Origin!
//! verificar <hex_extrinsic> <genesis_hex> pasa la firma por el check del runtime nativo
//! sobre el preset development (sin nodo)
//! ```
//!
//! Recorrido por RPC (nodo `run --dev --rpc-port 9955`):
//! `chain_getBlockHash 0` → génesis; `system_accountNextIndex` → nonce;
//! `author_submitExtrinsic` con el hex; y en el bloque que lo incluye,
//! `state_getStorage 0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7`
//! (System::Events) → `eventos`.
use frame_system::EventRecord;
use origin_chain_runtime::{
Runtime, RuntimeCall, RuntimeEvent, TxExtension, UncheckedExtrinsic, VERSION,
};
use parity_scale_codec::{Decode, Encode};
use sp_core::{sr25519, Pair, H256};
use sp_runtime::{
generic::{Era, SignedPayload},
MultiAddress, MultiSignature,
};
fn hex(arg: &str) -> Vec<u8> {
sp_core::bytes::from_hex(arg).unwrap_or_else(|e| panic!("hex inválido {arg}: {e:?}"))
}
fn llamada(nombre: &str) -> RuntimeCall {
let id = origin_chain_governance::ProposalId([9u8; 32]);
match nombre {
"remark" => RuntimeCall::System(frame_system::Call::remark {
remark: b"origin".to_vec(),
}),
"finalize_override" => {
RuntimeCall::Governance(origin_chain_governance::Call::finalize_override {
proposal_id: id,
})
}
"uphold0" => RuntimeCall::Governance(origin_chain_governance::Call::uphold_council_veto {
proposal_id: id,
stake_weight: 0,
}),
otra => match otra.strip_prefix("set_code:") {
Some(ruta) => {
let code = std::fs::read(ruta).unwrap_or_else(|e| panic!("{ruta}: {e}"));
RuntimeCall::Sudo(pallet_sudo::Call::sudo {
call: Box::new(RuntimeCall::System(frame_system::Call::set_code { code })),
})
}
None => panic!(
"llamada desconocida: {otra} (remark · finalize_override · uphold0 · set_code:<ruta>)"
),
},
}
}
fn firmar(call: RuntimeCall, nonce: u32, genesis: H256, spec: u32, tx: u32) -> UncheckedExtrinsic {
let alice = sr25519::Pair::from_string("//Alice", None).expect("//Alice");
let tx_ext: TxExtension = (
frame_system::CheckNonZeroSender::<Runtime>::new(),
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(Era::Immortal),
frame_system::CheckNonce::<Runtime>::from(nonce),
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false),
frame_system::WeightReclaim::<Runtime>::new(),
);
// Lo que pone el wallet: la versión que anuncia el RPC, no la que crea el storage.
let implicito = ((), spec, tx, genesis, genesis, (), (), (), None, ());
let payload = SignedPayload::<RuntimeCall, TxExtension>::from_raw(call, tx_ext, implicito);
let firma = payload.using_encoded(|p| alice.sign(p));
let (call, tx_ext, _) = payload.deconstruct();
UncheckedExtrinsic::new_signed(
call,
MultiAddress::Id(alice.public().into()),
MultiSignature::Sr25519(firma),
tx_ext,
)
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let modo = args.get(1).map(String::as_str).unwrap_or("");
match modo {
"firmar" => {
let nonce: u32 = args[3].parse().expect("nonce");
let genesis = H256::from_slice(&hex(&args[4]));
let spec = args
.get(5)
.map_or(VERSION.spec_version, |v| v.parse().expect("spec"));
let tx = args
.get(6)
.map_or(VERSION.transaction_version, |v| v.parse().expect("tx"));
let xt = firmar(llamada(&args[2]), nonce, genesis, spec, tx);
// Hex completo: `HexDisplay` recorta con «...» pasados 1024 bytes, y un
// `set_code` lleva el runtime entero (MB).
println!("{}", sp_core::bytes::to_hex(&xt.encode(), false));
}
"eventos" => {
let bytes = hex(&args[2]);
let eventos = Vec::<EventRecord<RuntimeEvent, H256>>::decode(&mut &bytes[..])
.expect("System::Events");
for e in eventos {
println!("{:?}", e.event);
}
}
"cuenta" => {
use sp_core::crypto::Ss58Codec;
let uri = format!("{}{}", args[2], args.get(3).map(String::as_str).unwrap_or(""));
let par = sr25519::Pair::from_string(&uri, None).expect("frase o derivación inválida");
let publica = par.public();
println!("sr25519 publica 0x{}", sp_core::hexdisplay::HexDisplay::from(&publica.0));
println!("ss58 (42) {}", publica.to_ss58check());
}
"verificar" => {
use sp_runtime::traits::Checkable;
let bytes = hex(&args[2]);
let genesis = H256::from_slice(&hex(&args[3]));
let xt = UncheckedExtrinsic::decode(&mut &bytes[..]).expect("extrinsic");
let json = origin_chain_runtime::genesis_config_presets::get_preset(
&sp_genesis_builder::PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET),
)
.expect("preset development");
let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
frame_support::genesis_builder_helper::build_state::<
origin_chain_runtime::RuntimeGenesisConfig,
>(json)
.expect("estado del génesis");
frame_system::Pallet::<Runtime>::set_block_number(5);
frame_system::BlockHash::<Runtime>::insert(0, genesis);
let r = xt.check(&frame_system::ChainContext::<Runtime>::default());
println!("check del runtime: {:?}", r.map(|_| "firma aceptada"));
});
}
_ => panic!("modo: firmar · eventos · verificar · cuenta (ver la cabecera del fichero)"),
}
}