Skip to content

Commit

Permalink
Merge pull request #941 from matter-labs/ib-934-state_keeper_fee_upda…
Browse files Browse the repository at this point in the history
…tes_only_when_sealing_block

state keeper fee updates
  • Loading branch information
popzxc authored Sep 16, 2020
2 parents ad966ea + 831ba27 commit 1d13d02
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 37 deletions.
25 changes: 15 additions & 10 deletions core/server/src/state_keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use models::{
},
ActionType, BlockCommitRequest, CommitRequest,
};
use plasma::state::{OpSuccess, PlasmaState};
use plasma::state::{CollectedFee, OpSuccess, PlasmaState};
use storage::ConnectionPool;
// Local uses
use crate::{gas_counter::GasCounter, mempool::ProposedBlock};
Expand Down Expand Up @@ -64,6 +64,8 @@ struct PendingBlock {
gas_counter: GasCounter,
/// Option denoting if this block should be generated faster than usual.
fast_processing_required: bool,
/// Fee should be applied only when sealing the block (because of corresponding logic in the circuit)
collected_fees: Vec<CollectedFee>,
}

impl PendingBlock {
Expand All @@ -79,6 +81,7 @@ impl PendingBlock {
withdrawals_amount: 0,
gas_counter: GasCounter::new(),
fast_processing_required: false,
collected_fees: Vec::new(),
}
}
}
Expand Down Expand Up @@ -624,10 +627,7 @@ impl PlasmaStateKeeper {
self.pending_block.chunks_left -= chunks_needed;
self.pending_block.account_updates.append(&mut updates);
if let Some(fee) = fee {
let fee_updates = self.state.collect_fee(&[fee], self.fee_account_id);
self.pending_block
.account_updates
.extend(fee_updates.into_iter());
self.pending_block.collected_fees.push(fee);
}
let block_index = self.pending_block.pending_op_block_index;
self.pending_block.pending_op_block_index += 1;
Expand Down Expand Up @@ -699,10 +699,7 @@ impl PlasmaStateKeeper {
self.pending_block.chunks_left -= chunks_needed;
self.pending_block.account_updates.append(&mut updates);
if let Some(fee) = fee {
let fee_updates = self.state.collect_fee(&[fee], self.fee_account_id);
self.pending_block
.account_updates
.extend(fee_updates.into_iter());
self.pending_block.collected_fees.push(fee);
}
let block_index = self.pending_block.pending_op_block_index;
self.pending_block.pending_op_block_index += 1;
Expand Down Expand Up @@ -740,7 +737,7 @@ impl PlasmaStateKeeper {

/// Finalizes the pending block, transforming it into a full block.
async fn seal_pending_block(&mut self) {
let pending_block = std::mem::replace(
let mut pending_block = std::mem::replace(
&mut self.pending_block,
PendingBlock::new(
self.current_unprocessed_priority_op,
Expand All @@ -751,6 +748,14 @@ impl PlasmaStateKeeper {
),
);

// Apply fees of pending block
let fee_updates = self
.state
.collect_fee(&pending_block.collected_fees, self.fee_account_id);
pending_block
.account_updates
.extend(fee_updates.into_iter());

let mut block_transactions = pending_block.success_operations;
block_transactions.extend(
pending_block
Expand Down
81 changes: 54 additions & 27 deletions js/tests/simple-integration-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,11 @@ async function testDeposit(depositWallet: Wallet, syncWallet: Wallet, token: typ
}
}

async function testTransferToSelf(syncWallet: Wallet, token: types.TokenLike, amount: BigNumber) {
async function testTransferToSelf(syncWallet: Wallet, token: types.TokenLike, amount: BigNumber, feeInfo: any) {
const fullFee = await syncProvider.getTransactionFee("Transfer", syncWallet.address(), token);
const fee = fullFee.totalFee;

const walletBeforeTransfer = await syncWallet.getBalance(token);
const operatorBeforeTransfer = await getOperatorBalance(token);
const startTime = new Date().getTime();
const transferToNewHandle = await syncWallet.syncTransfer({
to: syncWallet.address(),
Expand All @@ -110,29 +109,29 @@ async function testTransferToSelf(syncWallet: Wallet, token: types.TokenLike, am
await transferToNewHandle.awaitReceipt();
console.log(`Transfer to self committed: ${new Date().getTime() - startTime} ms`);
const walletAfterTransfer = await syncWallet.getBalance(token);
const operatorAfterTransfer = await getOperatorBalance(token);

let transferCorrect = true;
transferCorrect = transferCorrect && walletBeforeTransfer.sub(fee).eq(walletAfterTransfer);
transferCorrect = transferCorrect && operatorAfterTransfer.sub(operatorBeforeTransfer).eq(fee);
if (!transferCorrect) {
throw new Error("Transfer to self checks failed");
}

feeInfo.globalFee = feeInfo.globalFee.add(fee);
}

async function testTransfer(
syncWallet1: Wallet,
syncWallet2: Wallet,
token: types.TokenLike,
amount: BigNumber,
feeInfo: any,
timeoutBeforeReceipt = 0
) {
const fullFee = await syncProvider.getTransactionFee("Transfer", syncWallet2.address(), token);
const fee = fullFee.totalFee;

const wallet1BeforeTransfer = await syncWallet1.getBalance(token);
const wallet2BeforeTransfer = await syncWallet2.getBalance(token);
const operatorBeforeTransfer = await getOperatorBalance(token);
const startTime = new Date().getTime();
const transferToNewHandle = await syncWallet1.syncTransfer({
to: syncWallet2.address(),
Expand All @@ -146,29 +145,29 @@ async function testTransfer(
console.log(`Transfer committed: ${new Date().getTime() - startTime} ms`);
const wallet1AfterTransfer = await syncWallet1.getBalance(token);
const wallet2AfterTransfer = await syncWallet2.getBalance(token);
const operatorAfterTransfer = await getOperatorBalance(token);

let transferCorrect = true;
transferCorrect = transferCorrect && wallet1BeforeTransfer.sub(wallet1AfterTransfer).eq(amount.add(fee));
transferCorrect = transferCorrect && wallet2AfterTransfer.sub(wallet2BeforeTransfer).eq(amount);
transferCorrect = transferCorrect && operatorAfterTransfer.sub(operatorBeforeTransfer).eq(fee);
if (!transferCorrect) {
throw new Error("Transfer checks failed");
}

feeInfo.globalFee = feeInfo.globalFee.add(fee);
}

async function testWithdraw(
contract: Contract,
withdrawTo: Wallet,
syncWallet: Wallet,
token: types.TokenLike,
amount: BigNumber
amount: BigNumber,
feeInfo: any
) {
const fullFee = await syncProvider.getTransactionFee("Withdraw", withdrawTo.address(), token);
const fee = fullFee.totalFee;

const wallet2BeforeWithdraw = await syncWallet.getBalance(token);
const operatorBeforeWithdraw = await getOperatorBalance(token);
const onchainBalanceBeforeWithdraw = await withdrawTo.getEthereumBalance(token);
const startTime = new Date().getTime();
const withdrawHandle = await syncWallet.withdrawFromSyncToEthereum({
Expand All @@ -183,7 +182,6 @@ async function testWithdraw(
await promiseTimeout(VERIFY_TIMEOUT, withdrawHandle.awaitVerifyReceipt());
console.log(`Withdraw verified: ${new Date().getTime() - startTime} ms`);
const wallet2AfterWithdraw = await syncWallet.getBalance(token);
const operatorAfterWithdraw = await getOperatorBalance(token);
const onchainBalanceAfterWithdraw = await withdrawTo.getEthereumBalance(token);

const tokenId = await withdrawTo.provider.tokenSet.resolveTokenId(token);
Expand All @@ -192,26 +190,34 @@ async function testWithdraw(
if (!wallet2BeforeWithdraw.sub(wallet2AfterWithdraw).eq(amount.add(fee))) {
throw new Error("Wrong amount on wallet after WITHDRAW");
}
if (!operatorAfterWithdraw.sub(operatorBeforeWithdraw).eq(fee)) {
throw new Error("Wrong amount of operator fees after WITHDRAW");
}
if (!onchainBalanceAfterWithdraw.add(pendingToBeOnchainBalance).sub(onchainBalanceBeforeWithdraw).eq(amount)) {
throw new Error("Wrong amount onchain after WITHDRAW");
}

feeInfo.globalFee = feeInfo.globalFee.add(fee);

// Cause we awaited for verifying we must check the collected fees
const operatorBalanceAfter = await getOperatorBalance(token);
if (!operatorBalanceAfter.sub(feeInfo.lastVerifiedOperatorBalance).eq(feeInfo.globalFee)) {
console.log("The collected fee is not right :: expected(", feeInfo.globalFee.toString(), "), found(", operatorBalanceAfter.sub(feeInfo.lastVerifiedOperatorBalance).toString(), ")");
throw new Error("The collected fee is not right");
}
feeInfo.lastVerifiedOperatorBalance = operatorBalanceAfter;
feeInfo.globalFee = BigNumber.from(0);
}

async function testFastWithdraw(
contract: Contract,
withdrawTo: Wallet,
syncWallet: Wallet,
token: types.TokenLike,
amount: BigNumber
amount: BigNumber,
feeInfo: any
) {
const fullFee = await syncProvider.getTransactionFee("FastWithdraw", withdrawTo.address(), token);
const fee = fullFee.totalFee;

const wallet2BeforeWithdraw = await syncWallet.getBalance(token);
const operatorBeforeWithdraw = await getOperatorBalance(token);
const onchainBalanceBeforeWithdraw = await withdrawTo.getEthereumBalance(token);
const startTime = new Date().getTime();
const withdrawHandle = await syncWallet.withdrawFromSyncToEthereum({
Expand All @@ -227,7 +233,6 @@ async function testFastWithdraw(
await promiseTimeout(VERIFY_TIMEOUT, withdrawHandle.awaitVerifyReceipt());
console.log(`Fast withdraw verified: ${new Date().getTime() - startTime} ms`);
const wallet2AfterWithdraw = await syncWallet.getBalance(token);
const operatorAfterWithdraw = await getOperatorBalance(token);
const onchainBalanceAfterWithdraw = await withdrawTo.getEthereumBalance(token);

const tokenId = await withdrawTo.provider.tokenSet.resolveTokenId(token);
Expand All @@ -236,12 +241,20 @@ async function testFastWithdraw(
if (!wallet2BeforeWithdraw.sub(wallet2AfterWithdraw).eq(amount.add(fee))) {
throw new Error("Wrong amount on wallet after fast withdraw");
}
if (!operatorAfterWithdraw.sub(operatorBeforeWithdraw).eq(fee)) {
throw new Error("Wrong amount of operator fees after fast withdraw");
}
if (!onchainBalanceAfterWithdraw.add(pendingToBeOnchainBalance).sub(onchainBalanceBeforeWithdraw).eq(amount)) {
throw new Error("Wrong amount onchain after fast withdraw");
}

feeInfo.globalFee = feeInfo.globalFee.add(fee);

// Cause we awaited for verifying we must check the collected fees
const operatorBalanceAfter = await getOperatorBalance(token);
if (!operatorBalanceAfter.sub(feeInfo.lastVerifiedOperatorBalance).eq(feeInfo.globalFee)) {
console.log("The collected fee is not right :: expected(", feeInfo.globalFee.toString(), "), found(", operatorBalanceAfter.sub(feeInfo.lastVerifiedOperatorBalance).toString(), ")");
throw new Error("The collected fee is not right");
}
feeInfo.lastVerifiedOperatorBalance = operatorBalanceAfter;
feeInfo.globalFee = BigNumber.from(0);
}

async function testChangePubkeyOnchain(syncWallet: Wallet) {
Expand Down Expand Up @@ -314,6 +327,10 @@ async function moveFunds(
token: types.TokenLike,
depositAmountETH: string
) {
const feeInfo = {
lastVerifiedOperatorBalance: await getOperatorBalance(token),
globalFee: BigNumber.from(0)
};
const depositAmount = utils.parseEther(depositAmountETH);

// we do two transfers to test transfer to new and ordinary transfer.
Expand All @@ -326,19 +343,19 @@ async function moveFunds(
console.log(`Forever approved deposit ok, Token: ${token}`);
await testChangePubkeyOnchain(syncWallet1);
console.log(`Change pubkey onchain ok`);
await testTransfer(syncWallet1, syncWallet2, token, transfersAmount);
await testTransfer(syncWallet1, syncWallet2, token, transfersAmount, feeInfo);
console.log(`Transfer to new ok, Token: ${token}`);
await testTransfer(syncWallet1, syncWallet2, token, transfersAmount);
await testTransfer(syncWallet1, syncWallet2, token, transfersAmount, feeInfo);
console.log(`Transfer ok, Token: ${token}`);
await testTransferToSelf(syncWallet1, token, transfersAmount);
await testTransferToSelf(syncWallet1, token, transfersAmount, feeInfo);
console.log(`Transfer to self with fee ok, Token: ${token}`);
await testChangePubkeyOffchain(syncWallet2);
console.log(`Change pubkey offchain ok`);
await testSendingWithWrongSignature(syncWallet1, syncWallet2);
await testWithdraw(contract, syncWallet2, syncWallet2, token, withdrawAmount);
await testWithdraw(contract, syncWallet2, syncWallet2, token, withdrawAmount, feeInfo);
console.log(`Withdraw ok, Token: ${token}`);
// Note that wallet is different from `testWithdraw` to not interfere with previous withdrawal.
await testFastWithdraw(contract, syncWallet1, syncWallet1, token, withdrawAmount);
await testFastWithdraw(contract, syncWallet1, syncWallet1, token, withdrawAmount, feeInfo);
console.log(`Fast withdraw ok, Token: ${token}`);
}

Expand Down Expand Up @@ -412,6 +429,10 @@ async function checkFailedTransactionResending(
syncWallet1: Wallet,
syncWallet2: Wallet
) {
const feeInfo = {
lastVerifiedOperatorBalance: await getOperatorBalance("ETH"),
globalFee: BigNumber.from(0)
};
console.log("Checking invalid transaction resending");
const amount = utils.parseEther("0.2");

Expand All @@ -420,12 +441,17 @@ async function checkFailedTransactionResending(

await testAutoApprovedDeposit(depositWallet, syncWallet1, "ETH", amount.div(2).add(fee));
await testChangePubkeyOnchain(syncWallet1);
let testPassed = true;
try {
await testTransfer(syncWallet1, syncWallet2, "ETH", amount);
await testTransfer(syncWallet1, syncWallet2, "ETH", amount, feeInfo);
testPassed = false;
} catch (e) {
assert(e?.value?.failReason == `Not enough balance`);
console.log("Transfer failed (expected)");
}
if (!testPassed) {
throw new Error("checkFailedTransactionResending failed");
}

await testDeposit(depositWallet, syncWallet1, "ETH", amount.div(2));
// We should wait some `timeoutBeforeReceipt` to give server enough time
Expand All @@ -434,7 +460,7 @@ async function checkFailedTransactionResending(
// If we won't wait enough, then we'll get the receipt for the previous, failed tx,
// which has the same hash. The new (successful) receipt will be available only
// when tx will be executed again in state keeper, so we must wait for it.
await testTransfer(syncWallet1, syncWallet2, "ETH", amount, 3000);
await testTransfer(syncWallet1, syncWallet2, "ETH", amount, feeInfo, 3000);
}

(async () => {
Expand Down Expand Up @@ -489,11 +515,12 @@ async function checkFailedTransactionResending(
const ethWallet5 = ethers.Wallet.createRandom().connect(ethersProvider);
await await ethWallet.sendTransaction({ to: ethWallet5.address, value: utils.parseEther("6.0") });
const syncWallet5 = await Wallet.fromEthSigner(ethWallet5, syncProvider);
await checkFailedTransactionResending(contract, zksyncDepositorWallet, syncWallet4, syncWallet5);

await moveFunds(contract, ethProxy, zksyncDepositorWallet, syncWallet, syncWallet2, ERC20_SYMBOL, "200.0");
await moveFunds(contract, ethProxy, zksyncDepositorWallet, syncWallet, syncWallet3, "ETH", "1.0");

await checkFailedTransactionResending(contract, zksyncDepositorWallet, syncWallet4, syncWallet5);

await syncProvider.disconnect();
} catch (e) {
console.error("Error: ", e);
Expand Down

0 comments on commit 1d13d02

Please sign in to comment.