Created
February 28, 2021 10:21
-
-
Save yrong/eba777fd85c9d38e5b47711e92c9011d to your computer and use it in GitHub Desktop.
Darwinia Bridge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Theory | |
[White Paper](https://darwinia.network/ChainRelay_Technical_Paper(Preview)_EN.pdf) | |
[Darwinia-Ethereum Bridge](https://docs.google.com/document/d/1NVDSk6KZXV5CjE20cNPA8Swmmd89IYeWyN0T9lRBOEM) | |
# Projects Navigation | |
## [Darwinia Bridge SPV Module](https://github.com/darwinia-network/darwinia-common/tree/master/frame/bridge) | |
Cross-Chain State Verification | |
## [Bridger](https://github.com/darwinia-network/bridger) | |
Relayers (aka. Bridgers) in Darwinia Network are offchain worker clients which help relay the headers and messages between source chains and target chains | |
## [Shadow](https://github.com/darwinia-network/shadow) | |
ZK proof services for bridger which retrieve header data and generate proof | |
## Smart Contracts | |
### Contract Source | |
[issuing](https://github.com/evolutionlandorg/common-contracts/blob/master/contracts/Issuing.sol) | |
[relay](https://github.com/darwinia-network/darwinia-bridge-on-ethereum/blob/master/contracts/Relay.sol) | |
[bank](https://github.com/evolutionlandorg/bank/blob/master/contracts/GringottsBankV2.sol) | |
### [Contract Address](https://github.com/evolutionlandorg/contract-address) | |
``` | |
contract: | |
ring: | |
# erc20 contract of ring | |
address: 0x9469d013805bffb7d3debe5e7839237e535ec483 | |
topics: | |
# cross transfer topic | |
- 0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10 | |
kton: | |
# erc20 contract of kton | |
address: 0x9f284e1337a815fe77d2ff4ae46544645b20c5ff | |
topics: | |
# cross transfer topic | |
- 0xc9dcda609937876978d7e0aa29857cb187aea06ad9e843fd23fd32108da73f10 | |
bank: | |
# gringotts bank contract | |
address: 0x649fdf6ee483a96e020b889571e93700fbd82d88 | |
topics: | |
# cross transfer topic | |
- 0xe77bf2fa8a25e63c1e5e29e1b2fcb6586d673931e020c4e3ffede453b830fb12 | |
issuing: | |
# erc20 ring and kton issuing contract | |
address: 0xea7938985898af7fd945b03b7bc2e405e744e913 | |
relay: | |
# ethereum relay contract for darwinia to ethereum | |
address: 0x5cde5Aafeb8E06Ce9e4F94c2406d3B6CB7098E49 | |
topics: | |
# set authorities topic | |
- 0x91d6d149c7e5354d1c671fe15a5a3332c47a38e15e8ac0339b24af3c1090690f | |
``` | |
## [Wormhole](https://github.com/darwinia-network/wormhole) | |
CrossChain UI | |
# Key Codes Analysis | |
## d2e cross chain | |
### wormhole | |
* some balance/fee check | |
``` | |
const balances = await getTokenBalance(networkType, this.state.account[networkType]); | |
const isAllowance = await checkIssuingAllowance(this.state.account[networkType]); | |
const crossChainFee = await getEthereumToDarwiniaCrossChainFee(); | |
``` | |
* invoke token contract | |
``` | |
function redeemTokenEth(account, params, callback) { | |
let web3js = new Web3(window.ethereum || window.web3.currentProvider); | |
const contract = new web3js.eth.Contract(TokenABI, config[`${params.tokenType.toUpperCase()}_ETH_ADDRESS`]); | |
contract.methods.transferFrom(account, config['ETHEREUM_DARWINIA_ISSUING'], params.value, params.toHex).send({ from: account }).on('transactionHash', (hash) => { | |
callback && callback(hash) | |
}) | |
} | |
``` | |
* token contract forward to issuing contract which burn erc20 token and send BurnAndRedeem event | |
``` | |
/** | |
* @dev ERC223 fallback function, make sure to check the msg.sender is from target token contracts | |
* @param _from - person who transfer token in for deposits or claim deposit with penalty KTON. | |
* @param _amount - amount of token. | |
* @param _data - data which indicate the operations. | |
*/ | |
function tokenFallback( | |
address _from, | |
uint256 _amount, | |
bytes _data | |
) public whenNotPaused { | |
... | |
IBurnableERC20(msg.sender).burn(address(this), _amount); | |
emit BurnAndRedeem(msg.sender, _from, _amount, _data); | |
} | |
``` | |
### shadow | |
* add ethereum header info to mmr | |
``` | |
fn push(&mut self, elem: &[u8; 32]) -> Result<u64> { | |
let mut conn = self.db.get_conn()?; | |
let mut tx = conn.start_transaction(TxOpts::default())?; | |
// push elem | |
let leaf_count = self.get_leaf_count()?; | |
let mut batch: Vec<(Position, Hash, IsLeaf)> = vec![]; | |
let store = MysqlStore::new(self.db.clone(), &mut tx, &mut batch); | |
let mut mmr = MMR::<[u8; 32], MergeHash, _>::new(self.get_mmr_size()?, store); | |
//let elem = H256::from(elem)?; | |
let position = mmr.push(*elem)?; | |
mmr.commit()?; | |
if !batch.is_empty() { | |
let mut leaf_index = leaf_count; | |
let items: Vec<String> = batch.iter().map(|(pos, hash, is_leaf)| { | |
if *is_leaf { | |
let item = format!("({}, '{}', {}, {})", pos, hash, is_leaf, leaf_index); | |
leaf_index += 1; | |
item | |
} else { | |
format!("({}, '{}', {}, NULL)", pos, hash, is_leaf) | |
} | |
}).collect(); | |
let sql = format!("INSERT INTO mmr (position, hash, leaf, leaf_index) VALUES {}", items.join(",")); | |
tx.query_drop(sql)?; | |
} | |
tx.commit()?; | |
Ok(position) | |
} | |
``` | |
* generate proof from mmr | |
``` | |
fn gen_proof(&self, member: u64, last_leaf: u64) -> Result<Vec<String>> { | |
let mut conn = self.db.get_conn()?; | |
let mut tx = conn.start_transaction(TxOpts::default())?; | |
let mut batch: Vec<(Position, Hash, IsLeaf)> = vec![]; | |
let store = MysqlStore::new(self.db.clone(), &mut tx, &mut batch); | |
let mmr_size = cmmr::leaf_index_to_mmr_size(last_leaf); | |
let mmr = MMR::<[u8; 32], MergeHash, _>::new(mmr_size, store); | |
let proof = mmr.gen_proof(vec![cmmr::leaf_index_to_pos(member)])?; | |
tx.commit()?; | |
Ok( | |
proof | |
.proof_items() | |
.iter() | |
.map(|item| H256::hex(item)) | |
.collect::<Vec<String>>() | |
) | |
} | |
``` | |
### bridger | |
* scan ethereum block txs coming from token/relay and other subscribed contracts | |
``` | |
if l.topics.contains(&contracts.ring) || l.topics.contains(&contracts.kton) | |
{ | |
EthereumTransaction { | |
tx_hash: EthereumTransactionHash::Token( | |
l.transaction_hash.unwrap_or_default(), | |
), | |
block_hash: l.block_hash.unwrap_or_default(), | |
block, | |
index, | |
} | |
} | |
... | |
``` | |
* send affirm extrinsic which contains parcel(including block header hash from ethereum and mmr root from shadow) | |
``` | |
let parcel = shadow.parcel(target as usize + 1).await.with_context(|| { | |
format!( | |
"Fail to get parcel from shadow when affirming ethereum block {}", | |
target | |
) | |
})?; | |
if parcel.header == EthereumHeader::default() || parcel.mmr_root == [0u8; 32] { | |
return Err(BizError::ParcelFromShadowIsEmpty(target).into()); | |
} | |
let ex = Extrinsic::Affirm(parcel); | |
let msg = MsgExtrinsic(ex); | |
extrinsics_service.send(msg).await?; | |
``` | |
* send redeem extrinsic which contains receipt proof(including Merkle Patricia Trie proof from ethereum and mmr proof from shadow) | |
``` | |
let proof = shadow | |
.receipt(&format!("{:?}", tx.enclosed_hash()), last_confirmed) | |
.await?; | |
let redeem_for = match tx.tx_hash { | |
EthereumTransactionHash::Deposit(_) => RedeemFor::Deposit, | |
EthereumTransactionHash::Token(_) => RedeemFor::Token, | |
EthereumTransactionHash::SetAuthorities(_) => RedeemFor::SetAuthorities, | |
}; | |
let ex = Extrinsic::Redeem(redeem_for, proof, tx); | |
let msg = MsgExtrinsic(ex); | |
extrinsics_service.send(msg).await?; | |
``` | |
### darwinia bridge | |
#### [affirm](https://github.com/darwinia-network/darwinia-common/blob/master/frame/bridge/ethereum/relay) | |
``` | |
// TODO: weight | |
#[weight = 0] | |
pub fn affirm( | |
origin, | |
ethereum_relay_header_parcel: EthereumRelayHeaderParcel, | |
optional_ethereum_relay_proofs: Option<EthereumRelayProofs> | |
) { | |
let relayer = ensure_signed(origin)?; | |
let game_id = T::RelayerGame::affirm( | |
&relayer, | |
ethereum_relay_header_parcel, | |
optional_ethereum_relay_proofs | |
)?; | |
Self::deposit_event(RawEvent::Affirmed( | |
relayer, | |
RelayAffirmationId { game_id, round: 0, index: 0 } | |
)); | |
``` | |
* start relay game | |
``` | |
<Affirmations<T, I>>::append(&game_id, 0, relay_affirmation); | |
<BestConfirmedHeaderId<T, I>>::insert(&game_id, best_confirmed_relay_header_id); | |
<RoundCounts<T, I>>::insert(&game_id, 1); | |
<RelayHeaderParcelToResolve<T, I>>::mutate(|relay_header_parcel_to_resolve| { | |
relay_header_parcel_to_resolve.push(game_id.clone()) | |
}); | |
<GameSamplePoints<T, I>>::append(&game_id, vec![game_id.clone()]); | |
Self::update_timer_of_game_at(&game_id, 0, now); | |
Ok(game_id) | |
} | |
``` | |
* [update game](https://github.com/darwinia-network/darwinia-common/blob/master/frame/bridge/relayer-game/README.md) when block finalized | |
``` | |
fn on_finalize(now: BlockNumber<T>) { | |
let game_ids = <GamesToUpdate<T, I>>::take(now); | |
if !game_ids.is_empty() { | |
if let Err(e) = Self::update_games_at(game_ids, now) { | |
error!(target: "relayer-game", "{:?}", e); | |
} | |
} | |
// Return while no closed rounds found | |
} | |
``` | |
* game over | |
``` | |
pub fn game_over(game_id: RelayHeaderId<T, I>) { | |
// TODO: error trace | |
let _ = <RelayHeaderParcelToResolve<T, I>>::try_mutate(|relay_header_parcel_to_resolve| { | |
if let Some(i) = relay_header_parcel_to_resolve | |
.iter() | |
.position(|block_id| block_id == &game_id) | |
{ | |
relay_header_parcel_to_resolve.remove(i); | |
Ok(()) | |
} else { | |
Err(()) | |
} | |
}); | |
<Affirmations<T, I>>::remove_prefix(&game_id); | |
<BestConfirmedHeaderId<T, I>>::take(&game_id); | |
<RoundCounts<T, I>>::take(&game_id); | |
<AffirmTime<T, I>>::take(&game_id); | |
<GameSamplePoints<T, I>>::take(&game_id); | |
T::RelayableChain::game_over(&game_id); | |
} | |
``` | |
* confirm relay parcel | |
``` | |
pub fn update_confirmeds_with_reason( | |
relay_header_parcel: EthereumRelayHeaderParcel, | |
reason: Vec<u8>, | |
) { | |
let relay_block_number = relay_header_parcel.header.number; | |
ConfirmedBlockNumbers::mutate(|confirmed_block_numbers| { | |
// TODO: remove old numbers according to `ConfirmedDepth` | |
confirmed_block_numbers.push(relay_block_number); | |
BestConfirmedBlockNumber::put(relay_block_number); | |
}); | |
ConfirmedHeaderParcels::insert(relay_block_number, relay_header_parcel); | |
Self::deposit_event(RawEvent::PendingRelayHeaderParcelConfirmed( | |
relay_block_number, | |
reason, | |
)); | |
} | |
``` | |
#### [redeem](https://github.com/darwinia-network/darwinia-common/tree/master/frame/bridge/ethereum/backing) | |
``` | |
/// Redeem balances | |
/// | |
/// # <weight> | |
/// - `O(1)` | |
/// # </weight> | |
#[weight = 10_000_000] | |
pub fn redeem(origin, act: RedeemFor, proof: EthereumReceiptProofThing<T>) { | |
let redeemer = ensure_signed(origin)?; | |
if RedeemStatus::get() { | |
match act { | |
RedeemFor::Token => Self::redeem_token(&redeemer, &proof)?, | |
RedeemFor::Deposit => Self::redeem_deposit(&redeemer, &proof)?, | |
} | |
} else { | |
Err(<Error<T>>::RedeemDis)?; | |
} | |
} | |
``` | |
* finish the transfer and add to Verified Proof Map | |
``` | |
C::transfer( | |
&Self::account_id(), | |
&darwinia_account, | |
redeem_amount, | |
KeepAlive, | |
)?; | |
// // Transfer the fee from redeemer. | |
// T::RingCurrency::transfer(redeemer, &T::EthereumRelay::account_id(), fee, KeepAlive)?; | |
VerifiedProof::insert(tx_index, true); | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment