-
Notifications
You must be signed in to change notification settings - Fork 23
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
Add FullNode
support
#21
Conversation
src/handler/auditing.rs
Outdated
// Verify tx content | ||
self.check_anchoring_tx_content(&tx, cfg, state)?; | ||
// Checks with access to the `bitcoind` | ||
if let Some(ref client) = self.client { |
There was a problem hiding this comment.
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?.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/handler/auditing.rs
Outdated
|
||
// Get previous tx | ||
let prev_txid = tx.prev_hash().be_hex_string(); | ||
let prev_tx = if let Some(tx) = client.get_transaction(&prev_txid)? { |
There was a problem hiding this comment.
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.
src/handler/auditing.rs
Outdated
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()? { |
There was a problem hiding this comment.
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())
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
src/handler/auditing.rs
Outdated
} | ||
TxKind::Other(tx) => { | ||
let e = HandlerError::IncorrectLect { | ||
reason: "Weird input transaction".to_string(), |
There was a problem hiding this comment.
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
src/handler/auditing.rs
Outdated
|
||
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()) { |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
There was a problem hiding this 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.
sandbox_tests/tests/anchoring.rs
Outdated
@@ -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 |
There was a problem hiding this comment.
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<_>>(); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
sandbox_tests/tests/auditing.rs
Outdated
use anchoring_btc_sandbox::{AnchoringSandboxState, initialize_anchoring_sandbox}; | ||
use anchoring_btc_sandbox::helpers::*; | ||
|
||
fn gen_following_cfg(sandbox: &Sandbox, |
There was a problem hiding this comment.
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.
sandbox_tests/tests/auditing.rs
Outdated
client.expect(vec![request! { | ||
method: "getrawtransaction", | ||
params: [&anchored_tx.txid(), 1], | ||
response: { |
There was a problem hiding this comment.
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?
sandbox_tests/tests/auditing.rs
Outdated
}]); | ||
add_one_height_with_transactions(&sandbox, &sandbox_state, &[cfg_tx]); | ||
|
||
// Check enough confirmations case |
There was a problem hiding this comment.
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.
src/blockchain/transactions.rs
Outdated
id)); | ||
|
||
if prev_lect.id() != prev_txid { | ||
panic!("Inconsistency in blockchain found!"); |
There was a problem hiding this comment.
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.
src/handler/auditing.rs
Outdated
} | ||
if tx.find_out(&addr).is_some() { | ||
let e = HandlerError::IncorrectLect { | ||
reason: "Initial funding_tx have wrong output address".to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: "have" -> "has".
src/handler/basic.rs
Outdated
pub fn client(&self) -> &AnchoringRpc { | ||
self.client | ||
.as_ref() | ||
.expect("Client need to be exists for validator") |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
src/handler/error.rs
Outdated
} | ||
} | ||
|
||
fn cause(&self) -> Option<&::std::error::Error> { |
There was a problem hiding this comment.
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.
There was a problem hiding this 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> { | |||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
Closes #20