Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atomic Swap app #110

Merged
merged 5 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ Every time a user opens MusicDAO, they are asked to reload the page in order to
The feature-based models are gossiped along random walks through the network. At each peer they are merged and re-trained on peer's local data. The matrix factorization model seeks to learn a factorization of the user-song matrix. This means that one of the two factors contains only information on how users generally rate each song. This matrix can then be gossiped around the network while a user's personal vector as well as their listening history are kept private.
- [More about federated machine learning using gossiping for music recommendations](gossipML/README.md)

### Atomic Swap

AtomicSwap app allows two users to exchange different cryptocurrencies without the involvement of a third party and without having to trust each other. This is achieved by implementing the Atomic Swap protocol.

User can create trade offers by sending Ipv8 messages indicating that they wish to trade to the Swap community. Others can then accept the offer by sending another Ipv8 message. The swap procedure starts when the initiator receives an accept message for their trade offer.

Below is a video demo that shows the steps to do an atomic swap.

<a href="https://user-images.githubusercontent.com/21971137/164297537-e8b4ff5f-a999-4e6d-b1e8-17135399848e.mp4" title="Swap Demo"><img src="https://user-images.githubusercontent.com/21971137/164298818-a152b7ca-6ebe-4038-a449-e6a246c7f1ab.png" alt="Alternate Text" /></a>

[More about The Atomic Swap app](atomic-swap/README.md)

### Do you want to add your own app?

- [Adding your own app to the TrustChain Super App](doc/AppTutorial.md)
Expand Down
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ android {
exclude 'META-INF/kotlinx-coroutines-core.kotlin_module'
exclude 'META-INF/DEPENDENCIES'
}
ndkVersion '21.1.6352462'
}

repositories {
Expand Down Expand Up @@ -123,9 +124,13 @@ dependencies {
implementation project(':ig-ssi')
implementation project(':liquidity-pool')
implementation project(':valuetransfer')
implementation project(':atomic-swap')
api(project(':common')){
exclude group: 'net.java.dev.jna'
}
api(project(':common-ethereum')){
exclude group: 'net.java.dev.jna'
}
api(project(':currencyii')){
exclude group: 'net.java.dev.jna'
}
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@
android:name="nl.tudelft.trustchain.liquidity.LiquidityPoolMainActivity"
android:parentActivityName=".ui.dashboard.DashboardActivity" />

<activity
android:name="nl.tudelft.trustchain.atomicswap.AtomicSwapActivity"
android:parentActivityName=".ui.dashboard.DashboardActivity" />

<activity
android:name="nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity"
android:parentActivityName=".ui.dashboard.DashboardActivity"
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/nl/tudelft/trustchain/app/AppDefinition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import com.example.musicdao.MusicService
import nl.tudelft.trustchain.FOC.MainActivityFOC
import nl.tudelft.trustchain.atomicswap.AtomicSwapActivity
import nl.tudelft.trustchain.common.R
import nl.tudelft.trustchain.explorer.ui.TrustChainExplorerActivity
import nl.tudelft.trustchain.currencyii.CurrencyIIMainActivity
Expand Down Expand Up @@ -111,5 +112,11 @@ enum class AppDefinition(
R.color.colorPrimaryValueTransfer,
ValueTransferMainActivity::class.java,
true,
),
ATOMIC_SWAP(
R.drawable.ic_atomic_swap_24dp,
"Atomic Swap",
R.color.blue,
AtomicSwapActivity::class.java
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import nl.tudelft.ipv8.sqldelight.Database
import nl.tudelft.ipv8.util.hexToBytes
import nl.tudelft.ipv8.util.toHex
import nl.tudelft.trustchain.app.service.TrustChainService
import nl.tudelft.trustchain.atomicswap.AtomicSwapCommunity
import nl.tudelft.trustchain.atomicswap.AtomicSwapTrustchainConstants
import nl.tudelft.trustchain.atomicswap.ui.swap.LOG
import nl.tudelft.trustchain.common.DemoCommunity
import nl.tudelft.trustchain.common.MarketCommunity
import nl.tudelft.trustchain.common.bitcoin.WalletService
Expand Down Expand Up @@ -79,6 +82,7 @@ class TrustChainApplication : Application() {
createTFTPCommunity(),
createDemoCommunity(),
createWalletCommunity(),
createAtomicSwapCommunity(),
createMarketCommunity(),
createCoinCommunity(),
createVotingCommunity(),
Expand Down Expand Up @@ -179,6 +183,52 @@ class TrustChainApplication : Application() {
}
}
)

trustchain.registerTransactionValidator(
AtomicSwapTrustchainConstants.ATOMIC_SWAP_COMPLETED_BLOCK,
object : TransactionValidator {
override fun validate(
block: TrustChainBlock,
database: TrustChainStore
): ValidationResult {
if ((
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_FROM_COIN] != null &&
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_TO_COIN] != null &&
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_FROM_AMOUNT] != null &&
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_TO_AMOUNT] != null &&
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_OFFER_ID] != null
) ||
block.isAgreement
) {
return ValidationResult.Valid
} else {
return ValidationResult.Invalid(listOf("Proposal invalid"))
}
}
}
)

trustchain.registerBlockSigner(
AtomicSwapTrustchainConstants.ATOMIC_SWAP_COMPLETED_BLOCK,
object : BlockSigner {
override fun onSignatureRequest(block: TrustChainBlock) {
trustchain.createAgreementBlock(block, mapOf<Any?, Any?>())
Log.d(LOG, "Bob created a trustchain agreement block")
}
}
)

trustchain.addListener(
AtomicSwapTrustchainConstants.ATOMIC_SWAP_COMPLETED_BLOCK,
object : BlockListener {
override fun onBlockReceived(block: TrustChainBlock) {
Log.d(
"AtomicSwap",
"onBlockReceived: ${block.blockId} ${block.transaction}"
)
}
}
)
}

private fun createWalletCommunity(): OverlayConfiguration<AttestationCommunity> {
Expand All @@ -193,6 +243,14 @@ class TrustChainApplication : Application() {
)
}

private fun createAtomicSwapCommunity(): OverlayConfiguration<AtomicSwapCommunity> {
val randomWalk = RandomWalk.Factory()
return OverlayConfiguration(
Overlay.Factory(AtomicSwapCommunity::class.java),
listOf(randomWalk)
)
}

private fun createDiscoveryCommunity(): OverlayConfiguration<DiscoveryCommunity> {
val randomWalk = RandomWalk.Factory()
val randomChurn = RandomChurn.Factory()
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
<domain includeSubdomains="true">134.122.59.107</domain>
<!-- This is for RegTest faucet, to claim some starter money -->
<domain includeSubdomains="true">131.180.27.224</domain>
<!-- IP address of the host machine when ran from the Android emulator -->
<!-- <domain includeSubdomains="true">10.0.2.2</domain>-->
</domain-config>
</network-security-config>
124 changes: 124 additions & 0 deletions atomic-swap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Atomic Swap app

This app allows for the trading of Ethereum and Bitcoin through atomic swaps.

## Our approach

The Atomic Swap Protocol
Our app currently supports the following atomic swaps:
- Bitcoin <-> Bitcoin
- Ethereum <-> Bitcoin

We achieve atomic swaps by using hash timelocks.

The protocol we use consists of 4 messages; broadcast, accept, initiate and complete. Say we have two users, Alice and Bob. Alice swapping with Bob would look like this:
1. First Alice broadcasts a message indicating the details of the trade she wants to do.

2. Bob receives this message and sends an accept message containing his Bitcoin and Ethereum addresses.

3. Alice receives the accept message,generates a secret and locks funds for Bob in a hash timelock. The funds can be claimed by Bob with the secret. Afterwards, Alice sends an initiate message to Bob containing the transaction or transaction hash of Alice locking funds , the hash of the secret and Alice’s addresses.

4. Bob receives the initiate message and locks funds for Alice in a hash timelock. The funds can be claimed by Alice with the secret or reclaimed by Bob after some time. Bob also starts watching the blockchain for when Alice claims the funds that Bob locked. When Alice claims the funds, the secret is revealed and Bob can claim his funds. Afterwards, Bob sends Alice a complete message containing the transaction or transaction hash of the transaction where Bob locked the funds.

5. Alice receives the complete message and claims the funds Bob locked. Bob realizes this and then claims the funds Alice locked.

<img src="https://user-images.githubusercontent.com/21971137/164295370-bd3a8cdb-21e2-4773-89ee-dbbb038221cc.png" width="280">

### Bitcoin specifics

We use [bitcoinj](https://bitcoinj.org/) to interact with the Bitcoin network.

On the Bitcoin side, the hash timelock is achieved using a single Bitcoin script that contains the logic for either claiming or reclaiming.
When Bitcoin is being swapped and we lock funds in a hash timelock, we send the entire transaction to the counterparty. This is due to the fact that bitcoinj, does not support retrieving transactions by hash if we were not already watching an address involved in that transaction.

### Ethereum specifics

We use [web3j](https://github.com/web3j/web3j) to interact with the Ethereum network.
On the Ethereum side, the hash timelock is achieved using a solidity smart contract.
The contract can be found at `src\main\java\nl\tudelft\trustchain\atomicswap\swap\eth\swap_contract.sol`

### IPv8

We use IPv8 for communication and sending/receiving messages. We have created the AtomicSwap Community for this purpose, which implements callbacks for all the message types.

### TrustChain

Currently, we use TrustChain to store completed transactions between two users. The blocks will have the ATOMIC_SWAP_COMPLETED_BLOCK type, and each block contains the following information about the transaction: offer id, the source and destination coin, the sent and received amount of currency.

## Challenges

- Jvm cryptocurrency libraries are not as well maintained as Javascript libraries. We had some problems getting everything to work with Bitcoinj and Web3j.
- The superapp has many logs, so trying to debug our part was hard in the beginning. Additionally, sometimes the logs would not show up.
- Testing and debugging the code involves running the application on two devices and this setup therefore consumes much time.

## Future work

- Display more detailed status of your trade - maybe be able to expand the trade offer item, and view what is the current progress; we are already doing this in the logs, but not UI.
- TrustScore - crawl the chain of the user and calculate a score based on how many successful transactions that user has; this will be the trust/reliability score for that user.
- Persist swaps to memory to continue after the app is closed.
- Handle different kinds of exceptions e.g. when a user does not have enough money for the swap.
- Implement the reclaiming of the money if something does not go as expected. Currently the money will stay locked and the user has no way of retrieving it.

## Usage instructions

### Preparing the coin nodes

In order for the app to work, it has to be able to connect with Bitcoin and Ethereum (geth) nodes.
These can be deployed with the included Docker Compose file in the `docker` directory in the
project’s root by [following the instructions](../docker/README.md).

Once the nodes are deployed, the addresses have to be configured in the following build config
variables:

* `BITCOIN_DEFAULT_PEER` in the common module’s `build.gradle` file - IP address of the machine with
the Bitcoin node
* `ETH_HTTP_URL` in the common-ethereum module’s `build.gradle` file - URL of the node, complete
with the protocol (HTTP or HTTPS) and the port. In case the HTTPS protocol is used, the
certificate has to be signed by the trusted certificate authority (eg. Let’s Encrypt).

In case the Trustchain app is running on Android emulator, developer can launch Docker Compose
services locally and configure the aforementioned variables to point at the 10.0.2.2 address, on
which the host system is accessible.

Additionally, the IP address(es) have to be added to the `network_security_config.xml`
of the main application module, in order to allow the Trustchain’s application to communicate with
the server.

### Deploying Ethereum Contract

In order for the atomic swaps involving Ethereum to work, the Ethereum contract responsible for this
needs to be deployed. One way this can be done is to use [Remix](https://remix.ethereum.org/) and
copy, add the smart contract file to remix and deploy it by following
this [guide](https://remix-ide.readthedocs.io/en/latest/run.html).

After deploying the contract the build config variables need to be changed in the `build.gradle` of
the atomic swap module:
`ETH_SWAP_CONTRACT` should be changed to the contract address.
`ETH_CHAIN_ID` should be changed to the chain id of the network the contract was deployed at.

### Process of making a swap

A user creates a swap offer in the swap tab and broadcasts it to all users using ipv8.

<img src="https://user-images.githubusercontent.com/21971137/164291922-959cd2b8-a848-4f10-a4bb-4cadf8fbe617.png" width="280">


A user sees all available swap offers in the trade offers tab and can start a swap by clicking on the accept button of the desired swap offer.

<img src="https://user-images.githubusercontent.com/21971137/164292243-63417f62-8cd3-4758-ac29-1cad77ee05f3.png" width="280">


While the atomic swap is in progress, the status of the swap in the swap offers tab will be in progress and when the swap finishes, the status will change to completed.

<img src="https://user-images.githubusercontent.com/21971137/164292369-2191bfd2-5036-4e01-8b68-da0d444f802a.png" width="280"><img src="https://user-images.githubusercontent.com/21971137/164292663-d2b20d5c-c594-4f7e-9375-702bf8350e35.png" width="280">

The balance in the users wallets will change accordingly.

<img src="https://user-images.githubusercontent.com/21971137/164292774-640abb61-cd25-4b26-8a7f-8a9f6c800332.png" width="280"><img src="https://user-images.githubusercontent.com/21971137/164292789-1d064394-87a7-4c62-a22c-602c55128be3.png" width="280">

## Problems

- Currently, there is a random bug where IPV8 fails to decode messages correctly. Because of this
the swap may not work everytime.
- We created some tests, but we removed them since adding them somehow made the IPV8 decode problem
worse.
71 changes: 71 additions & 0 deletions atomic-swap/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android-extensions'

}

android {
compileSdkVersion 31
buildToolsVersion "29.0.3"

defaultConfig {
minSdkVersion 22
targetSdkVersion 31

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"

buildConfigField "String", "ETH_SWAP_CONTRACT", "\"0x8790bca86ee29530a17a9bf80bd9e6e3503ff4d1\""
buildConfigField "Long", "ETH_CHAIN_ID", "1337L"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
allWarningsAsErrors = true
}
buildFeatures {
viewBinding true
}
testOptions {
unitTests.returnDefaultValues = true
}
}

repositories {
flatDir {
dirs '../common/libs'
}
}

dependencies {
implementation project(":common")
implementation project(":common-bitcoin")
implementation project(":common-ethereum")
implementation project(":ipv8-android")

// TODO: Newer than the rest of apps - should we leave or downgrade
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'

implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'androidx.preference:preference-ktx:1.1.0'

implementation 'com.github.MattSkala:recyclerview-itemadapter:0.4'

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Empty file added atomic-swap/consumer-rules.pro
Empty file.
21 changes: 21 additions & 0 deletions atomic-swap/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Loading