2

I'm trying to figure out with OCW. And have a question is it possible to configure OCW so that if one node starts an HTTP call in OCW other nodes will skip it. I checked all examples I could find regarding OCW and it looks like a synchronization mechanism with off-chain storage working for OCWs from different pallets in one node. Also tried to use is_validator but it returns true for both nodes even if I start it without the validator flag. What I'm trying to achieve is when some event happens OCW sends a request to an external service and starts some job, and job results after success are stored on-chain.

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
    fn offchain_worker(block_number: T::BlockNumber) {
        log::info!("OCW {:?}", block_number);
        if <frame_system::Pallet<T>>::read_events_no_consensus()
            .into_iter()
            .filter_map(|event_record| {
                let local_event = <T as Config>::Event::from(event_record.event);
                local_event.try_into().ok()
            })
            .any(|event| matches!(event, Event::SomethingStored(..)))
        {
            let should_send = Self::choose_transaction_type(block_number);
            let res = match should_send {
                TransactionType::Signed => Self::call_api_and_send_transaction(),
                TransactionType::None => Ok(()),
            };
            if let Err(e) = res {
                log::error!("Error: {}", e);
            }
            log::info!("Hello world from OCW {:?}", block_number);
        }
    }
}

impl<T: Config> Pallet<T> {
    fn choose_transaction_type(block_number: T::BlockNumber) -> TransactionType {
        const RECENTLY_SENT: () = ();
        log::info!("Choose transaction ");
        let val = StorageValueRef::persistent(b"example_ocw::last_send");

        let res =
            val.mutate(|last_send: Result<Option<T::BlockNumber>, StorageRetrievalError>| {
                match last_send {
                    Ok(Some(block)) if block_number < block + T::GracePeriod::get() => {
                        log::info!("Recently Send ");
                        Err(RECENTLY_SENT)
                    },
                    _ => Ok(block_number),
                }
            });

        match res {
            Ok(_block_number) => TransactionType::Signed,
            Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => {
                TransactionType::None
            },

            Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None,
        }
    }

    fn call_api_and_send_transaction() -> Result<(), &'static str> {
        //TODO
        let validator = sp_io::offchain::is_validator();

        if validator {
            log::info!("Call api and send transaction ");
            log::info!("is validator {:?} ", validator);
        }
        
        Ok(())
    }
}

starting node 1

./target/release/node-template \
--base-path /tmp/alice \
--chain dev \
--alice \
--port 30333 \
--ws-port 9945 \
--rpc-port 9933 \
--node-key 0000000000000000000000000000000000000000000000000000000000000001 \
--telemetry-url "wss://telemetry.polkadot.io/submit/ 0" \
--validator \
--rpc-cors all \
--ws-external \
--rpc-external \
--rpc-methods Unsafe \

starting node 2

./target/release/node-template \
--base-path /tmp/bob \  
--chain dev \
--bob \  
--port 30334 \
--ws-port 9946 \
--rpc-port 9934 \
--telemetry-url "wss://telemetry.polkadot.io/submit/ 0" \
--bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp

1 Answer 1

2

"And have a question is it possible to configure OCW so that if one node starts an HTTP call in OCW other nodes will skip it", you can add a judgement to make sure only the author of the block will execute the ocw logic, such as block.header.author == current_account. Or you can add judgement in the extrinsic's handler method, if the answer has been submitted then ignore following answers which are submitted by other nodes.

I'll show how the second approach can be implemented:

  • Pallet's extrinsic api:

     #[pallet::weight(100)]
     pub fn submit_from_ocw(
         origin: OriginFor<T>,
         key: SomeType,
         Value: SomeType,
     ) {
       //guarantee only one validator can execute successfully
       if (store.contains(key)) {
          Error::XXX;
       }
       ...
     }
    
  • ocw part

    fn offchain_worker(block_number: BlockNumberFor) { for task in pending_tasks { //guarantee only the block that task is created at, will process the task if (task.create_at == block_number) { ... Pallet::::submit_from_ocw(key, value); } } }

9
  • Could you please check what I'm doing wrong. After event it is calling api which returns random values and I can see that for each node numbers is different. It looks like 2 calls was made. [link] (github.com/Leo777/substrate-node/blob/OCW/pallets/template/src/…) Jul 11, 2022 at 20:17
  • By saying "It looks like 2 calls was made", are these two call in the same block, or in different block ? If in different block, then you need add some logic to guard, if the 'task' has been processed in one block, then no other block can get it again. For eg, add a proccess_block storage for it, only process it when this field is not assigned. Jul 12, 2022 at 3:45
  • Correction: For eg, add a created_at_block storage field for it, only process it when created_at_block == current_block. Jul 12, 2022 at 3:51
  • @LeuBarycheuski add correction to my answer Jul 12, 2022 at 5:17
  • Thank you for your response! It is two calls in the same block. Sorry for being annoying but maybe you have some examples for that case? Jul 12, 2022 at 6:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.