Skip links

Interdependent Transactions in Bitcoin, RSK and Ethereum

A recent paper titled The Blockchain Anomaly presents the difficulties the authors encountered when trying to make two transactions interdependent such that the second transaction cannot be executed without the first being confirmed. Although the paper may be useful as a guide, the title is misleading since Bitcoin does not guarantee the order of transactions (no dependencies between inputs and outputs).

Also, “The Blockchain Anomaly” states that Bitcoin requires six block confirmations to finalize a transaction, but the original Bitcoin paper does not actually stipulate a specific number. In addition, each Bitcoin wallet application can choose which algorithm inform the user that a transaction has been confirmed. In fact, for stronger security, the number of block confirmations should not be constant, but should depend on several factors:

  • Transaction amount (large transactions require more confirmations to prevent double-spending)
  • The properties of other transactions included in competing blocks (such as the presence of bribe fees) during the confirmation interval
  • The properties of other transactions included in the confirmation chain (to detect if these other transactions could be targeted by double-spend attacks which reorganize the blockchain)
  • The state of the network’s hashing power (to detect sudden drops which might indicate the preparation of a parallel secret branch by an attacker)
  • The state of trusted peers (to detect Sybil attacks)

The same happens in Ethereum, users are not obliged to wait exactly 12 block confirmations. . First, let’s try to define the problem more clearly. One formulation might be: “Alice wants to pay to Carol only if Bob pays to Alice first”. As Nakamoto consensus only assures probabilistic settlement, this problem can be easily solved by picking a long enough confirmation time for the Bob/Alice transaction (e.g. 7 days of confirmation blocks). Then if the payment is reverted, the whole platform would be halted as insecure and the Alice/Carol payment will never be confirmed either, so this problem is not very interesting. A more interesting problem would be “Alice wants to pay to Carol as soon as Bob pays Alice”. This problem arises in a number of contexts such as in a crypto asset exchange, spending money right after an investment has been secured, or in fast betting applications, such as SatoshiDice.

Solutions

For Bitcoin, one solution is to create a single transaction that contains both payments, but this requires off-chain coordination that is beyond of the scope of the problem. In addition, this solution will not work if the second payment is governed by a third condition, such as winning the SatoshiDice game. A simpler and more elegant solution is to use the unspent output of the Bob/Alice payment as an input of the Alice/Carol payment. This makes the second payment consensus-dependent on the first, establishing a static dependency. The cost of this solution is near zero: in the worst case it is the transaction fees related to the overhead of the space consumed by one input.

In Ethereum and RSK we cannot link two transactions unless they are created by the same account and contract. When two transactions are created by the same account, the second payment cannot be confirmed without the first because each transaction has an increasing nonce value, and nonce values cannot be skipped. If the payments are created by different accounts, then we can link them with a conditional payment contract. This contract must be created and funded on creation, before the first payment, and it automatically self-destructs afterwards. Here it is:

contract conditionalPayment1 {
	address A = 0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C; // A account
	address B = 0x631F2DFF57D15D77215449490E747BE5191DF204; // B account
	address C = 0x8746B01315F995BCB8638E30C6695B7A63F8A346; // C account

	uint256 public payToC = 123456; // amount to be paid to C

	modifier onlyFrom(address _address) { // enables depending on invoker

		if (msg.sender != _address)
			throw;
	}

	function sendToAlice() onlyFrom(B) { // B sends to A
		if (msg.value >= payToC) {
			C.send(payToC); // A sends to C
			suicide(A); // Send the remainder back to Alice
		} else
			throw; // cancel contract execution
	}

} // contract

This contract solves the problem when the conditions of the payment to C (e.g. amount, additional transaction data) are known before the payment to the conditionalPayment1 is executed. If the conditions for payment to C must be decided by Alice after Bob’s payment, then a contract like the following conditionalPayment2 contract is required.

contract conditionalPayment2 {
	uint256 public paid; // amount paid by B to A
	address A = 0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C; // A account
	address B = 0x631F2DFF57D15D77215449490E747BE5191DF204; // B account
	address C = 0x8746B01315F995BCB8638E30C6695B7A63F8A346; // C account

	modifier onlyFrom(address _address) { // enables depending on invoker
		if (msg.sender != _address)
			throw;
	}

	function sendToAlice() onlyFrom(B) { // B sends to A
		paid += msg.value; // store the amount paid
	}

	function fund() onlyFrom(A) { // A sends to this contract (herself)
		paid += msg.value; // store the amount paid
	}

	function sendToCarol(uint256 _amount) onlyFrom(A) { // A sends to C
		if (_amount <= paid) {
			C.send(_amount);
			suicide(A); // Send the remaining back to Alice
		} else
			throw; // cancel contract execution
	}
} // contract

Both conditionalPayment1 and conditionalPayment2 are hard-wired for particular source and destination addresses, but a generic contract can also be created to handle third party conditional payments in a trustless manner. Generic contracts require:

  • An initial transaction to set up the conditional contract.
  • A secure method to send the address of the contract, since he may not know Alice’s address.
  • Two transactions If the payment from Alice to Carol is greater than the payment from Bob to Alice. Alice must fund the contract with additional coins (using fund() or when creating the contract), or split her payment to Carol into two different transactions, one coming from the conditional contract and the other from Alice’s own account.
  • Almost 100 times more gas than two standard transactions. This means that a generic contract is 100 times more expensive.

Both RSK and Ethereum platforms have a special opcode BLOCKHASH that allows a contract to retrieve the hash of one of the previous 256 blocks counting from the block being processed. This opcode is a better way to create dependencies. The underlying idea is that after Bob’s  transaction to Alice has been confirmed, Alice creates a transaction to Carol as an on-the-fly contract that checks the blockchain to make sure Bob’s transaction is there. If it is not, then the contract returns the funds to Alice, and only some minimal gas is consumed.

On-the-fly contracts can also be used to reduce storage costs. When a new contract is created, some setup code given by the creator is called. When programming in Solidity, this setup code is the smart contract constructor (whose name is the same as the contract name). The constructor can perform any operation and self-destruct afterwards , which prevents the smart contract from persisting, and refunds the cost of contract code storage to the creator. This makes Alice’s transaction far cheaper.

The following is an example of a Solidity smart contract based on BLOCKHASH. In Solidity, the BLOCKHASH opcode can be accessed using the block.blockhash() method. Alice must set the blockIndex and blockHash as soon as Bob’s contract has been observed in the blockchain. The blockIndex is the blockchain height of the block that contains Bob’s transaction and blockHash is the block ID or hash.

contract conditionalPayment3 {
	address A = 0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C; // address of A account
	address C = 0x8746B01315F995BCB8638E30C6695B7A63F8A346; // address of C account
	uint blockIndex = 1; // the block index where the B -> A transaction is included
	uint256 blockHash =0x0E747BE5191DF204631F2DFF57D15D7721544949631F2DFF57D15D7721544949;

	function conditionalPayment3()  { // Alice sends money to Carol
		uint256 b=uint(block.blockhash(blockIndex));
		if (b==blockHash) {
			suicide(C); // Send all to Carol
		} else
			suicide(A); // cancel contract execution
	}

} // contract

The cost of executing this contract is only five times the cost of a single transaction.  Re-writing this small contract in RVM/EVM assembler would probably cut that cost in half.

RSK Labs is creating an Ethereum-compatible platform that tries to cover the most common use cases at the lowest possible cost. RSK is working on new opcodes and native contracts to allow transaction dependencies with lower overhead. The RSK virtual machine release 2.0 has a new native contract, IS_TRANSACTION_MEMBER, which receives a block number, a transaction hash and an SPV proof for the transaction trie as arguments and returns “true” if the transaction is included in that block, and “false” otherwise. A similar native contract, IS_LOG_MEMBER, can be used to detect whether a certain log entry has been recorded in the receipt trie. With these native contracts you can, for example, create a contract that pays Carol if Bob pays Alice, but allows the payment from Bot to Alice to be fully independent from the contract. For example, suppose that X is a contract where users vote to elect one of the members as president. When a president is elected, a message stating the elected user account is logged or, some coins are sent to the elected user account. Then, a contract Y can be created that rewards the elected user by sending him coins from contract Y. Contract Y can be called by any user, sending the “proof of winning election” as an SPV proof using IS_LOG_MEMBER/IS_TRANSACTION_MEMBER.

Conclusion

Programmers of smart contracts should pay special attention to transaction dependencies when fast responses are required or when a break in the dependency causes significant monetary loss. Input/output dependencies can be used to easily create guaranteed transaction dependencies in Bitcoin , but special smart contracts are required in RSK and Ethereum. The naïve RSK/Ethereum smart contract which receives a first payment and only afterwards performs a second payment is very expensive in terms of consumed gas. BLOCKHASH can do the same thing at 1/20 of the cost. RSK is working on additional opcodes and native contracts to provide this functionality without the need to specify a new destination address for a new contract for the first payment.