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

feat(v2-batch-submissions): swallow BlockAlreadyInDbError during batch submissions #22

Merged
merged 12 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion v2_bridges/btc_on_int/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
license = "MIT"
publish = false
edition = "2021"
version = "1.1.0"
version = "1.2.0"
name = "btc_on_int"
readme = "README.md"
rust-version = "1.56"
Expand Down
118 changes: 113 additions & 5 deletions v2_bridges/btc_on_int/src/int/submit_int_block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::str::FromStr;

use common::{core_type::CoreType, traits::DatabaseInterface, types::Result};
use common::{core_type::CoreType, traits::DatabaseInterface, types::Result, AppError};
use common_eth::{
check_for_parent_of_eth_block_in_state,
maybe_add_eth_block_and_receipts_to_db_and_return_state,
Expand Down Expand Up @@ -75,10 +75,24 @@ pub fn submit_int_blocks_to_core<D: DatabaseInterface>(db: &D, blocks: &str) ->
.and_then(|_| db.start_transaction())
.and_then(|_| EthSubmissionMaterialJsons::from_str(blocks))
.and_then(|jsons| {
jsons
.iter()
.map(|block| submit_int_block(db, block))
.collect::<Result<Vec<_>>>()
let mut outputs = vec![];

for json in jsons.iter() {
match submit_int_block(db, json) {
Ok(o) => {
outputs.push(o);
continue;
},
Err(AppError::BlockAlreadyInDbError(e)) => {
warn!("block already in db error: {e}");
info!("moving on to next block in batch!");
continue;
},
Err(e) => return Err(e),
}
}

Ok(outputs)
})
.map(IntOutputs::new)
.and_then(|outputs| {
Expand Down Expand Up @@ -251,4 +265,98 @@ mod tests {
// NOTE: Check the tx is decodable...
assert!(convert_hex_tx_to_btc_transaction(tx_info.btc_signed_tx).is_ok());
}

#[test]
fn should_batch_submit_int_blocks_successfully_even_if_one_already_in_db() {
// Init the BTC side...
let btc_pk = "93GJ65qHNjGFHzQVTzEEAdBS7vMxe3XASfWE8RUASSfd3EtfmzP";
let db = get_test_database();
let btc_db_utils = BtcDbUtils::new(&db);
let btc_state = BtcState::init(&db);
let btc_fee = 15;
let btc_difficulty = 1;
let btc_network = "Testnet";
let btc_canon_to_tip_length = 2;
let btc_block_0 = get_sample_btc_submission_material_json_str_n(0);
init_btc_core(
btc_state,
&btc_block_0,
btc_fee,
btc_difficulty,
btc_network,
btc_canon_to_tip_length,
)
.unwrap();

// NOTE: Overwrite the BTC private key fields since they're randomly generated upon init.
let btc_pk = BtcPrivateKey::from_wif(btc_pk).unwrap();
let address = btc_pk.to_p2pkh_btc_address();
btc_db_utils.put_btc_private_key_in_db(&btc_pk).unwrap();
btc_db_utils.put_btc_address_in_db(&address).unwrap();
btc_db_utils
.put_btc_pub_key_slice_in_db(&btc_pk.to_public_key_slice())
.unwrap();

// Init the ETH side...
let eth_block_0 = get_sample_int_submission_material_json_str_n(0);
let eth_state = EthState::init(&db);
let eth_chain_id = 3;
let eth_gas_price = 20_000_000_000;
let eth_canon_to_tip_length = 2;
let ptoken_address_hex = "0x0f513aA8d67820787A8FDf285Bfcf967bF8E4B8b";
let ptoken_address = convert_hex_to_eth_address(ptoken_address_hex).unwrap();
let router_address_hex = "0x88d19e08cd43bba5761c10c588b2a3d85c75041f";
let router_address = convert_hex_to_eth_address(router_address_hex).unwrap();
init_int_core(
eth_state,
&eth_block_0,
eth_chain_id,
eth_gas_price,
eth_canon_to_tip_length,
&ptoken_address,
&router_address,
)
.unwrap();

// NOTE: Overwrite the ETH private key fields since they're randomly generated upon init.
let eth_db_utils = EthDbUtils::new(&db);
let eth_pk_bytes = hex::decode("262e2a3a7fa5ae40ea04584f20b51fc3918b42e7dd89926b9f4e2196c8a032ba").unwrap();
let eth_pk = EthPrivateKey::from_slice(&eth_pk_bytes).unwrap();
eth_db_utils.put_eth_private_key_in_db(&eth_pk).unwrap();
eth_db_utils
.put_public_eth_address_in_db(&eth_pk.to_public_key().to_address())
.unwrap();

// NOTE First we submit enough BTC blocks to have a UTXO to spend...
let btc_block_1 = get_sample_btc_submission_material_json_str_n(1);
submit_btc_block_to_core(&db, &btc_block_1).unwrap();
let btc_block_2 = get_sample_btc_submission_material_json_str_n(2);
submit_btc_block_to_core(&db, &btc_block_2).unwrap();
let btc_block_3 = get_sample_btc_submission_material_json_str_n(3);
submit_btc_block_to_core(&db, &btc_block_3).unwrap();
let utxo_nonce = get_utxo_nonce_from_db(&db).unwrap();
assert_eq!(utxo_nonce, 1);

// NOTE: Submit first block, this one has a peg in in it.
let int_block_1 = get_sample_int_submission_material_json_str_n(1);

let submission_string = int_block_1.clone();
let block_num = 12000342;

// NOTE: This totally normal submission should succeed
submit_int_block_to_core(&db, &submission_string).unwrap();

// NOTE: However it will fail a second time due to the block already being in the db...
match submit_int_block_to_core(&db, &submission_string) {
Ok(_) => panic!("should not have succeeded!"),
Err(AppError::BlockAlreadyInDbError(e)) => assert_eq!(e.block_num, block_num),
Err(e) => panic!("wrong error received: {e}"),
};

// NOTE: However if the same block forms part of a _batch_ of blocks, the
// `BlockAlreadyInDbError` should be swallowed, and thus no errors.
let batch = format!("[{submission_string},{submission_string},{submission_string}]");
let result = submit_int_blocks_to_core(&db, &batch);
assert!(result.is_ok());
}
}
2 changes: 1 addition & 1 deletion v2_bridges/eos_on_int/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
license = "MIT"
publish = false
edition = "2021"
version = "2.0.0"
version = "2.1.0"
name = "eos_on_int"
readme = "README.md"
rust-version = "1.56"
Expand Down
102 changes: 97 additions & 5 deletions v2_bridges/eos_on_int/src/int/submit_int_block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::str::FromStr;

use common::{core_type::CoreType, traits::DatabaseInterface, types::Result};
use common::{core_type::CoreType, traits::DatabaseInterface, types::Result, AppError};
use common_eth::{
check_for_parent_of_eth_block_in_state,
maybe_add_eth_block_and_receipts_to_db_and_return_state,
Expand Down Expand Up @@ -83,10 +83,24 @@ pub fn submit_int_blocks_to_core<D: DatabaseInterface>(db: &D, blocks: &str) ->
.and_then(|_| db.start_transaction())
.and_then(|_| EthSubmissionMaterialJsons::from_str(blocks))
.and_then(|jsons| {
jsons
.iter()
.map(|block| submit_int_block(db, block))
.collect::<Result<Vec<_>>>()
let mut outputs = vec![];

for json in jsons.iter() {
match submit_int_block(db, json) {
Ok(o) => {
outputs.push(o);
continue;
},
Err(AppError::BlockAlreadyInDbError(e)) => {
warn!("block already in db error: {e}");
info!("moving on to next block in batch!");
continue;
},
Err(e) => return Err(e),
}
}

Ok(outputs)
})
.map(IntOutputs::new)
.and_then(|outputs| {
Expand Down Expand Up @@ -218,4 +232,82 @@ mod tests {
// NOTE: The first four bytes/8 hex chars are an encoded timestamp,
assert_eq!(result.eos_serialized_tx[8..], expected_result.eos_serialized_tx[8..]);
}

#[test]
fn should_batch_submit_int_blocks_successfully_even_if_one_already_in_db() {
let db = get_test_database();
let router_address = get_sample_router_address();

// NOTE: Initialize the EOS core...
let eos_chain_id = "4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11";
let maybe_eos_account_name = Some("intoneostest");
let maybe_eos_token_symbol = None;
let eos_init_block = get_sample_eos_init_block();
let is_native = true;
initialize_eos_core_inner(
&db,
eos_chain_id,
maybe_eos_account_name,
maybe_eos_token_symbol,
&eos_init_block,
is_native,
)
.unwrap();

// NOTE: Overwrite the EOS private key since it's generated randomly above...
let eos_pk = get_sample_eos_private_key();
eos_pk.write_to_db(&db).unwrap();
assert_eq!(EosPrivateKey::get_from_db(&db).unwrap(), eos_pk);

// NOTE: Initialize the INT side of the core...
let int_confirmations = 0;
let int_gas_price = 20_000_000_000;
let contiguous_int_block_json_strs = get_contiguous_int_block_json_strs();
let int_init_block = contiguous_int_block_json_strs[0].clone();
let is_native = false;
initialize_eth_core_with_router_contract_and_return_state(
&int_init_block,
&EthChainId::Ropsten,
int_gas_price,
int_confirmations,
IntState::init(&db),
&router_address,
is_native,
)
.unwrap();

// NOTE: Overwrite the INT address & private key since it's generated randomly above...
let int_address = get_sample_int_address();
let int_private_key = get_sample_int_private_key();
let int_db_utils = EthDbUtils::new(&db);
int_db_utils
.put_eth_address_in_db(&int_db_utils.get_eth_address_key(), &int_address)
.unwrap();
int_db_utils.put_eth_private_key_in_db(&int_private_key).unwrap();
assert_eq!(int_db_utils.get_public_eth_address_from_db().unwrap(), int_address);
assert_eq!(int_db_utils.get_eth_private_key_from_db().unwrap(), int_private_key);

// NOTE: Add the token dictionary to the db...
let dictionary = get_sample_dictionary();
dictionary.save_to_db(&db).unwrap();

let submission_string = contiguous_int_block_json_strs[1].clone();
let block_num = 11618227;

// NOTE: This totally normal submission should succeed
submit_int_block_to_core(&db, &submission_string).unwrap();

// NOTE: However it will fail a second time due to the block already being in the db...
match submit_int_block_to_core(&db, &submission_string) {
Ok(_) => panic!("should not have succeeded!"),
Err(AppError::BlockAlreadyInDbError(e)) => assert_eq!(e.block_num, block_num),
Err(e) => panic!("wrong error received: {e}"),
};

// NOTE: However if the same block forms part of a _batch_ of blocks, the
// `BlockAlreadyInDbError` should be swallowed, and thus no errors.
let batch = format!("[{submission_string},{submission_string},{submission_string}]");
let result = submit_int_blocks_to_core(&db, &batch);
assert!(result.is_ok());
}
}
Loading
Loading