Code Monkey home page Code Monkey logo

dogethereum-contracts's Introduction

Dogethereum Contracts

Build Status

Ethereum contracts for the Dogecoin <-> Ethereum bridge.

If you are new to the Dogecoin <-> Ethereum bridge, please check the docs repository first.

Core components

  • DogeSuperblocks contract
    • Keeps a copy of the Dogecoin Superblockchain
    • Informs DogeToken contract when a Dogecoin transaction locked or unlocked funds.
    • It's kind of a Doge version of BtcRelay but using Superblocks instead of blocks.
  • DogeToken contract
    • An ERC20 contract where 1 token is worth 1 Dogecoin.
    • Tokens are minted when coins are locked on the Dogecoin blockchain.
    • Tokens are destroyed when coins should go back to the Dogecoin blockchain.
  • SuperblockClaims contract
    • Manages the interactive (challenge/response) validation of Superblocks.
    • Inspired on Truebit's Scrypt interactive ScryptClaims
  • DogeMessageLibrary
    • Library for parsing/working with Dogecoin blocks, txs and merkle trees

Prerequisites

To build, deploy or run tests on these contracts you need to install the following:

Installing

To run tests or deployment you need to install the root package dependencies. To do so:

  • cd to the directory where the repo is cloned.
  • Execute npm install.

Running the Tests

There are two kinds of tests:

Some contracts are copies from the scrypt-interactive repository. These can be found in contracts/scrypt-interactive. You may need to update them to test with the latest version.

Contract tests

These are unit tests for all contract funcionality.

Just run npm test to run all contract tests.

Integration tests

These tests setup an environment where the contracts are deployed and interacted with by the dogethereum tools and the dogethereum agents.

First, you need to set a few environment variables:

  • agentRootDir: path to the root of the dogethereum agents repository.
  • agentDataDir: path to the data directory of the dogethereum agents. This is where the agents store a database of the dogecoin and ethereum networks. Specifically, this is the data.directory key in the agent configuration.
  • agentConfig: path to the agents config file.
  • toolsRootDir: path to the root of the dogethereum tools repository.
  • scryptInteractiveDir: path to the root of the scrypt-interactive repository.

To set them, run

$ export agentRootDir=/your/agent/path
$ export agentDataDir=/your/agent/path/to/data/dir
$ export agentConfig=/your/agent/config/path/dogethereum-agents.conf
$ export toolsRootDir=/your/tools/path
$ export scryptInteractiveDir=/your/path/to/scrypt-interactive

Then run npm run integration-tests. Note that doing this will launch a dogecoin node in regtest mode with a graphical interface.

The integration tests will use ganache-cli by default but hardhat network can be used instead by setting:

$ USE_HH_NETWORK=true

At one point, the test will require the manual launch of the dogethereum agents.

Scrypt checker integration

Tests involving the scrypt checker monitor require an additional environment variable:

$ export scryptInteractiveDir=/your/scrypt-interactive/path

Manual testing

Additionally, there are a few dogethereum commands that facilitate performing specific operations with the contracts. These are implemented as Hardhat tasks so you can enumerate them with npx hardhat --help. To see further options you can invoke the help for that particular task. E.g. for the dogethereum.challenge task you can invoke npx hardhat dogethereum.challenge --help.

$ npx hardhat --network NETWORK COMMAND [OPTIONS]

Where COMMAND

  • dogethereum.challenge: Start a challenge to a superblock

    Available OPTIONS are:

    • --from ADDRESS: Address used to send the challenge from. When not specified it will use the first account available in the runtime environment.

    • --superblock SUPERBLOCK_ID: Superblock ID to challenge. When none is specified it will challenge the next superblock. If the superblock was not submitted it will wait for it.

    • --deposit AMOUNT: It will deposit the amount of ether in the contract. If the balance is zero and no deposit is specified it will try to deposit 1000 wei.

Deployment

To deploy the contracts there's a basic script that takes care of writing out the deployment data into a json file.

These json files are stored into the deployment/$NETWORK directory, where $NETWORK is the hardhat network selected with the --network option.

Currently, all networks configured in hardhat.config.ts point to the port 8545 in the localhost. You can edit the URLs to point to third party ethereum node backend endpoints like Infura or Alchemy if you want.

To run the deployment on, e.g. rinkeby, execute:

$ npx hardhat --network rinkeby run scripts/deployDogethereum.ts

Upgrades

The contracts support upgrades using OpenZeppelin's upgrades plugin.

Some of these contracts use state variables for values that are meant to be constant so that they can be set during initialization. When planning an upgrade, it is important to determine if part of the altered behaviour is based on some of these variables and update them as needed.

For example, superblock duration is one of such "constant" state variables in the SuperblockClaims contract.

Ideally, we would use immutable contract attributes for these, but the upgrades plugin does not support setting immutable attributes in constructors for logic/implementation contracts yet. See this issue for more details.

License

MIT License
Copyright (c) 2021 Coinfabrik & Oscar Guindzberg
License

Donations

BTC: 37gWZJPmEjM8RdgjauLsDUgbkYPe5bRFtt
ETH: 0xFc7E364035f52ecA68D71dcfb63D1E3769413d69
DOGE: D5q6QoN51z1daFpkreNqpbVq6i6oP6S35m

dogethereum-contracts's People

Contributors

cat-j avatar dependabot[bot] avatar hswick avatar ismaelbej avatar mikerah avatar oscarguindzberg avatar scnale avatar wanidulf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dogethereum-contracts's Issues

Review how ScryptChecker/DogeRelay deals with invalid hashes

  • Think the process how to deal with invalid hashes. Should ScryptChecker notify DogeRelay of invalid hashes? If not, should we have an agent that removes timedout pending headers from the list? Is there an attack we prevent by doing that? Does it has any benefit in terms of gas usage, or freeing up storage space has no gas cost incentive?

Set DogeToken address in DogeRelay during setup

  • relayTx() function stop receiving DogeToken address
  • Create a storage variable to store the address
  • update relayTx() to use the address from the storage variable
  • DogeSubmitter: stop sending DogeToken address when invoking relayTx()
  • update deployment javascript script to set DogeToken address in DogeRelay

Bounty Claim

Hi!
We at the Komodo Platform just did a ETH/DOGE atomic swap. We exchanged it in BarterDEX using ETOMIC.

This is the proof:

SWAP completed! 1322053329-1186185224 {"expiration":1519068637,"tradeid":335663668,"requestid":1322053329,"quoteid":1186185224,"iambob":0,"Bgui":"","Agui":"nogui","gui":"nogui","bob":"ETH","bobtomic":"0x0000000000000000000000000000000000000000","etomicsrc":"0x012f7e74120d3d002e8f215ccc6f6f65618f00cb","srcamount":0.04928730,"bobtxfee":0.00001000,"alice":"DOGE","etomicdest":"0x078a1db84a2995c22cb72a390c3d279448dbfbf2","destamount":10,"alicetxfee":1,"aliceid":"2572377750843424768","sentflags":["alicespend", "bobpayment", "alicepayment", "bobdeposit", "myfee"],"values":[0.04929730, 0, 0.04930730, 12, 0.05546821, 0, 0.01287001, 0, 0, 0, 0],"result":"success","status":"finished","finishtime":1519053161,"bobdeposit":"417b4a91650b74e06b84a50cd4f90c757502f9615ae732702a50682f8925d6fd","alicepayment":"df1547ec128a5d78604bd6c6610c6a6e6b096d8f8ced1acb7bb6ed1611f5322f","bobpayment":"6c3a2269a8f3b111142c1291ffc9548bde61fc2d34ec465486e8a4e937c12217","paymentspent":"f2cf1b572d0084684cf1915de608f664604a92eaf3961d04676e7598d6d12d59","Apaymentspent":"0000000000000000000000000000000000000000000000000000000000000000","depositspent":"0000000000000000000000000000000000000000000000000000000000000000","method":"tradestatus","finishtime":1519053161}

  • bob makes Eth deposit
    ropsten.etherscan.io/tx/0xea333077d5753d29909136f950b2ba224312a0a6ed7dd8558350faaacd877c74

  • alice payment
    dogechain.info/tx/df1547ec128a5d78604bd6c6610c6a6e6b096d8f8ced1acb7bb6ed1611f5322f

  • bob makes Eth payment
    ropsten.etherscan.io/tx/0xdf181653159e77194f8dbd91aa397250062e1f1d38d839b300c29d5678711185

  • alice claims payment
    ropsten.etherscan.io/tx/0x874449fb261e1bda008af33bf8264c464a7363140d34d735b2b02e2d0c891db7

  • bob claims deposit
    ropsten.etherscan.io/tx/0xcda27396a376909ef73e86d241f284de0c6bc04a4c9a6bd19834612408d6800c

Look into security measures against falsifying _truebitClaimantAddress argument in storeBlockHeader

  • Problem: how do we know that the person whose address is being passed as _truebitClaimantAddress has authorised whoever is calling storeBlockHeader to use it?
  • Possible solution 1: add a database of addresses authorised to send blocks
  • Possible solution 2: add a digital signature system to DogeRelay so that storeBlockHeader requires the claimant's private DogeRelay key along with their public Ethereum address
  • Maybe both solutions could work together

Challenger can request headers for non-existent blocks

doQueryBlockHeader does not check whether blockHash is in the corresponding superblock's Merkle root hashes. Therefore, it's possible for a malicious challenger to request a hash that isn't even in the main chain, making it impossible for an honest defender to respond with an actual block header. Currently, the defender just sends a null block and loses the battle by timeout.

Re evaluate how we identify block proposal to scrypt cheker

  • We need a way to identify blocks when they come back from ScryptChecker. Use ibIndex as identifier
    Option 1: Give each block a custom id (think whether to reuse ibIndex or use another counter). Option 2: Doge header hash may seem a good way for identification, the problem is an attacker sending valid doge hashes with invalid blockheaders, so nobody else could submit the valid header, because the valid hash is being used. Even if we discard the proposal later, it will take some time until discarded. We can think the attack of sending invalid hashes has a cost and the consequences of the attack are not dramatic, so we can still use the doge header hash as an id.

Reduce gas usage, in particular when adding doge block headers

Background

The first naive approach to do a Doge->Eth bridge is to just port BtcRelay to Dogecoin. Then, scrypt can not be executed onchain.
So we have this DogeRelay+ScryptInteractiveVerification solution that we have been coding for months.

Problem

DogeRelay requires all new doge block headers to be submitted to it.
That costs:
1440 blocks per day x 300,000 unit of gas per block x 50gwei (gasPrice) x 1200 USD/eth = USD 26k per day.

The DogeRelay code is missing some checks yet. Once we do that, I expect gas cost to increase to 350,000 per block, ie daily cost would go to USD 32k per day.

I have some ideas to optimize DogeRelay.

  • We don't need to store the entire doge block header (that means storing 40 bytes instead of 80 bytes per block header)
  • We could remove block headers older than 1 week as new blocks are added.
    But still, an optimistic goal would be to reduce gas needed by 3.
    As a reference BtcRelay consumes 200,000 gas per btc block header.

We might use a lower gas price... maybe 10gwei... it is just optimizing by 5.

Block submitters' incentive is they get a fee for every lock tx that is processed by DogeRelay.
They would have a daily cost of USD 3k to 32k and we don't expect fees to be that big.
Also, the cost is fixed but the reward is variable, that makes things to look less attractive for block submitters.

Solution

  • DogeRelay won't receive any doge blocks, just doge lock txs.
  • This info would be sent when sending a lock tx: doge tx + block hash where the tx was included + Partial Merkle Tree (SPV proof tx is included in the block).
  • That is the same information that is submitted nowadays.
  • We check onchain the doge tx is actually a lock tx and the SPV proof.
  • We implement interactive verification per tx: To verify the submitted block hash of the tx is part of the doge mainchain.
  • When a lock tx is submitted to DogeRelay, and a validator finds it is not included on doge mainchan, it would do a "pre-challenge". Then, the submitter would have to send the doge header where the tx is included + 100 doge headers on top of that one.
  • We can write a solidity program were we validate those 100 headers: We validate the Scrypt PoW of those 100 block headers, check the headers are connected, check that difficulty adjustments are ok, etc. That algorithm returns true/false.
  • Executing that program would consume around 300,000,000 x 100 unit of gas, so we implement an interactive verification of it.

Before doing this

  • Think whether the current scheme would work: Optimize DogeRelay gas usage further and expect lots of money flowing through the bridge.
  • Make sure the cryptoeconomics of a challenge/response game would work for a game of 30,000,000,000 unit of gas.
  • Think how we establish the difficulty we expect from those 100 blocks

Concerning retrieving your private key for the eth-address

I have the following question about step 7 from the wiki:

7. Get the Dogecoin address that "sent" the DOGE.

Doge tokens will be assigned to the Ethereum address controlled by the private key who signed the Dogecoin transaction.

Is my understanding correct that the Dogerelay contract creates a separate new doge address that makes a doge transaction? (this account's private key will be the ethereum private key to retrieve your eth tokens)

If so, how come you can use dogecoin-qt to retrieve the private key of this new address?

bulkStoreHeaders review and optimisation

  • Why does it cost more gas than just repeatedly calling storeBlockHeader?
  • Possible explanation: bulkStoreHeaders copies all the headers that are passed to it with sliceArray

Optimise variable usage to avoid VM exceptions

Possible solutions:

  • Group related variables into structs (make sure they've actually got something to do with each other so as not to affect code readability too much)
  • If a value isn't needed more than one or two times, don't store it in a local variable. We will have to measure gas usage as this could result in more reads from storage.
    • Could we do without some of the mappings? Perhaps internalBlock can be replaced by something less costly.

We might end up with orphan blocks If scrypt verification success msg is not received in the same order it was sent

  • Let' say a submitter sends to DogeRelay block 10, 11 and 12. They are valid blocks.
  • A challenger does a challenge on block's 10 scrypt hash. Claimant replies to the challenge. Challenger does another challenge on block 10, claimant replies....
  • Meanwhile, there are no challenges on block 11 nor 12. When timeout for challenges is met, ClaimManager informs DogeRelay block 11 and block 12 are ok.
  • Since block 10 is still in the middle of the challenge/response battle, it was not added to DogeRelay doge blockchain yet. So, when DogeRelay does some extra validations on block 11, it finds out that its parent block (block 10) is not in the blockchain. Block 11 will be discarded. Block 12 will have the same problem.
  • Eventually block 10 challenge/response battle will end, block 10 will be added to DogeRelay blockchain, but blocks 11 and block 12 would be lost.
  • DogeSubmitter would have to resend block 11 and 12 and pay again the gas for adding those blocks, while he already payed the gas for sending those blocks earlier.

Brainstorming ideas for a solution :

  • Have an orphan pool
  • Limit the size of the orphan pool to prevent other attacks
  • Have some conditions before adding a block to the orphan pool (eg it's hight should be a few blocks on top of current chain tip, although I doubt we can get block height from a doge header)
  • DogeRelay just send to ClaimManager blocks once the parent of that block has been confirmed by ClaimManager. This solution might introduce other problems: extra delays, other attack vectors, more complex code on DogeRelay.
  • DogeSubmitter don't send more blocks if there is a block waiting for a scrypt verification resolution. This might be the simplest solution but new delays are introduced. Are new attack vectors opened by this approach?

m_saveAncestors review

I guess it is supposed to store block-1, block-5, block-25, block-125... but I am not sure it is doing that

Code clean up

  • Replace function names m_....
  • Add comments to the code
  • Tests: improve test names
  • Document how to get sample block headers from doge blockchain (libdohj java code written by oscar)

Improvements to the deposit system

  • Define fair deposits required fo each operation: challenge, each particular query, each particular response to query. Today: 1 wei for everything
  • Deposit should cover what I am forcing the counterparty to spend + a reward to the counterparty.
    Measure how much gas is required for each operation and harcode constants with those values.
  • Define a harcoded value for gas price

Extra validation of lock txs

validate it is not a release tx
Extra validations to make sure tx input is p2pkh
Review RSK's bridge contract and copy checks done before accepting a tx.

Make a plan in case of a dogecoin or ethereum chain hard forks

Think more about this:

Dogecoin hard fork:
If Dogecoin's PoW algorithm remains the same, the bridge will follow the chain with higher PoW.

Ethereum hard fork:
If there is a hard fork, no problems will pop up.
The problem is what happens if the fork is contentious and a new "ethereum classic" emerges.
Then we will have 2 bridges.
Ideally we would need to end up with 2 bridges, each one with their own operators, locked doges, utxo set, etc.
The problem is at the time of the fork, the utxo set will be shared.
If all previous bridge users and operators decide to follow one chain, there is no issue.
The problem is if some users and operators decide to follow one chain and others decide to follow another chain.

Add an option for sending destination eth address in an OP_RETURN output

Reading public key from first tx input

  • Hardware wallet unfriendly on eth side
  • Have to extract private key from doge and copy to eth wallet
  • Have to add some eth to the account to pay for gas. Users would use that account just use for the doge-eth bridge, so adding some eth there is a nuissance.

Using OP_RETURN

  • Have to develop a special program to build the doge tx.
  • Users have install/use that program instead of the standard wallet they are used to (dogecoin-qt or some other wallet)

Complete block difficulty adjustement code for doge testnet

  • Not working for doge testnet
  • This code has bugs:
    if (net == Network.TESTNET && m_getTimestamp(hashPrevBlock) - m_getTimestamp(blockSha256Hash) > 120) {
    newBits = 0x1e0fffff;
    }
  • Code should be:
    if (net == Network.TESTNET && m_getTimestampOfBlockBeingAddedWhichIsNotInStorageYet(blockSha256Hash) - m_getTimestamp(hashPrevBlock) > 120) {
    newBits = 0x1e0fffff;
    }
  • Update tests so we can have some doge testnet blocks that took less than 120 seconds to mine too. Currently we only have sample doge testnet blocks that took more than 120 seconds.

major flaw found in auxpow header parsing and auxpow POW

related to #13 but was missed or left out. There is an inherent flaw in the way that dogecoin auxpow blocks are validated.

in parseAuxPow the parent block hash is extracted from auxpow header. This parent block hash is checked for proof of work according to the parent block bits. However according to the merge-mining spec (https://en.bitcoin.it/wiki/Merged_mining_specification) "Note that the block_hash element is not needed as you have the full parent_block header element and can calculate the hash from that. The current Namecoin client doesn't check this field for validity, and as such some AuxPOW blocks have it little-endian, and some have it big-endian." Thus you cannot depend on the the extracted hash as you don't know the endianness you have to recalculate it from the parent block header.

The problem is that doge uses scrypt and would need some type of truebit integration here to re-calc this scrypt hash.

This problem was found as I was integrating Syscoin I noticed that the endianness changed per block and thus POW was failing on auxpow blocks. I found on the spec that it can be arbritary. The problem is easily able to be fixed on Syscoin because it merges to bitcoin and thus I have access to sha256 internally so no need for a game around that hash.

As a side note, the ismergemined(blockheader) doesn't actually ever pass (so your auxpow code was never actually exercised and tested). The one with (headerbytes,pos) passes and is the one I use.

Also note that the 20 bytes check that the merkle root starts from the scriptSig of the parent coinbase tx input position is also invalid. The merkle root hash needs to be also checked in the coinbase if you want to do this check. If the merge mined header is found it doesn't check for the 20 byte position rule and its also for backwards compatbility reasons as well.

if (pcHead != script.end())
{
	// Enforce only one chain merkle root by checking that a single instance of the merged
	// mining header exists just before.
	if (script.end() != std::search(pcHead + 1, script.end(), UBEGIN(pchMergedMiningHeader), UEND(pchMergedMiningHeader)))
		return error("Multiple merged mining headers in coinbase");
	if (pcHead + sizeof(pchMergedMiningHeader) != pc)
		return error("Merged mining header is not just before chain merkle root");
}
else
{
	// For backward compatibility.
	// Enforce only one chain merkle root by checking that it starts early in the coinbase.
	// 8-12 bytes are enough to encode extraNonce and nBits.
	if (pc - script.begin() > 20)
		return error("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase");
}

Note that the check happens only if the merge mining header isn't found and not when it is found as the check in parseAuxPow is doing and if you are doing the check it should be based on a search in the coinbase for the chain merkle root.

Add missing validations on AuxPow

  • Check doge sha256hash is included in litecoin coinbase (via a merged mining pmt)
  • check litecoin coinbase is in litecoin transactions partial merkle tree
  • check litecoin transactions partial merkle tree root is in litecoin header

Unlock tx are forced to have a change output

DogeTx.parseTransaction() fails for an unlock tx if the tx does not contains a change output sending doges to an operator.
DogeToken.processTransaction() needs to know the value sent to the operator, no matter it is a lock or unlock tx.
if there is no change, that value should be 0.
unlock txs should be detected by DogeToken.processTransaction() by checking the inputs are signed by the operator.

Workaround that is currently implemented: On the agent isReleaseTx() returns false for unlock tx that don't have change. To relay those tx is just usefule to prevent the operator from being penalized for not doing un unlock. Since penalization is not implemented yet, the workaround works.

Another hack that might solve this: DogeToken should make sure there is always change. When it select utxos, it should make sure there is change. This just require changing ">=" by ">"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.