poanetwork / hydrabadger

A simple, experimental, peer-to-peer network node using the hbbft consensus algorithm which can be run as a standalone client or used as a library
GNU General Public License v3.0
22 stars 9 forks source link

Send custom transactions #24

Open KORuL opened 5 years ago

KORuL commented 5 years ago

Hi! We use this repo for creating crypto-based messenger app. The are some issues and improvements suggestoins we want to introduce. It would be cool if you add the following capablities we need:

afck commented 5 years ago

Hydrabadger is already generic in the type of contribution C. The idea is that C is a list of transactions, but it can be anything! So I'm not sure I understand the issue: Which kinds of transactions does the application need?

KORuL commented 5 years ago

I mean to make an API for hydrabadger that allows you to send a transaction. Now it is implemented in the form of a lambda function, which is very inconvenient when you need to make a transaction from the outside. I work at the java level, and hydrabadger is a dll. Therefore, I would like an accessible API to send a transaction.

c0gent commented 5 years ago

The Hydrabadger::propose_user_contribution method is what you want. You might want to add an ffi layer which includes a function roughly like the following:

#[no_mangle]
pub extern "C" fn send_transaction<C: Contribution, N: NodeId>(
    hdb: &Hydrabadger<C, N>, 
    txn: Transaction) 
    -> i32
{

    hdb.propose_user_contribution(vec![txn]).{convert_error_code_to_i32_etc};
}

Please let me know if that will work or if I'm not understanding you correctly.

KORuL commented 5 years ago

At first glance it looks like what you need. How to save an object on hdb, so that after the run_node method, you can call this method after some time interval? Will this method work correctly if it is called when the status of the validator belongs to another?

c0gent commented 5 years ago

How to save an object on hdb, so that after the run_node method, you can call this method after some time interval?

Clone your Hydrabadger instance before calling node or run_node e.g.: hdb.clone().run_node(...). A Hydrabadger object is internally Arc-wrapped.

Will this method work correctly if it is called when the status of the validator belongs to another?

What do you mean by "the status of the validator belongs to another?"

KORuL commented 5 years ago

This method does not take into account that during a call, its user may have non-validator status.

    /// Handles a incoming batch of user transactions.
    pub fn propose_user_contribution(&self, txn: C) -> Result<(), Error> {
        if self.is_validator() {
            self.send_internal(InternalMessage::hb_contribution(
                self.inner.nid.clone(),
                OutAddr(*self.inner.addr),
                txn,
            ));
            Ok(())
        } else {
            Err(Error::ProposeUserContributionNotValidator)
        }
    }

In order not to request the status of a validator or not after a certain period of time,

    pub fn is_validator(&self) -> bool {
        self.state_dsct_stale() == StateDsct::Validator
    }

can you create a callback function to get the status of the event as a validator?

c0gent commented 5 years ago

I'm not sure I completely understand what you're asking for. If you attempt to call propose_user_contribution when the node is not a validator, an Err(Error::ProposeUserContributionNotValidator) is returned.

Can you please clarify what do you mean by "create a callback function to get the status of the event as a validator?"

KORuL commented 5 years ago

I work with Hydrabadger as a dll through certain methods. And it would be convenient for me to have a method to send a message, which then resolved the situation itself when the validator and when not. At the application level, I call the method to send a message, the corresponding method is called in hydrabadger. What is the most convenient way to resolve the situation when I am not a validator at the moment? to start a stream and wait when I become a validator, asking again after a certain time interval? Or maybe there is a way to implement something like a callback function that I am now a validator and at this point to send a message? Help please understand how better and some sort of listing

c0gent commented 5 years ago

I would recommend simply checking the result of the call to propose_user_contribution for an Err(Error::ProposeUserContributionNotValidator) return value, then returning an error value (perhaps as an integer, -1 for example) from send_transaction.

In your application code, simply check for a returned error (check for -1 etc.) then take the appropriate action, perhaps queueing your message for retry.

An even more robust solution is to use some notion of state (connected/disconnected/etc.) in your application so that it knows when it can and cannot send messages. You will want to check this state before attempting to send any message. How you want to design it is up to you but I would recommend simply queuing all messages in one step, then attempting to flush the queue (by sending all queued messages as a contribution) in a separate step.

As an additional layer of robustness you could add a verification / acknowledgement mechanism which ensures that each message was received before completely removing it from the queue (where each queued message has an unsent / sent / ackd state for example).

KORuL commented 5 years ago

An even more robust solution is to use some notion of state (connected/disconnected/etc.) in your application so that it knows when it can and cannot send messages. You will want to check this state before attempting to send any message.

Is there a way to find out if hydrabadger can send it or not?

KORuL commented 5 years ago

I do not fully understand if we now take hydrabadger and on top of it make a function similar to this:

    pub fn send_transaction(&self, txn: String) {
        let mut txn_: Option<Transaction> = None;
        txn_ = Some(Transaction(txn.to_string()));

        while self.hydrabadger.lock().clone().unwrap().is_validator() {
            let ten_millis = time::Duration::from_millis(10);
            thread::sleep(ten_millis);
        }

        match self.hydrabadger.lock().clone().unwrap().propose_user_contribution(vec![txn_])
        {
            Err(e) => {
                warn!("send_transaction Error: {}", e.to_string());
            }
            _ => {
                warn!("send_transaction success");
            }
        };
    }

then the application will crash because it did not send an empty transaction when it was a validator. Can you help me write a universal method for me? So that I called the method and it waited for the status of the validator and sent the transaction, and I returned the code successful. if there is no transaction, then sent it itself empty.

KORuL commented 5 years ago

Help write a universal method for sending transactions. If I enter a transaction from the outside, the method must wait for the status of the validator and then send. If there is no transaction from outside, then when the status of the validator comes, an empty transaction is sent. Now we have to use the workaround that allows it, but it is wrong

c0gent commented 5 years ago

If you can give me a link to your code I might be able to help advise you more, though I can't promise how soon. Let me just say though that there are a many different ways to implement what you want. Ideally your Java code has some notion of the state of the Hydrabadger connection. I think it would take me quite a bit of time to properly tell you how to do this but I'll take a look.

KORuL commented 5 years ago

26