From c2f09432ef2f31ae10a7b9d20f5ad8199ce52d8b Mon Sep 17 00:00:00 2001 From: Boris Oncev Date: Thu, 28 Mar 2024 17:27:17 +0100 Subject: [PATCH 1/2] fix tracking of multisig/script addresses in api server --- .../scanner-lib/src/blockchain_state/mod.rs | 288 ++++++++---------- api-server/scanner-lib/src/sync/tests.rs | 135 +++++++- 2 files changed, 255 insertions(+), 168 deletions(-) diff --git a/api-server/scanner-lib/src/blockchain_state/mod.rs b/api-server/scanner-lib/src/blockchain_state/mod.rs index 409cd133a7..02b51ff399 100644 --- a/api-server/scanner-lib/src/blockchain_state/mod.rs +++ b/api-server/scanner-lib/src/blockchain_state/mod.rs @@ -421,59 +421,43 @@ async fn update_tables_from_block_reward( .await; } TxOutput::Transfer(output_value, destination) - | TxOutput::LockThenTransfer(output_value, destination, _) => match destination { - Destination::PublicKey(_) | Destination::PublicKeyHash(_) => { - let address = Address::::new(&chain_config, destination.clone()) - .expect("Unable to encode destination"); - match output_value { - OutputValue::TokenV0(_) => {} - OutputValue::TokenV1(token_id, amount) => { - increase_address_amount( - db_tx, - &address, - amount, - CoinOrTokenId::TokenId(*token_id), - block_height, - ) - .await; - } - OutputValue::Coin(amount) => { - increase_address_amount( - db_tx, - &address, - amount, - CoinOrTokenId::Coin, - block_height, - ) - .await; - } + | TxOutput::LockThenTransfer(output_value, destination, _) => { + let address = Address::::new(&chain_config, destination.clone()) + .expect("Unable to encode destination"); + match output_value { + OutputValue::TokenV0(_) => {} + OutputValue::TokenV1(token_id, amount) => { + increase_address_amount( + db_tx, + &address, + amount, + CoinOrTokenId::TokenId(*token_id), + block_height, + ) + .await; + } + OutputValue::Coin(amount) => { + increase_address_amount( + db_tx, + &address, + amount, + CoinOrTokenId::Coin, + block_height, + ) + .await; } - set_utxo( - OutPointSourceId::BlockReward(block_id), - idx, - output, - db_tx, - block_height, - false, - &chain_config, - ) - .await; - } - Destination::AnyoneCanSpend => { - // for tests - set_utxo( - OutPointSourceId::BlockReward(block_id), - idx, - output, - db_tx, - block_height, - false, - &chain_config, - ) - .await; } - Destination::ClassicMultisig(_) | Destination::ScriptHash(_) => {} - }, + set_utxo( + OutPointSourceId::BlockReward(block_id), + idx, + output, + db_tx, + block_height, + false, + &chain_config, + ) + .await; + } } } @@ -969,70 +953,58 @@ async fn update_tables_from_transaction_inputs( | TxOutput::IssueFungibleToken(_) | TxOutput::CreateStakePool(_, _) | TxOutput::ProduceBlockFromStake(_, _) => {} - TxOutput::IssueNft(token_id, _, destination) => match destination { - Destination::AnyoneCanSpend - | Destination::ClassicMultisig(_) - | Destination::ScriptHash(_) => {} - Destination::PublicKey(_) | Destination::PublicKeyHash(_) => { - let address = - Address::::new(&chain_config, destination) - .expect("Unable to encode destination"); - - address_transactions - .entry(address.clone()) - .or_default() - .insert(tx.get_id()); - - decrease_address_amount( - db_tx, - address, - &Amount::from_atoms(1), - CoinOrTokenId::TokenId(token_id), - block_height, - ) - .await; - } - }, + TxOutput::IssueNft(token_id, _, destination) => { + let address = Address::::new(&chain_config, destination) + .expect("Unable to encode destination"); + + address_transactions + .entry(address.clone()) + .or_default() + .insert(tx.get_id()); + + decrease_address_amount( + db_tx, + address, + &Amount::from_atoms(1), + CoinOrTokenId::TokenId(token_id), + block_height, + ) + .await; + } TxOutput::LockThenTransfer(output_value, destination, _) - | TxOutput::Transfer(output_value, destination) => match destination { - Destination::AnyoneCanSpend - | Destination::ClassicMultisig(_) - | Destination::ScriptHash(_) => {} - Destination::PublicKey(_) | Destination::PublicKeyHash(_) => { - let address = - Address::::new(&chain_config, destination) - .expect("Unable to encode destination"); - - address_transactions - .entry(address.clone()) - .or_default() - .insert(tx.get_id()); - - match output_value { - OutputValue::TokenV0(_) => {} - OutputValue::TokenV1(token_id, amount) => { - decrease_address_amount( - db_tx, - address, - &amount, - CoinOrTokenId::TokenId(token_id), - block_height, - ) - .await; - } - OutputValue::Coin(amount) => { - decrease_address_amount( - db_tx, - address, - &amount, - CoinOrTokenId::Coin, - block_height, - ) - .await; - } + | TxOutput::Transfer(output_value, destination) => { + let address = Address::::new(&chain_config, destination) + .expect("Unable to encode destination"); + + address_transactions + .entry(address.clone()) + .or_default() + .insert(tx.get_id()); + + match output_value { + OutputValue::TokenV0(_) => {} + OutputValue::TokenV1(token_id, amount) => { + decrease_address_amount( + db_tx, + address, + &amount, + CoinOrTokenId::TokenId(token_id), + block_height, + ) + .await; + } + OutputValue::Coin(amount) => { + decrease_address_amount( + db_tx, + address, + &amount, + CoinOrTokenId::Coin, + block_height, + ) + .await; } } - }, + } } } }, @@ -1173,64 +1145,46 @@ async fn update_tables_from_transaction_outputs( .expect("Unable to encode address"); address_transactions.entry(address.clone()).or_default().insert(transaction_id); } - TxOutput::Transfer(output_value, destination) => match destination { - Destination::PublicKey(_) | Destination::PublicKeyHash(_) => { - let address = Address::::new(&chain_config, destination.clone()) - .expect("Unable to encode destination"); + TxOutput::Transfer(output_value, destination) => { + let address = Address::::new(&chain_config, destination.clone()) + .expect("Unable to encode destination"); - address_transactions.entry(address.clone()).or_default().insert(transaction_id); + address_transactions.entry(address.clone()).or_default().insert(transaction_id); - let token_decimals = match output_value { - OutputValue::TokenV0(_) => None, - OutputValue::TokenV1(token_id, amount) => { - increase_address_amount( - db_tx, - &address, - amount, - CoinOrTokenId::TokenId(*token_id), - block_height, - ) - .await; - Some(token_decimals(*token_id, &BTreeMap::new(), db_tx).await?.1) - } - OutputValue::Coin(amount) => { - increase_address_amount( - db_tx, - &address, - amount, - CoinOrTokenId::Coin, - block_height, - ) - .await; - None - } - }; + let token_decimals = match output_value { + OutputValue::TokenV0(_) => None, + OutputValue::TokenV1(token_id, amount) => { + increase_address_amount( + db_tx, + &address, + amount, + CoinOrTokenId::TokenId(*token_id), + block_height, + ) + .await; + Some(token_decimals(*token_id, &BTreeMap::new(), db_tx).await?.1) + } + OutputValue::Coin(amount) => { + increase_address_amount( + db_tx, + &address, + amount, + CoinOrTokenId::Coin, + block_height, + ) + .await; + None + } + }; - let outpoint = UtxoOutPoint::new( - OutPointSourceId::Transaction(transaction_id), - idx as u32, - ); - let utxo = Utxo::new(output.clone(), token_decimals, false); - db_tx - .set_utxo_at_height(outpoint, utxo, address.as_str(), block_height) - .await - .expect("Unable to set utxo"); - } - Destination::AnyoneCanSpend => { - // for tests - set_utxo( - OutPointSourceId::Transaction(transaction_id), - idx, - output, - db_tx, - block_height, - false, - &chain_config, - ) - .await; - } - Destination::ClassicMultisig(_) | Destination::ScriptHash(_) => {} - }, + let outpoint = + UtxoOutPoint::new(OutPointSourceId::Transaction(transaction_id), idx as u32); + let utxo = Utxo::new(output.clone(), token_decimals, false); + db_tx + .set_utxo_at_height(outpoint, utxo, address.as_str(), block_height) + .await + .expect("Unable to set utxo"); + } TxOutput::LockThenTransfer(output_value, destination, lock) => { let address = Address::::new(&chain_config, destination.clone()) .expect("Unable to encode destination"); diff --git a/api-server/scanner-lib/src/sync/tests.rs b/api-server/scanner-lib/src/sync/tests.rs index fb76913c6f..e38fb1a72b 100644 --- a/api-server/scanner-lib/src/sync/tests.rs +++ b/api-server/scanner-lib/src/sync/tests.rs @@ -403,6 +403,7 @@ async fn compare_pool_rewards_with_chainstate_real_state(#[case] seed: Seed) { let remaining_coins = remaining_coins - rng.gen_range(0..10); eprintln!("coins: {remaining_coins}"); + let (_, deleg_pk) = PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); let transaction = TransactionBuilder::new() .add_input( TxInput::from_utxo(OutPointSourceId::Transaction(prev_tx_id), 0), @@ -413,7 +414,7 @@ async fn compare_pool_rewards_with_chainstate_real_state(#[case] seed: Seed) { Destination::AnyoneCanSpend, )) .add_output(TxOutput::CreateDelegationId( - Destination::AnyoneCanSpend, + Destination::PublicKeyHash((&deleg_pk).into()), pool_id, )) .build(); @@ -1074,3 +1075,135 @@ async fn sync_and_compare( assert_eq!(balance, Amount::ZERO); } } + +#[rstest] +#[trace] +#[case(test_utils::random::Seed::from_entropy())] +#[tokio::test] +async fn check_all_destinations_are_trucked(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + + let mut tf = TestFramework::builder(&mut rng).build(); + + let chain_config = Arc::clone(tf.chainstate.get_chain_config()); + let storage = { + let mut storage = TransactionalApiServerInMemoryStorage::new(&chain_config); + + let mut db_tx = storage.transaction_rw().await.unwrap(); + db_tx.reinitialize_storage(&chain_config).await.unwrap(); + db_tx.commit().await.unwrap(); + + storage + }; + let mut local_state = BlockchainState::new(chain_config.clone(), storage); + local_state.scan_genesis(chain_config.genesis_block().as_ref()).await.unwrap(); + + let target_block_time = chain_config.target_block_spacing(); + + let (_priv_key, pub_key) = PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + + let public_key_dest = Destination::PublicKey(pub_key.clone()); + let public_key_hash_dest = Destination::PublicKeyHash((&pub_key).into()); + let classic_multisig_dest = Destination::ClassicMultisig((&pub_key).into()); + let script_dest = Destination::ScriptHash(Id::new(H256::from_slice(&rng.gen::<[u8; 32]>()))); + + let with_public_key = TxOutput::Transfer( + OutputValue::Coin(Amount::from_atoms(1)), + public_key_dest.clone(), + ); + let with_public_key_hash = TxOutput::Transfer( + OutputValue::Coin(Amount::from_atoms(1)), + public_key_hash_dest.clone(), + ); + let with_multisig = TxOutput::Transfer( + OutputValue::Coin(Amount::from_atoms(1)), + classic_multisig_dest.clone(), + ); + let with_script = TxOutput::Transfer( + OutputValue::Coin(Amount::from_atoms(1)), + script_dest.clone(), + ); + + let locked_with_public_key = TxOutput::LockThenTransfer( + OutputValue::Coin(Amount::from_atoms(1)), + public_key_dest.clone(), + OutputTimeLock::ForBlockCount(1), + ); + let locked_with_public_key_hash = TxOutput::LockThenTransfer( + OutputValue::Coin(Amount::from_atoms(1)), + public_key_hash_dest.clone(), + OutputTimeLock::ForBlockCount(1), + ); + let locked_with_multisig = TxOutput::LockThenTransfer( + OutputValue::Coin(Amount::from_atoms(1)), + classic_multisig_dest.clone(), + OutputTimeLock::ForBlockCount(1), + ); + let locked_with_script = TxOutput::LockThenTransfer( + OutputValue::Coin(Amount::from_atoms(1)), + script_dest.clone(), + OutputTimeLock::ForBlockCount(1), + ); + + let transaction = TransactionBuilder::new() + .add_input( + TxInput::from_utxo( + OutPointSourceId::BlockReward(chain_config.genesis_block_id()), + 0, + ), + InputWitness::NoSignature(None), + ) + // Add all different destinations + .add_output(with_script.clone()) + .add_output(with_multisig.clone()) + .add_output(with_public_key.clone()) + .add_output(with_public_key_hash.clone()) + // Add all different destinations while locked + .add_output(locked_with_script.clone()) + .add_output(locked_with_multisig.clone()) + .add_output(locked_with_public_key.clone()) + .add_output(locked_with_public_key_hash.clone()) + .build(); + + let prev_block_hash = chain_config.genesis_block_id(); + + tf.progress_time_seconds_since_epoch(target_block_time.as_secs()); + let block = tf + .make_block_builder() + .with_parent(prev_block_hash) + .with_transactions(vec![transaction]) + .build(); + + tf.process_block(block.clone(), BlockSource::Local).unwrap(); + let block_height = local_state + .storage() + .transaction_ro() + .await + .unwrap() + .get_best_block() + .await + .unwrap() + .block_height(); + local_state.scan_blocks(block_height, vec![block]).await.unwrap(); + + // Check all the utxos have been added in both locked and unlocked and balance has been updated + let db_tx = local_state.storage().transaction_ro().await.unwrap(); + for dest in [script_dest, classic_multisig_dest, public_key_dest, public_key_hash_dest] { + let address = Address::new(&chain_config, dest.clone()).unwrap(); + let amount = + db_tx.get_address_balance(address.as_str(), CoinOrTokenId::Coin).await.unwrap(); + + assert_eq!(amount, Some(Amount::from_atoms(1))); + + let locked_amount = db_tx + .get_address_locked_balance(address.as_str(), CoinOrTokenId::Coin) + .await + .unwrap(); + + assert_eq!(locked_amount, Some(Amount::from_atoms(1))); + + let utxos = db_tx.get_address_all_utxos(address.as_str()).await.unwrap(); + // check we have 2 utxos one locked and one unlocked + assert_eq!(utxos.len(), 2); + } +} From 9b9c5e520fb0e546545152c54959c605c7cfa10a Mon Sep 17 00:00:00 2001 From: Boris Oncev Date: Thu, 28 Mar 2024 17:31:19 +0100 Subject: [PATCH 2/2] bump db version to force a rescan --- api-server/api-server-common/src/storage/impls/mod.rs | 2 +- api-server/scanner-lib/src/sync/tests.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api-server/api-server-common/src/storage/impls/mod.rs b/api-server/api-server-common/src/storage/impls/mod.rs index 373f230d06..24438065d2 100644 --- a/api-server/api-server-common/src/storage/impls/mod.rs +++ b/api-server/api-server-common/src/storage/impls/mod.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub const CURRENT_STORAGE_VERSION: u32 = 8; +pub const CURRENT_STORAGE_VERSION: u32 = 10; pub mod in_memory; pub mod postgres; diff --git a/api-server/scanner-lib/src/sync/tests.rs b/api-server/scanner-lib/src/sync/tests.rs index e38fb1a72b..da834a7297 100644 --- a/api-server/scanner-lib/src/sync/tests.rs +++ b/api-server/scanner-lib/src/sync/tests.rs @@ -1080,7 +1080,7 @@ async fn sync_and_compare( #[trace] #[case(test_utils::random::Seed::from_entropy())] #[tokio::test] -async fn check_all_destinations_are_trucked(#[case] seed: Seed) { +async fn check_all_destinations_are_tracked(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); let mut tf = TestFramework::builder(&mut rng).build();