Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FullNode support #21

Merged
merged 33 commits into from
May 12, 2017
Merged

Add FullNode support #21

merged 33 commits into from
May 12, 2017

Conversation

alekseysidorov
Copy link
Contributor

Closes #20

// Verify tx content
self.check_anchoring_tx_content(&tx, cfg, state)?;
// Checks with access to the `bitcoind`
if let Some(ref client) = self.client {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slowli
What use of verifying anchoring without bitcoind? Consistency of inner exonum transactions? Does it really matter without comparison against bitcoin transactions?.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auditing nodes should probably verify all Exonum transactions; this is a reasonable default. In the case of anchoring, right now internal Exonum verifications without bitcoind don't matter much, but they could become much more useful if we introduce a Bitcoin Blockchain oracle.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slowli @alekseysidorov I assume verification of inner consistency should be done during Transaction::execute(). If the transaction doesn't satisfy necessary restrictions, it should be discarded without changing state_hash. If state_hash of node's block differs from majority's' state_hash, it'll get a panic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will works fine if during the MsgAnchoringLect execution we ensure that at least 2/3+1 known prev_hash as lect.
After when this check will be implemented auditor_node will check anchoring consistency automaticaly during blocks execution.


// Get previous tx
let prev_txid = tx.prev_hash().be_hex_string();
let prev_tx = if let Some(tx) = client.get_transaction(&prev_txid)? {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slowli
Is there any verification that tx is the only valid output of prev_tx, used for anchoring? (if tx has 2+ outputs, it can still support anchoring different forks).

Also, i don't see, how the whole lect chain may unwound by check_anchoring_lect: starting from trusted FundingTx.

@alekseysidorov please, clarify.

let anchoring_schema = AnchoringSchema::new(state.view());
// Check that tx address is correct
let tx_addr = tx.output_address(cfg.network);
let addr = if let Some(following) = anchoring_schema.following_anchoring_config()? {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alekseysidorov is this is the place to replace with anchoring_schema.following_anchoring_config_at_height(state.height())
?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

}
TxKind::Other(tx) => {
let e = HandlerError::IncorrectLect {
reason: "Weird input transaction".to_string(),
Copy link

@dj8yfo dj8yfo Apr 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to a more descriptive message, please, like Transaction format not corresponding to expected: TxKind::Anchoring or TxKind::FundingTx


if let Some((lect, count)) = lects.iter().max_by_key(|&(_, v)| v) {
if *count >= ::majority_count(validators_count as u8) {
match TxKind::from(lect.clone()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a helper function for matching -> Result<LectKing, Error> ?
there's one more occurence

src/service.rs Outdated
error!("An error occured: {}", e);
handler.errors.push(e);
Ok(())
}
Err(e) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alekseysidorov @slowli propose doing panic for all of these, when 'cfg(not(feature="sandbox_tests")'.
Looking at all occurencies of

pub enum Error {
+    IncorrectLect { reason: String, tx: BitcoinTx },

all of them are unrecoverable error in anchoring verification. It would be harder to spot such during benchmark if it only goes to log (they shouldn't occur during normal operation, only in some byzantine/fork/[validators' collusion] scenarios).
Also it will allow to spot verification logic errors with respect to independent bitcoind instances for each of exonum nodes, which may see bitcoin ledger differently at the same point of time. After corresponding benchmark (separate [validator+bitcoind], separate [fullnode+bitcoind]), additional adjustments for that inconsistency may have to be made.
Not so sure about `LectNotFound' though.

Copy link

@dj8yfo dj8yfo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

panics around IncorrectLect, and, very likely, LectNotFound

@alekseysidorov alekseysidorov changed the title WIP: Add FullNode support Add FullNode support May 3, 2017
Copy link
Contributor

@slowli slowli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surface-level review as of 5c5039c. Mostly nitpicks; sorry for that. I'll try to review the code one more time soon, focusing on logic.

@@ -651,14 +651,52 @@ fn test_anchoring_lect_incorrect_anchoring_payload() {
assert_eq!(lects_before, lects_after);
}

// We received correct lect with the unknown prev_hash
// problems: None
// result: we ignores it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: "ignores" -> "ignore".

@@ -64,6 +65,12 @@ impl AnchoringSandboxState {
self.handler.lock().unwrap()
}

pub fn take_errors(&self) -> Vec<HandlerError> {
let mut handler = self.handler();
let v = handler.errors.drain(..).collect::<Vec<_>>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be possible to just return handler.errors... without declaring v?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I want to take values instead of clonning.

use anchoring_btc_sandbox::{AnchoringSandboxState, initialize_anchoring_sandbox};
use anchoring_btc_sandbox::helpers::*;

fn gen_following_cfg(sandbox: &Sandbox,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment with a description of how the following config differs from the previous one.

client.expect(vec![request! {
method: "getrawtransaction",
params: [&anchored_tx.txid(), 1],
response: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to collapse 3 instances of this transaction in this file into one?

}]);
add_one_height_with_transactions(&sandbox, &sandbox_state, &[cfg_tx]);

// Check enough confirmations case
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, split this into two tests? Or is the previous block not supposed to check anything? 🤔 In the latter case, the comment should be reworded in order not to give a wrong impression.

id));

if prev_lect.id() != prev_txid {
panic!("Inconsistency in blockchain found!");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be more specific here, like "Inconsistent reference to previous lect in Exonum and Bitcoin" (if I understand this check correctly), maybe with further details.

}
if tx.find_out(&addr).is_some() {
let e = HandlerError::IncorrectLect {
reason: "Initial funding_tx have wrong output address".to_string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: "have" -> "has".

pub fn client(&self) -> &AnchoringRpc {
self.client
.as_ref()
.expect("Client need to be exists for validator")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I'd say "Bitcoin client needs to be present for validator node".

Ok(AnchoringState::Auditing { cfg: actual })
} else {
let state = if let Some(cfg) = self.following_config(state)? {
if actual.redeem_script().1 != cfg.config.redeem_script().1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: cfg.config is hurting my brain. Maybe, rename cfg -> following?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replace it in #38.

}
}

fn cause(&self) -> Option<&::std::error::Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't ::std:: qualifier unnecessary here? You used error::Error previously.

Copy link
Contributor

@slowli slowli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mergeable as of bea916d.

@@ -124,8 +130,7 @@ impl<'a> AnchoringSchema<'a> {
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method pub fn lect(&self, validator: u32) -> Result<BitcoinTx, StorageError> contains an implicit unwrap. Does the assumption, that corresponding lects table contains at least one element, always holds true when the method is called? It has many usages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we assume that at least initial funding transaction exist in lect table. This tx is root of anchoring chain.

impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::IncorrectLect { .. } => "Incorrect lect",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format!("Incorrect lect: {}, tx={:#?}", reason, tx) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And are how you going to return reference to local owned value.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, ok.

if let HandlerError::IncorrectLect { .. } = e {
panic!("A critical error occured: {}", e);
}
error!("An error occured: {}", e);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alekseysidorov Maybe we should panic, if HandlerError::LectNotFound didn't occur for too long?

By collecting the count of failed attempts in NodeState. If it were problems with bitcoind, fix them and then recover after node restart (count equal to 0 again). Just an idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to looking like crutch. We needs to investigate a normal events mechanism to inform the node admin about errors.

Ok(())
}

fn check_anchoring_lect(&self, tx: AnchoringTx) -> Result<(), ServiceError> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check doesn't try to compare against tx.payload().height, which may have occured too long ago (cfg.latest_anchoring_height() - 5000 ). I assume, with this code anchoring audit will pass for fullnode, if there's at least one lect existing in bitcoin blockchain which is last for majority of validators.

@alekseysidorov are you handling this in another pr?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be handled in another PR.

If I understand correctly, atm check_funding_lect and check_anchoring_lect only verify that the supplied transaction is present in the Bitcoin network (if I interpret get_transaction correctly, the transaction does not even need to be confirmed); this may be too lax. The validators may just stall the anchoring process by not coming up with new lects, which can be discovered with the check you describe. Maybe, the number of confirmations on the Bitcoin blockchain should be checked as well, although I cannot yet come up with a stalling/malicious strategy that would be foiled by this check.

Another interesting question is: can the present check trigger a false alarm, e.g., if an anchoring transaction had been orphaned due to blockchain reorg? That would be a more pressing issue, imo.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slowli. Would it be enough to wait for 3 confirmations? To cover some 99,999% of situations, when bitcoin forks/reorganizes. For the remaining 0,0001% it still won't help.

I assume, the possibility of fork/reorganization has to be handled anyway, as it would be the same problem for bitcoin relay approach. @VukW mentioned something similar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gisochre I'm sorry; the problematic scenario I had in mind does not actually involve a blockchain reorg. It goes like this:

  • Validators come up with a lect and send it to the Bitcoin network
  • The transaction in the lect is somehow mutated (say, by a malicious validator)
  • The mutated transaction is eventually mined
  • Thus, the original lect transaction is orphaned
  • Some time later (say, 1 year) an auditor starts syncing with the Exonum blockchain and encounters the collected lect that is not present in the Bitcoin blockchain (and which has never appeared in any Bitcoin block)

It seems that the auditor will panic in this case because get_transaction RPC will return None; bitcoind obviously does not persist double-spent txs that have never been in a block.

Now, a similar situation could occur during a reorg, when an anchoring transaction becomes unconfirmed. If the tx was double-spent in this case, it could potentially lead to the same problem. I'm not sure, but it seems logical that bitcoind would not download orphaned blocks from far past, so get_transaction might return None for the collected lect for a syncing node (interestingly: would it be deterministic, or depend on how far in the past the reorg happened?). But, as far as I understand, the validators would not actually abandon/double-spend the newly unconfirmed tx after the reorg; they would rather continue building new anchoring txs on top of it. Thus, the reorg scenario is not really a problem.

Maybe, I'm missing something completely? For instance, I don't quite get what you mean by waiting for 3 confirms: 3 confirms between adjacent anchorings?

Copy link

@dj8yfo dj8yfo May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transaction in the lect is somehow mutated (say, by a malicious validator)

How? signature malleability has been fixed, i believe.

Thus, the reorg scenario is not really a problem.

then no need in 3 confirmations i guess. 3 confirmations before a validator updates his last lect (it may have been very hard/impossible to fit into current implementation).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How? signature malleability has been fixed, i believe.

The signature malleability by the signature author cannot be fixed by anything short of SegWit; it's always possible for the author to create another valid signature over the same message (only in SegWit it would not invalidate the following anchoring txs).

Additionally, there are marginal cases, in which tx signatures may be malleated by third parties.

@alekseysidorov alekseysidorov merged commit 4f558ca into exonum:master May 12, 2017
@alekseysidorov alekseysidorov deleted the 20-fullnode-support branch June 1, 2017 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants