Skip to content

Instantly share code, notes, and snippets.

@yrong
Created February 28, 2021 10:21
Show Gist options
  • Save yrong/eba777fd85c9d38e5b47711e92c9011d to your computer and use it in GitHub Desktop.
Save yrong/eba777fd85c9d38e5b47711e92c9011d to your computer and use it in GitHub Desktop.
Darwinia Bridge
# 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