基于Substrate的项目中,使用polkadot.js开发的Node.js应用无法修改链上存储的问题求助
Let’s break down why your Node.js app isn’t updating the Substrate chain storage and fix it step by step. I’ve gone through your code and spotted several key issues that are causing the problem:
1. You’re Not Actually Sending Transactions to the Chain
In your initial code, you only created transaction objects like api.tx.carpooling.addNewCab() but never signed and submitted them to the chain. Transactions won’t be processed until you call signAndSend() with a valid account. Even your updated code has execution order issues that break the workflow.
2. Async Code Execution Order Is Wrong
In your updated code, you’re trying to call bookRide() right after starting the addNewCab() promise. This means you’re attempting to book a driver that hasn’t been added to the chain yet—triggering the DriverDoesNotExist error and failing the transaction.
3. Frontend Type Definitions Don’t Match the Chain’s Struct
Your chain’s SDriver struct includes a destination field, but your frontend DriverOf type definition is missing it. This causes encoding errors when sending transactions, so the node can’t parse your data correctly.
4. The getDriver RPC Method Isn’t Implemented On-Chain
You defined api.rpc.carpooling.getDriver() in your frontend, but there’s no corresponding RPC endpoint in your pallet. That’s why you’re getting None—the method doesn’t actually fetch any data from storage.
Step-by-Step Fixes
Fix 1: Align Frontend Types with the Chain
Update your types configuration to match the SDriver struct exactly. Note that Polkadot.js uses arrays to represent tuples (not parentheses like Rust):
types: { DriverOf: { id: 'u32', car_no: 'Hash', location: ['u32', 'u32'], price: 'u32', destination: ['u32', 'u32'], // Add the missing destination field }, CustomerOf: { id: 'u32', name: 'Hash', location: ['u32', 'u32'], }, }
Fix 2: Fix Async Execution Order & Properly Send Transactions
Wrap the bookRide call inside the addNewCab success callback to ensure the driver exists before booking. Also, add proper transaction status and error handling:
// Add keyring setup at the top of your main function const { Keyring } = require('@polkadot/keyring'); const keyring = new Keyring({ type: 'sr25519' }); const alice = keyring.addFromUri('//Alice'); // Use a valid funded account // ... try { const cabNo = 'UP76 E 8550'; const output = digestMessage(cabNo); output.then(async (hash) => { // 1. First, add the driver const addDriverTx = api.tx.carpooling.addNewCab(12, { id: 12, car_no: hash, location: [10, 20], price: 50, destination: [30, 40] }); // Send the add driver transaction and wait for confirmation await addDriverTx.signAndSend(alice, ({ status, events, dispatchError }) => { if (dispatchError) { handleTxError(dispatchError); return; } if (status.isInBlock) { console.log('Add driver tx included in block'); // Check for success event (make sure your pallet emits this!) events.forEach(({ event }) => { if (event.section === 'carpooling' && event.method === 'DriverAdded') { console.log('Driver added successfully!'); // 2. Now book the ride since the driver exists bookRide(api, alice, 12, 99); } }); } else if (status.isFinalized) { console.log('Add driver tx finalized'); } }); }).catch(e => console.log('Error processing cab number:', e)); } catch (error) { console.log('Main error:', error); } // Helper function to handle transaction errors function handleTxError(dispatchError) { if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); const { section, name, documentation } = decoded; console.error(`Tx failed: ${section}.${name} — ${documentation.join(' ')}`); } else { console.error(`Tx failed: ${dispatchError.toString()}`); } } // Helper function to book a ride async function bookRide(api, account, driverId, customerId) { const bookRideTx = api.tx.carpooling.bookRide(driverId, customerId); await bookRideTx.signAndSend(account, ({ status, events, dispatchError }) => { if (dispatchError) { handleTxError(dispatchError); return; } if (status.isInBlock) { console.log('Book ride tx included in block'); events.forEach(({ event }) => { if (event.section === 'carpooling' && event.method === 'CabBooked') { console.log('Cab booked successfully!'); // Query the storage directly to confirm the update queryBooking(api, driverId); } }); } else if (status.isFinalized) { console.log('Book ride tx finalized'); } }); } // Helper function to query booking storage async function queryBooking(api, driverId) { // Query the pallet's storage directly instead of relying on unimplemented RPC const bookedCustomer = await api.query.carpooling.booking(driverId); console.log(`Booked customer for driver ${driverId}: ${bookedCustomer.toString()}`); }
Fix 3: Implement the getDriver RPC Endpoint (If Needed)
If you want to use api.rpc.carpooling.getDriver(), add this RPC implementation to your pallet:
#[pallet::rpc] pub trait Rpc<T: Config> { #[rpc(name = "carpooling_getDriver")] fn get_driver(&self, driver_id: u32) -> Result<Option<SDriver<T::Hash>>, DispatchError>; } impl<T: Config> Rpc<T> for Pallet<T> { fn get_driver(&self, driver_id: u32) -> Result<Option<SDriver<T::Hash>>, DispatchError> { // Fetch the driver from storage let driver = <Driver<T>>::get(&driver_id); Ok(driver) } }
Debugging Tips
- Check Node Logs: Run your Substrate node with
./target/release/node-template --dev -lruntime=debugto see detailed transaction errors. - Test with Polkadot.js Apps: First confirm your pallet works via the local Apps interface (
http://localhost:8000) to rule out chain-side issues. - Verify Account Balance: Make sure the account you’re using (Alice) has enough balance to pay transaction fees.
内容的提问来源于stack exchange,提问作者Aman Verma

