Skip to content

Commit

Permalink
feat(protocol): use ring buffer for ETH deposit and optimize storage (#…
Browse files Browse the repository at this point in the history
…13868)

Co-authored-by: dantaik <[email protected]>
Co-authored-by: David <[email protected]>
  • Loading branch information
3 people authored Jun 16, 2023
1 parent 2c83f3b commit acffb61
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 135 deletions.
17 changes: 8 additions & 9 deletions packages/eventindexer/contracts/taikol1/TaikoL1.go

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

15 changes: 8 additions & 7 deletions packages/protocol/contracts/L1/TaikoConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ library TaikoConfig {
// Set it to 6M, since its the upper limit of the Alpha-2
// testnet's circuits.
blockMaxGasLimit: 6_000_000,
// Set it to 79 (+1 TaikoL2.anchor transaction = 80),
// and 80 is the upper limit of the Alpha-2 testnet's circuits.
maxTransactionsPerBlock: 79,
minEthDepositsPerBlock: 8,
maxEthDepositsPerBlock: 32,
maxEthDepositAmount: 10_000 ether,
minEthDepositAmount: 1 ether,
// Set it to 120KB, since 128KB is the upper size limit
// of a geth transaction, so using 120KB for the proposed
// transactions list calldata, 8K for the remaining tx fields.
Expand All @@ -38,8 +31,16 @@ library TaikoConfig {
// If block number is N, then only when N % 10 == 0, the real ZKP
// is needed. For mainnet, this must be 0 or 1.
realProofSkipSize: 10,
// Set it to 79 (+1 TaikoL2.anchor transaction = 80),
// and 80 is the upper limit of the Alpha-2 testnet's circuits.
maxTransactionsPerBlock: 79,
ethDepositMinCountPerBlock: 8,
ethDepositMaxCountPerBlock: 32,
ethDepositMaxAmount: 10_000 ether,
ethDepositMinAmount: 1 ether,
ethDepositGas: 21_000,
ethDepositMaxFee: 1 ether / 10,
ethDepositRingBufferSize: 1024,
txListCacheExpiry: 0,
relaySignalRoot: false
});
Expand Down
15 changes: 8 additions & 7 deletions packages/protocol/contracts/L1/TaikoData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ library TaikoData {
uint256 realProofSkipSize;
uint256 ethDepositGas;
uint256 ethDepositMaxFee;
uint64 minEthDepositsPerBlock;
uint64 maxEthDepositsPerBlock;
uint96 maxEthDepositAmount;
uint96 minEthDepositAmount;
uint256 ethDepositRingBufferSize;
uint64 ethDepositMinCountPerBlock;
uint64 ethDepositMaxCountPerBlock;
uint96 ethDepositMaxAmount;
uint96 ethDepositMinAmount;
bool relaySignalRoot;
}

Expand Down Expand Up @@ -108,7 +109,6 @@ library TaikoData {
uint24 size;
}

// 2 slot
struct EthDeposit {
address recipient;
uint96 amount;
Expand All @@ -127,7 +127,8 @@ library TaikoData {
) forkChoiceIds;
mapping(address account => uint256 balance) taikoTokenBalances;
mapping(bytes32 txListHash => TxListInfo) txListInfo;
EthDeposit[] ethDeposits;
mapping(uint256 depositId_mod_ethDepositRingBufferSize => uint256)
ethDeposits;
// Never or rarely changed
// Slot 7: never or rarely changed
uint64 genesisHeight;
Expand All @@ -137,7 +138,7 @@ library TaikoData {
uint64 __reserved72;
// Slot 8
uint64 __reserved80;
uint64 __reserved81;
uint64 numEthDeposits;
uint64 numBlocks;
uint64 nextEthDepositToProcess;
// Slot 9
Expand Down
21 changes: 16 additions & 5 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract TaikoL1 is
uint256[100] private __gap;

receive() external payable {
depositEtherToL2();
depositEtherToL2(address(0));
}

/**
Expand Down Expand Up @@ -143,10 +143,21 @@ contract TaikoL1 is
});
}

function depositEtherToL2() public payable {
LibEthDepositing.depositEtherToL2(
state, getConfig(), AddressResolver(this)
);
function depositEtherToL2(address recipient) public payable {
LibEthDepositing.depositEtherToL2({
state: state,
config: getConfig(),
resolver: AddressResolver(this),
recipient: recipient
});
}

function canDepositEthToL2(uint256 amount) public view returns (bool) {
return LibEthDepositing.canDepositEthToL2({
state: state,
config: getConfig(),
amount: amount
});
}

function getTaikoTokenBalance(address addr) public view returns (uint256) {
Expand Down
174 changes: 107 additions & 67 deletions packages/protocol/contracts/L1/libs/LibEthDepositing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,102 +25,130 @@ library LibEthDepositing {
function depositEtherToL2(
TaikoData.State storage state,
TaikoData.Config memory config,
AddressResolver resolver
AddressResolver resolver,
address recipient
)
internal
{
if (
msg.value < config.minEthDepositAmount
|| msg.value > config.maxEthDepositAmount
) {
if (!canDepositEthToL2(state, config, msg.value)) {
revert L1_INVALID_ETH_DEPOSIT();
}

TaikoData.EthDeposit memory deposit = TaikoData.EthDeposit({
recipient: msg.sender,
amount: uint96(msg.value),
id: uint64(state.ethDeposits.length)
});

address to = resolver.resolve("ether_vault", true);
if (to == address(0)) {
to = resolver.resolve("bridge", false);
}
to.sendEther(msg.value);

state.ethDeposits.push(deposit);
emit EthDeposited(deposit);
// Put the deposit and the end of the queue.
address _recipient = recipient == address(0) ? msg.sender : recipient;
uint256 slot = state.numEthDeposits % config.ethDepositRingBufferSize;
state.ethDeposits[slot] = _encodeEthDeposit(_recipient, msg.value);

emit EthDeposited(
TaikoData.EthDeposit({
recipient: _recipient,
amount: uint96(msg.value),
id: state.numEthDeposits
})
);

unchecked {
state.numEthDeposits++;
}
}

// When ethDepositMaxCountPerBlock is 32, the average gas cost per
// EthDeposit is about 2700 gas. We use 21000 so the proposer
// may earn a small profit if there are 32 deposits included
// in the block; if there are less EthDeposit to process, the
// proposer may suffer a loss so the proposer should simply wait
// for more EthDeposit be become available.
function processDeposits(
TaikoData.State storage state,
TaikoData.Config memory config,
address beneficiary
address feeRecipient
)
internal
returns (TaikoData.EthDeposit[] memory depositsProcessed)
returns (TaikoData.EthDeposit[] memory deposits)
{
// Allocate one extra slot for collecting fees on L2
depositsProcessed = new TaikoData.EthDeposit[](
config.maxEthDepositsPerBlock
);

uint256 j; // number of deposits to process on L2
if (
state.ethDeposits.length
>= state.nextEthDepositToProcess + config.minEthDepositsPerBlock
) {
unchecked {
// When maxEthDepositsPerBlock is 32, the average gas cost per
// EthDeposit is about 2700 gas. We use 21000 so the proposer
// may earn a small profit if there are 32 deposits included
// in the block; if there are less EthDeposit to process, the
// proposer may suffer a loss so the proposer should simply wait
// for more EthDeposit be become available.
uint96 feePerDeposit = uint96(
config.ethDepositMaxFee.min(
block.basefee * config.ethDepositGas
)
);
uint96 totalFee;
uint64 i = state.nextEthDepositToProcess;
while (
i < state.ethDeposits.length
&& state.nextEthDepositToProcess
+ config.maxEthDepositsPerBlock > i
) {
depositsProcessed[j] = state.ethDeposits[i];

if (depositsProcessed[j].amount > feePerDeposit) {
totalFee += feePerDeposit;
depositsProcessed[j].amount -= feePerDeposit;
} else {
totalFee += depositsProcessed[j].amount;
depositsProcessed[j].amount = 0;
}

uint256 numPending =
state.numEthDeposits - state.nextEthDepositToProcess;
if (numPending < config.ethDepositMinCountPerBlock) {
deposits = new TaikoData.EthDeposit[](0);
} else {
deposits = new TaikoData.EthDeposit[](
numPending.min(config.ethDepositMaxCountPerBlock)
);

uint96 fee = uint96(
config.ethDepositMaxFee.min(
block.basefee * config.ethDepositGas
)
);
uint64 j = state.nextEthDepositToProcess;
uint96 totalFee;
for (uint256 i; i < deposits.length;) {
uint256 data =
state.ethDeposits[j % config.ethDepositRingBufferSize];

deposits[i] = TaikoData.EthDeposit({
recipient: address(uint160(data >> 96)),
amount: uint96(data), // works
id: j
});

uint96 _fee =
deposits[i].amount > fee ? fee : deposits[i].amount;

unchecked {
deposits[i].amount -= _fee;
totalFee += _fee;
++i;
++j;
}
}
state.nextEthDepositToProcess = j;

// Fee collecting deposit
if (totalFee > 0) {
TaikoData.EthDeposit memory deposit = TaikoData.EthDeposit({
recipient: beneficiary,
amount: totalFee,
id: uint64(state.ethDeposits.length)
});
// This is the fee deposit
state.ethDeposits[state.numEthDeposits
% config.ethDepositRingBufferSize] =
_encodeEthDeposit(feeRecipient, totalFee);

state.ethDeposits.push(deposit);
}
// Advance cursor
state.nextEthDepositToProcess = i;
unchecked {
state.numEthDeposits++;
}
}
}

assembly {
mstore(depositsProcessed, j)
function canDepositEthToL2(
TaikoData.State storage state,
TaikoData.Config memory config,
uint256 amount
)
internal
view
returns (bool)
{
if (
amount < config.ethDepositMinAmount
|| amount > config.ethDepositMaxAmount
) {
return false;
}

unchecked {
uint256 numPending =
state.numEthDeposits - state.nextEthDepositToProcess;

// We need to make sure we always reverve one slot for the fee
// deposit
if (numPending >= config.ethDepositRingBufferSize - 1) {
return false;
}
}

return true;
}

function hashEthDeposits(TaikoData.EthDeposit[] memory deposits)
Expand All @@ -130,4 +158,16 @@ library LibEthDepositing {
{
return keccak256(abi.encode(deposits));
}

function _encodeEthDeposit(
address addr,
uint256 amount
)
private
pure
returns (uint256)
{
if (amount >= type(uint96).max) revert L1_INVALID_ETH_DEPOSIT();
return uint256(uint160(addr)) << 96 | amount;
}
}
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/libs/LibUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ library LibUtils {
numBlocks: state.numBlocks,
lastVerifiedBlockId: state.lastVerifiedBlockId,
nextEthDepositToProcess: state.nextEthDepositToProcess,
numEthDeposits: uint64(state.ethDeposits.length)
numEthDeposits: state.numEthDeposits - state.nextEthDepositToProcess
});
}

Expand Down
16 changes: 10 additions & 6 deletions packages/protocol/contracts/L1/libs/LibVerifying.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@ library LibVerifying {
|| config.blockMaxGasLimit == 0
|| config.maxTransactionsPerBlock == 0
|| config.maxBytesPerTxList == 0
|| config.txListCacheExpiry > 30 * 24 hours
// EIP-4844 blob size up to 128K
|| config.maxBytesPerTxList > 128 * 1024
|| config.maxEthDepositsPerBlock == 0
|| config.maxEthDepositsPerBlock < config.minEthDepositsPerBlock
// EIP-4844 blob deleted after 30 days
|| config.txListCacheExpiry > 30 * 24 hours
|| config.ethDepositGas == 0 //
|| config.ethDepositMinCountPerBlock == 0
|| config.ethDepositMaxCountPerBlock
< config.ethDepositMinCountPerBlock || config.ethDepositGas == 0 //
|| config.ethDepositMinAmount == 0
|| config.ethDepositMaxAmount <= config.ethDepositMinAmount
|| config.ethDepositMaxAmount >= type(uint96).max
|| config.ethDepositMaxFee == 0
|| config.ethDepositMaxFee >= type(uint96).max
|| config.ethDepositMaxFee
>= type(uint96).max / config.ethDepositMaxCountPerBlock
|| config.ethDepositRingBufferSize <= 1
) revert L1_INVALID_CONFIG();

uint64 timeNow = uint64(block.timestamp);
Expand Down
Loading

0 comments on commit acffb61

Please sign in to comment.