From d1cadd19fc0891725407a598df7a6106626217dc Mon Sep 17 00:00:00 2001 From: scx1332 Date: Thu, 14 Mar 2024 21:22:34 +0100 Subject: [PATCH] Proper deposit closing method implemented. --- .github/workflows/ci.yml | 80 ------------ .github/workflows/deposit.yml | 2 +- .github/workflows/deposit2.yml | 120 ++++++++++++++++++ crates/erc20_payment_lib/config-payments.toml | 6 +- crates/erc20_payment_lib/src/contracts.rs | 23 ++++ crates/erc20_payment_lib/src/runtime.rs | 93 +++++++++++--- .../erc20_payment_lib/src/sender/batching.rs | 105 ++++++++++++--- .../erc20_payment_lib/src/sender/service.rs | 34 +++-- crates/erc20_payment_lib/src/transaction.rs | 101 +++++++++++---- .../migrations/20240312000000_add_finish.sql | 48 +++++++ .../src/db/model/token_transfer_dao.rs | 3 +- .../src/db/ops/token_transfer_ops.rs | 98 +++++++++++++- src/actions/deposit/close.rs | 1 + src/main.rs | 15 ++- 14 files changed, 550 insertions(+), 179 deletions(-) create mode 100644 .github/workflows/deposit2.yml create mode 100644 crates/erc20_payment_lib_common/migrations/20240312000000_add_finish.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69c2e2f1..70b18254 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -187,86 +187,6 @@ jobs: - name: Run tests (docker_03_problems) run: cargo test --test docker_03_problems --profile=release-fast -- --test-threads=10 - test_faucet: - name: Test Goerli faucet - timeout-minutes: 20 - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - with: - shared-key: "dev-build-cache" - - - name: Build - run: cargo build - - - name: Run tests (faucet) - run: cargo run -- generate-key -n 1 > .env - - - name: Check if balance is 0 - run: | - [ $(cargo run -- balance -c goerli | jq -r '.[] | .gasDecimal') == "0" ] - [ $(cargo run -- balance -c goerli | jq -r '.[] | .tokenDecimal') == "0" ] - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Get ETH from faucet - run: cargo run -- get-dev-eth -c goerli - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Check ETH balance after getting funds from faucet (should be 0.01) - run: | - sleep 60 # give time for the blockchain to propagate info about the transaction - [ $(cargo run -- balance -c goerli | jq -r '.[] | .gasDecimal') == "0.01" ] - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Mint tokens - run: | - cargo run -- mint-test-tokens -c goerli - cargo run -- run - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Check token balance - run: | - [ $(cargo run -- balance -c goerli | jq -r '.[] | .tokenDecimal') == "1000" ] - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Transfer 166.6 GLM tokens - run: | - cargo run -- transfer -c goerli --recipient 0x5b984629E2Cc7570cBa7dD745b83c3dD23Ba6d0f --token glm --amount 166.6 - cargo run -- run - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Transfer all GLM tokens - run: | - cargo run -- transfer -c goerli --recipient 0x5b984629E2Cc7570cBa7dD745b83c3dD23Ba6d0f --token glm --all - cargo run -- run - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Check token balance zero - run: | - [ $(cargo run -- balance -c goerli | jq -r '.[] | .tokenDecimal') == "0" ] - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - - - name: Transfer all left ETH tokens - run: | - cargo run -- transfer -c goerli --recipient 0x5b984629E2Cc7570cBa7dD745b83c3dD23Ba6d0f --token eth --all - cargo run -- run - env: - GOERLI_GETH_ADDR: ${{ secrets.GOERLI_RPC_ADDRESS }} - test_faucet_holesky: name: Test Holesky faucet timeout-minutes: 20 diff --git a/.github/workflows/deposit.yml b/.github/workflows/deposit.yml index 40d5298d..55eca4c9 100644 --- a/.github/workflows/deposit.yml +++ b/.github/workflows/deposit.yml @@ -108,7 +108,7 @@ jobs: erc20_processor run erc20_processor balance - - name: Free deposit + - name: Close deposit run: | set -x erc20_processor deposit close --deposit-id $DEPOSIT_ID0 --account-no 2 diff --git a/.github/workflows/deposit2.yml b/.github/workflows/deposit2.yml new file mode 100644 index 00000000..712b7ce4 --- /dev/null +++ b/.github/workflows/deposit2.yml @@ -0,0 +1,120 @@ +name: Deposit tests 2 + +on: + push: + workflow_dispatch: + schedule: + - cron: "42 3 * * *" + +jobs: + test_deposit: + name: Test Deposit 2 + timeout-minutes: 20 + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cache dependencies + uses: Swatinem/rust-cache@v2 + with: + shared-key: "dev-build-cache" + + - name: Build + run: | + cargo build + cp target/debug/erc20_processor /usr/local/bin/erc20_processor + [ $(which erc20_processor) == "/usr/local/bin/erc20_processor" ] + + - name: Generate ethereum accounts + run: | + erc20_processor generate-key -n 5 > .env + cat .env | grep ETH_ADDRESS | sed "s/#\s//g" | sed "s/:\s/=/g" > $GITHUB_ENV + + - name: Create random deposit id + run: | + echo DEPOSIT_NONCE0=$(shuf -i 0-2000000000000000000 -n 1) >> $GITHUB_ENV + echo DEPOSIT_NONCE1=$(shuf -i 0-2000000000000000000 -n 1) >> $GITHUB_ENV + echo DEPOSIT_NONCE2=$(shuf -i 0-2000000000000000000 -n 1) >> $GITHUB_ENV + + - name: Show created addresses + run: | + echo "Eth address 0: $ETH_ADDRESS_0" + echo "Eth address 1: $ETH_ADDRESS_1" + echo "Eth address 2: $ETH_ADDRESS_2" + echo "Eth address 3: $ETH_ADDRESS_3" + echo "Eth address 4: $ETH_ADDRESS_4" + + - name: Get ETH from faucet for account 1 and 2 + run: | + erc20_processor get-dev-eth --account-no 1 + erc20_processor get-dev-eth --account-no 2 + + - name: Check ETH balance after getting funds from faucet (should be 0.01) + run: | + x=1; while [ $(erc20_processor balance | jq -r ".\"$ETH_ADDRESS_1\".gasDecimal") != "0.01" ]; do echo "Waiting for funds for $x seconds"; sleep 5; x=$(( x + 5 )); done + while [ $(erc20_processor balance | jq -r ".\"$ETH_ADDRESS_2\".gasDecimal") != "0.01" ]; do echo "Waiting for funds for $x seconds"; sleep 5; x=$(( x + 5 )); done + + - name: Mint tokens + run: | + erc20_processor mint-test-tokens --account-no 1 + erc20_processor run + + - name: Check token balance + run: | + [ $(erc20_processor balance | jq -r ".\"$ETH_ADDRESS_1\".tokenDecimal") == "1000" ] + + - name: Create deposit + run: | + set -x + erc20_processor deposit create --account-no 1 --amount 1 --fee-amount 0.1 --block-for 0 --spender $ETH_ADDRESS_2 --deposit-nonce $DEPOSIT_NONCE0 + erc20_processor deposit create --account-no 1 --amount 1 --fee-amount 0.1 --block-for 1000 --spender $ETH_ADDRESS_2 --deposit-nonce $DEPOSIT_NONCE1 + erc20_processor run + + - name: Get Deposit ID from funder and nonce id + run: | + echo DEPOSIT_ID0=$(erc20_processor deposit check --deposit-nonce $DEPOSIT_NONCE0 --deposit-funder=$ETH_ADDRESS_1 | jq -r ".depositId") >> $GITHUB_ENV + echo DEPOSIT_ID1=$(erc20_processor deposit check --deposit-nonce $DEPOSIT_NONCE1 --deposit-funder=$ETH_ADDRESS_1 | jq -r ".depositId") >> $GITHUB_ENV + + - name: Make single transfer from deposit + run: | + set -x + erc20_processor transfer --deposit-id $DEPOSIT_ID0 --account-no 2 --amount 0.0001 --recipient $ETH_ADDRESS_4 + erc20_processor run + + - name: Make single transfer without deposit + run: | + set -x + erc20_processor run + + - name: Make multiple transfers from deposit + run: | + set -x + erc20_processor transfer --account-no 1 --amount 0.0001 --recipient $ETH_ADDRESS_4 + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000134 --recipient $ETH_ADDRESS_0 + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.0001632 --recipient $ETH_ADDRESS_3 + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.0001724 --recipient $ETH_ADDRESS_4 + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000163 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.0001235 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.0001634 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000173 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.0001346 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.0001346 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000136 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000145 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.00016 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000173 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000145 --recipient random + erc20_processor transfer --deposit-id $DEPOSIT_ID1 --account-no 2 --amount 0.000164 --recipient random + erc20_processor deposit close --deposit-id $DEPOSIT_ID0 --account-no 2 + erc20_processor deposit close --deposit-id $DEPOSIT_ID1 --account-no 2 + erc20_processor run + + - name: Transfer all left ETH tokens + run: | + set -x + erc20_processor transfer --account-no 1 --recipient 0x5b984629E2Cc7570cBa7dD745b83c3dD23Ba6d0f --token eth --all + erc20_processor transfer --account-no 2 --recipient 0x5b984629E2Cc7570cBa7dD745b83c3dD23Ba6d0f --token eth --all + erc20_processor run \ No newline at end of file diff --git a/crates/erc20_payment_lib/config-payments.toml b/crates/erc20_payment_lib/config-payments.toml index 8cf8c34f..282f7d26 100644 --- a/crates/erc20_payment_lib/config-payments.toml +++ b/crates/erc20_payment_lib/config-payments.toml @@ -122,13 +122,13 @@ https://rpc.ankr.com/eth_goerli, priority = 0 max-timeout-ms = 5000 verify-interval-secs = 60 -allowed-head-behind-secs = 60 +allowed-head-behind-secs = 12000 [[chain.goerli.rpc-endpoints]] priority = 0 max-timeout-ms = 5000 verify-interval-secs = 60 -allowed-head-behind-secs = 120 +allowed-head-behind-secs = 12000 dns-source = "goerli.rpc-node.dev.golem.network." @@ -137,7 +137,7 @@ chain-name = "Holesky" chain-id = 17000 currency-symbol = "tETH" priority-fee = 0.000001 -max-fee-per-gas = 10.0 +max-fee-per-gas = 20.0 transaction-timeout = 100 token = { address = "0x8888888815bf4DB87e57B609A50f938311EEd068", symbol = "tGLM" } multi-contract = { address = "0xAaAAAaA00E1841A63342db7188abA84BDeE236c7", max-at-once = 10 } diff --git a/crates/erc20_payment_lib/src/contracts.rs b/crates/erc20_payment_lib/src/contracts.rs index 0c83493b..5c44831d 100644 --- a/crates/erc20_payment_lib/src/contracts.rs +++ b/crates/erc20_payment_lib/src/contracts.rs @@ -94,6 +94,17 @@ pub fn encode_deposit_transfer( ) } +pub fn encode_deposit_transfer_and_close( + deposit_id: U256, + packed: Vec<[u8; 32]>, +) -> Result, web3::ethabi::Error> { + contract_encode( + &LOCK_CONTRACT_TEMPLATE, + "depositTransferAndClose", + (deposit_id, packed), + ) +} + pub fn encode_multi_direct( recipients: Vec
, amounts: Vec, @@ -180,6 +191,18 @@ pub fn encode_payout_single( ) } +pub fn encode_payout_single_and_close( + id: U256, + recipient: Address, + amount: U256, +) -> Result, web3::ethabi::Error> { + contract_encode( + &LOCK_CONTRACT_TEMPLATE, + "depositSingleTransferAndClose", + (id, recipient, amount), + ) +} + pub fn encode_get_deposit_details(id: U256) -> Result, web3::ethabi::Error> { contract_encode(&LOCK_CONTRACT_TEMPLATE, "getDeposit", (id,)) } diff --git a/crates/erc20_payment_lib/src/runtime.rs b/crates/erc20_payment_lib/src/runtime.rs index da0ce68b..09b626e5 100644 --- a/crates/erc20_payment_lib/src/runtime.rs +++ b/crates/erc20_payment_lib/src/runtime.rs @@ -1,14 +1,15 @@ use crate::signer::{Signer, SignerAccount}; use crate::transaction::{ - create_close_deposit, create_faucet_mint, create_make_deposit, create_terminate_deposit, - create_token_transfer, find_receipt_extended, FindReceiptParseResult, + create_faucet_mint, create_make_deposit, create_terminate_deposit, create_token_transfer, + find_receipt_extended, FindReceiptParseResult, }; use crate::{err_custom_create, err_from}; use erc20_payment_lib_common::create_sqlite_connection; use erc20_payment_lib_common::ops::{ cleanup_allowance_tx, cleanup_token_transfer_tx, delete_tx, get_last_unsent_tx, - get_transaction_chain, get_transactions, get_unpaid_token_transfers, insert_token_transfer, - insert_tx, + get_token_transfers_by_deposit_id, get_transaction_chain, get_transactions, + get_unpaid_token_transfers, insert_token_transfer, insert_token_transfer_with_deposit_check, + insert_tx, update_token_transfer, }; use std::collections::BTreeMap; use std::ops::DerefMut; @@ -32,6 +33,7 @@ use crate::eth::{ use crate::sender::service_loop; use crate::utils::{DecimalConvExt, StringConvExt, U256ConvExt}; use chrono::{DateTime, Utc}; +use erc20_payment_lib_common::model::TokenTransferDbObj; use erc20_payment_lib_common::{ DriverEvent, DriverEventContent, FaucetData, SharedInfoTx, StatusProperty, TransactionFailedReason, TransactionStuckReason, Web3RpcPoolContent, @@ -921,9 +923,7 @@ impl PaymentRuntime { transfer_args.deposit_id, ); - insert_token_transfer(&self.conn, &token_transfer) - .await - .map_err(err_from!())?; + insert_token_transfer_with_deposit_check(&self.conn, &token_transfer).await?; if !self.setup.ignore_deadlines { if let Some(deadline) = transfer_args.deadline { @@ -1126,6 +1126,7 @@ pub struct CloseDepositOptionsInt { pub lock_contract_address: Address, pub skip_deposit_check: bool, pub deposit_id: U256, + pub token_address: Address, } pub struct TerminateDepositOptionsInt { @@ -1141,14 +1142,6 @@ pub async fn close_deposit( from: Address, opt: CloseDepositOptionsInt, ) -> Result<(), PaymentError> { - let free_deposit_tx_id = create_close_deposit( - from, - opt.lock_contract_address, - chain_id, - None, - opt.deposit_id, - )?; - //let mut block_info: Option = None; if !opt.skip_deposit_check { let deposit_details = @@ -1168,12 +1161,74 @@ pub async fn close_deposit( } let mut db_transaction = conn.begin().await.map_err(err_from!())?; - let make_deposit_tx = insert_tx(&mut *db_transaction, &free_deposit_tx_id) - .await - .map_err(err_from!())?; + + let current_token_transfers = get_token_transfers_by_deposit_id( + &mut *db_transaction, + chain_id as i64, + &format!("{:#x}", opt.deposit_id), + ) + .await + .map_err(err_from!())?; + + for tt in ¤t_token_transfers { + if tt.deposit_finish > 0 { + return Err(err_custom_create!( + "Deposit {} already being closed or closed", + opt.deposit_id + )); + } + } + let mut candidate_for_mark_close: Option<&TokenTransferDbObj> = None; + for tt in ¤t_token_transfers { + if tt.tx_id.is_none() { + if let Some(old_tx) = candidate_for_mark_close { + if old_tx.id < tt.id { + candidate_for_mark_close = Some(tt); + } else { + candidate_for_mark_close = Some(old_tx); + } + } else { + candidate_for_mark_close = Some(tt); + } + } + } + + if let Some(tt) = candidate_for_mark_close { + let mut tt = tt.clone(); + tt.deposit_finish = 1; + update_token_transfer(&mut *db_transaction, &tt) + .await + .map_err(err_from!())?; + } else { + //empty transfer is just a marker that we need deposit to be closed + let new_tt = TokenTransferDbObj { + id: 0, + payment_id: Some(format!("close_deposit_{:#x}", opt.deposit_id)), + from_addr: format!("{:#x}", from), + receiver_addr: format!("{:#x}", Address::zero()), + chain_id: chain_id as i64, + token_addr: Some(format!("{:#x}", opt.token_address)), + token_amount: "0".to_string(), + deposit_id: Some(format!("{:#x}", opt.deposit_id)), + deposit_finish: 1, + create_date: chrono::Utc::now(), + tx_id: None, + paid_date: None, + fee_paid: None, + error: None, + }; + insert_token_transfer(&mut *db_transaction, &new_tt) + .await + .map_err(err_from!())?; + } + + //let mut db_transaction = conn.begin().await.map_err(err_from!())?; + //let make_deposit_tx = insert_tx(&mut *db_transaction, &free_deposit_tx_id) + // .await + // .map_err(err_from!())?; db_transaction.commit().await.map_err(err_from!())?; - log::info!("Close deposit added to queue: {}", make_deposit_tx.id); + //log::info!("Close deposit added to queue: {}", make_deposit_tx.id); Ok(()) } diff --git a/crates/erc20_payment_lib/src/sender/batching.rs b/crates/erc20_payment_lib/src/sender/batching.rs index 75740d10..dfede812 100644 --- a/crates/erc20_payment_lib/src/sender/batching.rs +++ b/crates/erc20_payment_lib/src/sender/batching.rs @@ -6,9 +6,9 @@ use erc20_payment_lib_common::ops::*; use crate::error::{AllowanceRequest, ErrorBag, PaymentError}; use crate::transaction::{ - create_erc20_deposit_transfer, create_erc20_transfer, create_erc20_transfer_multi, - create_erc20_transfer_multi_deposit, create_eth_transfer, MultiTransferArgs, - MultiTransferDepositArgs, + create_close_deposit, create_erc20_deposit_transfer, create_erc20_transfer, + create_erc20_transfer_multi, create_erc20_transfer_multi_deposit, create_eth_transfer, + MultiTransferArgs, MultiTransferDepositArgs, SingleTransferDepositArgs, }; use crate::setup::PaymentSetup; @@ -51,7 +51,8 @@ pub struct TokenTransferMultiOrder { pub async fn gather_transactions_pre( account: &SignerAccount, conn: &SqlitePool, - _payment_setup: &PaymentSetup, + payment_setup: &PaymentSetup, + process_tx_needed: &mut bool, ) -> Result { let mut transfer_map = TokenTransferMap::new(); @@ -81,6 +82,59 @@ pub async fn gather_transactions_pre( continue; } } + if let Some(deposit_id) = f.deposit_id.as_ref() { + if f.deposit_finish > 0 + && Address::from_str(&f.receiver_addr).ok() == Some(Address::zero()) + && U256::from_dec_str(&f.token_amount).ok() == Some(U256::zero()) + { + log::info!( + "Creating close deposit transaction for deposit id: {}", + deposit_id + ); + + let deposit_id_u256 = U256::from_str(deposit_id) + .map_err(|_| err_custom_create!("Invalid deposit id: {}", deposit_id))?; + + let lock_contract_address = payment_setup + .chain_setup + .get(&f.chain_id) + .ok_or(err_custom_create!( + "No setup found for chain id: {}", + f.chain_id + ))? + .lock_contract_address + .ok_or(err_custom_create!( + "Lock contract address not set for chain id: {}", + f.chain_id + ))?; + + let from_addr = Address::from_str(&f.from_addr).map_err(err_from!())?; + + let close_deposit_tx_id = create_close_deposit( + from_addr, + lock_contract_address, + f.chain_id as u64, + None, + deposit_id_u256, + )?; + + let mut transaction = conn.begin().await.map_err(err_from!())?; + + let new_tx = insert_tx(&mut *transaction, &close_deposit_tx_id) + .await + .map_err(err_from!())?; + + let mut tt = f.clone(); + tt.tx_id = Some(new_tx.id); + update_token_transfer(&mut *transaction, &tt) + .await + .map_err(err_from!())?; + + transaction.commit().await.map_err(err_from!())?; + *process_tx_needed = true; + continue; + } + } match Address::from_str(&f.receiver_addr) { Ok(rec_address) => { if rec_address == Address::zero() { @@ -198,10 +252,15 @@ pub async fn gather_transactions_batch_multi( for smaller_order in split_orders { let mut erc20_to = Vec::with_capacity(smaller_order.len()); let mut erc20_amounts = Vec::with_capacity(smaller_order.len()); + let mut is_deposit_finish = false; for token_t in &mut *smaller_order { let mut sum = U256::zero(); for token_transfer in &token_t.token_transfers { sum += U256::from_dec_str(&token_transfer.token_amount).map_err(err_from!())?; + //close deposit if needed + if token_transfer.deposit_finish > 0 { + is_deposit_finish = true; + } } erc20_to.push(token_t.receiver); erc20_amounts.push(sum); @@ -231,15 +290,16 @@ pub async fn gather_transactions_batch_multi( ))?; let deposit_id = U256::from_str(deposit_id) .map_err(|err| err_custom_create!("Invalid deposit id: {}", err))?; - create_erc20_deposit_transfer( - Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, - erc20_to[0], - erc20_amounts[0], - token_transfer.chain_id as u64, - None, - lock_contract_address, + create_erc20_deposit_transfer(SingleTransferDepositArgs { + from: Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, + lock_contract: lock_contract_address, + erc20_to: erc20_to[0], + erc20_amount: erc20_amounts[0], + chain_id: token_transfer.chain_id as u64, + gas_limit: None, deposit_id, - )? + deposit_finish: is_deposit_finish, + })? } else { create_erc20_transfer( Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, @@ -263,6 +323,7 @@ pub async fn gather_transactions_batch_multi( lock_contract_address, erc20_to.len(), ); + create_erc20_transfer_multi_deposit(MultiTransferDepositArgs { from: Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, lock_contract: lock_contract_address, @@ -271,6 +332,7 @@ pub async fn gather_transactions_batch_multi( chain_id: token_transfer.chain_id as u64, gas_limit: None, deposit_id, + deposit_finish: is_deposit_finish, })? } else if let Some(multi_contract_address) = chain_setup.multi_contract_address { log::info!( @@ -356,15 +418,18 @@ pub async fn gather_transactions_batch( ))?; let deposit_id = U256::from_str(deposit_id) .map_err(|err| err_custom_create!("Invalid deposit id: {}", err))?; - create_erc20_deposit_transfer( - Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, - Address::from_str(&token_transfer.receiver_addr).map_err(err_from!())?, - sum, - token_transfer.chain_id as u64, - None, - lock_contract_address, + + let is_deposit_finish = token_transfers.iter().any(|f| f.deposit_finish > 0); + create_erc20_deposit_transfer(SingleTransferDepositArgs { + from: Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, + lock_contract: lock_contract_address, + erc20_to: Address::from_str(&token_transfer.receiver_addr).map_err(err_from!())?, + erc20_amount: sum, + chain_id: token_transfer.chain_id as u64, + gas_limit: None, deposit_id, - )? + deposit_finish: is_deposit_finish, + })? } else { create_erc20_transfer( Address::from_str(&token_transfer.from_addr).map_err(err_from!())?, diff --git a/crates/erc20_payment_lib/src/sender/service.rs b/crates/erc20_payment_lib/src/sender/service.rs index 180b8a44..41782677 100644 --- a/crates/erc20_payment_lib/src/sender/service.rs +++ b/crates/erc20_payment_lib/src/sender/service.rs @@ -573,25 +573,33 @@ pub async fn service_loop( continue; } } + metrics::counter!(metric_label_gather_pre, 1); log::debug!("Gathering payments..."); - let mut token_transfer_map = - match gather_transactions_pre(&signer_account, conn, payment_setup).await { - Ok(token_transfer_map) => token_transfer_map, - Err(e) => { - metrics::counter!(metric_label_gather_pre_error, 1); - log::error!( + + let mut token_transfer_map = match gather_transactions_pre( + &signer_account, + conn, + payment_setup, + &mut process_tx_needed, + ) + .await + { + Ok(token_transfer_map) => token_transfer_map, + Err(e) => { + metrics::counter!(metric_label_gather_pre_error, 1); + log::error!( "Error in gather transactions, driver will be stuck, Fix DB to continue {:?}", e ); - tokio::time::sleep(std::time::Duration::from_secs( - payment_setup.process_interval_after_error, - )) - .await; - continue; - } - }; + tokio::time::sleep(std::time::Duration::from_secs( + payment_setup.process_interval_after_error, + )) + .await; + continue; + } + }; metrics::counter!(metric_label_gather_post, 1); match gather_transactions_post( diff --git a/crates/erc20_payment_lib/src/transaction.rs b/crates/erc20_payment_lib/src/transaction.rs index a10b6644..e989ee2c 100644 --- a/crates/erc20_payment_lib/src/transaction.rs +++ b/crates/erc20_payment_lib/src/transaction.rs @@ -130,6 +130,7 @@ pub fn create_token_transfer( token_addr: token_addr.map(|addr| format!("{addr:#x}")), token_amount: token_amount.to_string(), deposit_id, + deposit_finish: 0, create_date: Utc::now(), tx_id: None, paid_date: None, @@ -177,26 +178,55 @@ pub fn create_erc20_transfer( }) } +pub struct SingleTransferDepositArgs { + pub from: Address, + pub lock_contract: Address, + pub erc20_to: Address, + pub erc20_amount: U256, + pub chain_id: u64, + pub gas_limit: Option, + pub deposit_id: U256, + pub deposit_finish: bool, +} + pub fn create_erc20_deposit_transfer( - from: Address, - erc20_to: Address, - erc20_amount: U256, - chain_id: u64, - gas_limit: Option, - lock_contract_address: Address, - deposit_id: U256, + single_args: SingleTransferDepositArgs, ) -> Result { - Ok(TxDbObj { - method: "LOCK.depositSingleTransfer".to_string(), - from_addr: format!("{from:#x}"), - to_addr: format!("{lock_contract_address:#x}"), - chain_id: chain_id as i64, - gas_limit: gas_limit.map(|gas_limit| gas_limit as i64), - call_data: Some(hex::encode( - encode_payout_single(deposit_id, erc20_to, erc20_amount).map_err(err_from!())?, - )), - ..Default::default() - }) + if !single_args.deposit_finish { + Ok(TxDbObj { + method: "LOCK.depositSingleTransfer".to_string(), + from_addr: format!("{:#x}", single_args.from), + to_addr: format!("{:#x}", single_args.lock_contract), + chain_id: single_args.chain_id as i64, + gas_limit: single_args.gas_limit.map(|gas_limit| gas_limit as i64), + call_data: Some(hex::encode( + encode_payout_single( + single_args.deposit_id, + single_args.erc20_to, + single_args.erc20_amount, + ) + .map_err(err_from!())?, + )), + ..Default::default() + }) + } else { + Ok(TxDbObj { + method: "LOCK.depositSingleTransferAndClose".to_string(), + from_addr: format!("{:#x}", single_args.from), + to_addr: format!("{:#x}", single_args.lock_contract), + chain_id: single_args.chain_id as i64, + gas_limit: single_args.gas_limit.map(|gas_limit| gas_limit as i64), + call_data: Some(hex::encode( + encode_payout_single_and_close( + single_args.deposit_id, + single_args.erc20_to, + single_args.erc20_amount, + ) + .map_err(err_from!())?, + )), + ..Default::default() + }) + } } pub struct MultiTransferDepositArgs { @@ -207,6 +237,7 @@ pub struct MultiTransferDepositArgs { pub chain_id: u64, pub gas_limit: Option, pub deposit_id: U256, + pub deposit_finish: bool, } pub fn create_erc20_transfer_multi_deposit( @@ -215,16 +246,30 @@ pub fn create_erc20_transfer_multi_deposit( let (packed, _sum) = pack_transfers_for_multi_contract(multi_args.erc20_to, multi_args.erc20_amount)?; - let data = encode_deposit_transfer(multi_args.deposit_id, packed).map_err(err_from!())?; - Ok(TxDbObj { - method: "LOCK.depositTransfer".to_string(), - from_addr: format!("{:#x}", multi_args.from), - to_addr: format!("{:#x}", multi_args.lock_contract), - chain_id: multi_args.chain_id as i64, - gas_limit: multi_args.gas_limit.map(|gas_limit| gas_limit as i64), - call_data: Some(hex::encode(data)), - ..Default::default() - }) + if !multi_args.deposit_finish { + let data = encode_deposit_transfer(multi_args.deposit_id, packed).map_err(err_from!())?; + Ok(TxDbObj { + method: "LOCK.depositTransfer".to_string(), + from_addr: format!("{:#x}", multi_args.from), + to_addr: format!("{:#x}", multi_args.lock_contract), + chain_id: multi_args.chain_id as i64, + gas_limit: multi_args.gas_limit.map(|gas_limit| gas_limit as i64), + call_data: Some(hex::encode(data)), + ..Default::default() + }) + } else { + let data = encode_deposit_transfer_and_close(multi_args.deposit_id, packed) + .map_err(err_from!())?; + Ok(TxDbObj { + method: "LOCK.depositTransferAndClose".to_string(), + from_addr: format!("{:#x}", multi_args.from), + to_addr: format!("{:#x}", multi_args.lock_contract), + chain_id: multi_args.chain_id as i64, + gas_limit: multi_args.gas_limit.map(|gas_limit| gas_limit as i64), + call_data: Some(hex::encode(data)), + ..Default::default() + }) + } } pub struct MultiTransferArgs { diff --git a/crates/erc20_payment_lib_common/migrations/20240312000000_add_finish.sql b/crates/erc20_payment_lib_common/migrations/20240312000000_add_finish.sql new file mode 100644 index 00000000..9bad53cb --- /dev/null +++ b/crates/erc20_payment_lib_common/migrations/20240312000000_add_finish.sql @@ -0,0 +1,48 @@ +DROP TABLE IF EXISTS "old_table"; +DROP TABLE IF EXISTS "new_table"; + +CREATE TABLE "old_table" +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + payment_id TEXT NULL, + from_addr TEXT NOT NULL, + receiver_addr TEXT NOT NULL, + chain_id INTEGER NOT NULL, + token_addr TEXT NULL, + token_amount TEXT NOT NULL, + deposit_id TEXT NULL, + create_date TEXT NOT NULL, + paid_date TEXT NULL, + tx_id INTEGER NULL, + fee_paid TEXT NULL, + error TEXT NULL +) strict; + +INSERT INTO old_table SELECT * FROM token_transfer; + +DROP TABLE token_transfer; + +CREATE TABLE token_transfer +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + payment_id TEXT NULL, + from_addr TEXT NOT NULL, + receiver_addr TEXT NOT NULL, + chain_id INTEGER NOT NULL, + token_addr TEXT NULL, + token_amount TEXT NOT NULL, + deposit_id TEXT NULL, + deposit_finish INTEGER NOT NULL DEFAULT 0, + create_date TEXT NOT NULL, + paid_date TEXT NULL, + tx_id INTEGER NULL, + fee_paid TEXT NULL, + error TEXT NULL, + CONSTRAINT "fk_token_transfer_tx" FOREIGN KEY ("tx_id") REFERENCES "tx" ("id") +) strict; + +INSERT INTO token_transfer(id, payment_id, from_addr, receiver_addr, chain_id, token_addr, token_amount, deposit_id, create_date, paid_date, tx_id, fee_paid, error) + SELECT id, payment_id, from_addr, receiver_addr, chain_id, token_addr, token_amount, deposit_id, create_date, paid_date, tx_id, fee_paid, error FROM old_table; + +-- Make sure that extracting transfers for given deposit_id is fast +CREATE INDEX "idx_deposit_id" ON "token_transfer" (deposit_id); diff --git a/crates/erc20_payment_lib_common/src/db/model/token_transfer_dao.rs b/crates/erc20_payment_lib_common/src/db/model/token_transfer_dao.rs index 89ba31cd..5d00ffd8 100644 --- a/crates/erc20_payment_lib_common/src/db/model/token_transfer_dao.rs +++ b/crates/erc20_payment_lib_common/src/db/model/token_transfer_dao.rs @@ -13,8 +13,9 @@ pub struct TokenTransferDbObj { pub token_amount: String, /// If set payment done from internal deposit pub deposit_id: Option, + pub deposit_finish: i64, /// The time when the record is inserted into the database - /// It is overriden when inserting new entry to db + /// It is override when inserting new entry to db pub create_date: DateTime, pub tx_id: Option, pub paid_date: Option>, diff --git a/crates/erc20_payment_lib_common/src/db/ops/token_transfer_ops.rs b/crates/erc20_payment_lib_common/src/db/ops/token_transfer_ops.rs index cd558958..6f9b9c2d 100644 --- a/crates/erc20_payment_lib_common/src/db/ops/token_transfer_ops.rs +++ b/crates/erc20_payment_lib_common/src/db/ops/token_transfer_ops.rs @@ -1,8 +1,8 @@ use super::model::TokenTransferDbObj; use crate::db::ops::get_chain_transfers_by_chain_id; -use crate::err_from; use crate::error::PaymentError; use crate::error::*; +use crate::{err_custom_create, err_from}; use chrono::{DateTime, Duration, Utc}; use sqlx::Executor; use sqlx::Sqlite; @@ -12,6 +12,24 @@ use std::ops::AddAssign; use std::str::FromStr; use web3::types::{Address, U256}; +pub async fn check_if_deposit_closed<'c, E>( + executor: E, + chain_id: i64, + deposit_id: &str, +) -> Result +where + E: Executor<'c, Database = Sqlite>, +{ + let finished_count = sqlx::query_as::<_, (i64,)>( + "SELECT COUNT(*) FROM token_transfer WHERE chain_id=$1 AND deposit_id=$2 AND deposit_finish=1", + ) + .bind(chain_id) + .bind(deposit_id) + .fetch_one(executor) + .await?; + Ok(finished_count.0 > 0) +} + pub async fn insert_token_transfer<'c, E>( executor: E, token_transfer: &TokenTransferDbObj, @@ -21,8 +39,8 @@ where { sqlx::query_as::<_, TokenTransferDbObj>( r"INSERT INTO token_transfer -(payment_id, from_addr, receiver_addr, chain_id, token_addr, token_amount, deposit_id, create_date, tx_id, paid_date, fee_paid, error) -VALUES ($1, $2, $3, $4, $5, $6, $7, strftime('%Y-%m-%dT%H:%M:%f', 'now'), $9, $10, $11, $12) RETURNING *; +(payment_id, from_addr, receiver_addr, chain_id, token_addr, token_amount, deposit_id, deposit_finish, create_date, tx_id, paid_date, fee_paid, error) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, strftime('%Y-%m-%dT%H:%M:%f', 'now'), $9, $10, $11, $12) RETURNING *; ", ) .bind(&token_transfer.payment_id) @@ -32,6 +50,7 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, strftime('%Y-%m-%dT%H:%M:%f', 'now'), $9, $1 .bind(&token_transfer.token_addr) .bind(&token_transfer.token_amount) .bind(&token_transfer.deposit_id) + .bind(token_transfer.deposit_finish) .bind(token_transfer.tx_id) .bind(token_transfer.paid_date) .bind(&token_transfer.fee_paid) @@ -40,6 +59,50 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, strftime('%Y-%m-%dT%H:%M:%f', 'now'), $9, $1 .await } +pub async fn insert_token_transfer_with_deposit_check( + conn: &SqlitePool, + token_transfer: &TokenTransferDbObj, +) -> Result { + if let Some(deposit_id) = token_transfer.deposit_id.as_ref() { + let mut transaction = conn.begin().await.map_err(err_from!())?; + let is_finished = + check_if_deposit_closed(&mut *transaction, token_transfer.chain_id, deposit_id) + .await + .map_err(err_from!())?; + if is_finished { + return Err(err_custom_create!( + "Cannot add token_transfer to already finished deposit" + )); + } + let res = sqlx::query_as::<_, TokenTransferDbObj>( + r"INSERT INTO token_transfer +(payment_id, from_addr, receiver_addr, chain_id, token_addr, token_amount, deposit_id, deposit_finish, create_date, tx_id, paid_date, fee_paid, error) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, strftime('%Y-%m-%dT%H:%M:%f', 'now'), $9, $10, $11, $12) RETURNING *; +", + ) + .bind(&token_transfer.payment_id) + .bind(&token_transfer.from_addr) + .bind(&token_transfer.receiver_addr) + .bind(token_transfer.chain_id) + .bind(&token_transfer.token_addr) + .bind(&token_transfer.token_amount) + .bind(&token_transfer.deposit_id) + .bind(token_transfer.deposit_finish) + .bind(token_transfer.tx_id) + .bind(token_transfer.paid_date) + .bind(&token_transfer.fee_paid) + .bind(&token_transfer.error) + .fetch_one(&mut *transaction) + .await.map_err(err_from!())?; + transaction.commit().await.map_err(err_from!())?; + Ok(res) + } else { + insert_token_transfer(conn, token_transfer) + .await + .map_err(err_from!()) + } +} + pub async fn remap_token_transfer_tx<'c, E>( executor: E, old_tx_id: i64, @@ -96,10 +159,11 @@ chain_id = $5, token_addr = $6, token_amount = $7, deposit_id = $8, -tx_id = $9, -paid_date = $10, -fee_paid = $11, -error = $12 +deposit_finish = $9, +tx_id = $10, +paid_date = $11, +fee_paid = $12, +error = $13 WHERE id = $1 ", ) @@ -111,6 +175,7 @@ WHERE id = $1 .bind(&token_transfer.token_addr) .bind(&token_transfer.token_amount) .bind(&token_transfer.deposit_id) + .bind(token_transfer.deposit_finish) .bind(token_transfer.tx_id) .bind(token_transfer.paid_date) .bind(&token_transfer.fee_paid) @@ -150,6 +215,24 @@ pub async fn get_token_transfers_by_chain_id( Ok(rows) } +pub async fn get_token_transfers_by_deposit_id<'c, E>( + conn: E, + chain_id: i64, + deposit_id: &str, +) -> Result, sqlx::Error> +where + E: Executor<'c, Database = Sqlite>, +{ + let rows = sqlx::query_as::<_, TokenTransferDbObj>( + r"SELECT * FROM token_transfer WHERE chain_id = $1 AND deposit_id = $2 ORDER by id DESC", + ) + .bind(chain_id) + .bind(deposit_id) + .fetch_all(conn) + .await?; + Ok(rows) +} + pub async fn get_pending_token_transfers( conn: &SqlitePool, account: Address, @@ -159,6 +242,7 @@ pub async fn get_pending_token_transfers( WHERE tx_id is null AND error is null AND from_addr = $1 +ORDER by id ASC ", ) .bind(format!("{:#x}", account)) diff --git a/src/actions/deposit/close.rs b/src/actions/deposit/close.rs index fcc6c180..b382ce19 100644 --- a/src/actions/deposit/close.rs +++ b/src/actions/deposit/close.rs @@ -71,6 +71,7 @@ pub async fn close_deposit_local( .expect("No lock contract found"), deposit_id, skip_deposit_check: close_deposit_options.skip_check, + token_address: chain_cfg.token.address, }, ) .await?; diff --git a/src/main.rs b/src/main.rs index ca5526a0..5987e977 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,8 @@ use erc20_payment_lib::signer::PrivateKeySigner; use erc20_payment_lib_common::create_sqlite_connection; use erc20_payment_lib_common::error::*; use erc20_payment_lib_common::ops::{ - get_next_transactions_to_process, insert_token_transfer, update_token_transfer, + get_next_transactions_to_process, insert_token_transfer, + insert_token_transfer_with_deposit_check, update_token_transfer, }; use erc20_payment_lib_common::*; @@ -439,9 +440,9 @@ async fn main_internal() -> Result<(), PaymentError> { .get(account_no) .expect("No public adss found with specified account_no") } else { - *public_addrs.first().expect("No public adss found") + *public_addrs.first().expect("No public address found") }; - let mut db_transaction = conn.clone().unwrap().begin().await.unwrap(); + //let mut db_transaction = conn.clone().unwrap().begin().await.unwrap(); let amount_str = if let Some(amount) = single_transfer_options.amount { amount.to_u256_from_eth().unwrap().to_string() @@ -490,8 +491,8 @@ async fn main_internal() -> Result<(), PaymentError> { }; let amount_decimal = amount_str.to_eth().unwrap(); - let mut tt = insert_token_transfer( - &mut *db_transaction, + let mut tt = insert_token_transfer_with_deposit_check( + &conn.clone().unwrap(), &TokenTransferDbObj { id: 0, payment_id: None, @@ -501,6 +502,7 @@ async fn main_internal() -> Result<(), PaymentError> { token_addr: token, token_amount: amount_str, deposit_id: single_transfer_options.deposit_id, + deposit_finish: 0, create_date: Default::default(), tx_id: None, paid_date: None, @@ -513,11 +515,10 @@ async fn main_internal() -> Result<(), PaymentError> { let payment_id = format!("{}_transfer_{}", single_transfer_options.token, tt.id); tt.payment_id = Some(payment_id.clone()); - update_token_transfer(&mut *db_transaction, &tt) + update_token_transfer(&conn.clone().unwrap(), &tt) .await .unwrap(); - db_transaction.commit().await.unwrap(); log::info!( "Transfer added to db amount: {}, payment id: {}", amount_decimal,