Skip to content

Commit

Permalink
Implmentation of EthSwap mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
rmoreliovlabs committed Mar 26, 2024
1 parent 71b5f5b commit d268a16
Showing 6 changed files with 470 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import co.rsk.net.TransactionValidationResult;
import co.rsk.net.handler.TxPendingValidator;
import co.rsk.net.handler.quota.TxQuotaChecker;
import co.rsk.util.ContractUtil;
import com.google.common.annotations.VisibleForTesting;
import org.ethereum.core.*;
import org.ethereum.db.BlockStore;
@@ -477,7 +478,13 @@ private boolean senderCanPayPendingTransactionsAndNewTx(Transaction newTx, Repos
}

Coin costWithNewTx = accumTxCost.add(getTxBaseCost(newTx));
return costWithNewTx.compareTo(currentRepository.getBalance(newTx.getSender(signatureCache))) <= 0;
boolean senderCanPay = costWithNewTx.compareTo(currentRepository.getBalance(newTx.getSender(signatureCache))) <= 0;

if(senderCanPay) {
return true;
}

return ContractUtil.isClaimTxAndValid(newTx, currentRepository, costWithNewTx, config.getNetworkConstants(), signatureCache);
}

private Coin getTxBaseCost(Transaction tx) {
96 changes: 96 additions & 0 deletions rskj-core/src/main/java/co/rsk/util/ContractUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package co.rsk.util;

import co.rsk.core.Coin;
import co.rsk.core.RskAddress;
import co.rsk.db.RepositorySnapshot;
import org.ethereum.config.Constants;
import org.ethereum.core.CallTransaction;
import org.ethereum.core.SignatureCache;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.HashUtil;
import org.ethereum.solidity.SolidityType;
import org.ethereum.util.ByteUtil;
import org.ethereum.vm.DataWord;

import java.math.BigInteger;
import java.util.Arrays;

public class ContractUtil {
public static byte[] calculateSwapHash(Transaction newTx, SignatureCache signatureCache) {
CallTransaction.Invocation invocation = getTxInvocation(newTx);

// Get arguments from the invocation object
byte[] preimage = (byte[]) invocation.args[0];
// Encode preimage with sha256, this is to imitate the encoding done in teh claim function before calling hashValues
byte[] preimageHash = HashUtil.sha256(preimage);
BigInteger amount = (BigInteger) invocation.args[1];
DataWord refundAddress = (DataWord) invocation.args[2];
BigInteger timeLock = (BigInteger) invocation.args[3];


// Remove the leading zeroes from the arguments bytes and merge them
byte[] argumentsBytes = encodePacked(
preimageHash,
amount,
newTx.getSender(signatureCache),
new RskAddress(ByteUtil.toHexString(refundAddress.getLast20Bytes())),
timeLock
);

return HashUtil.keccak256(argumentsBytes);
}

public static byte[] encodePacked(Object...arguments) {
byte[] encodedArguments = new byte[]{};

for(Object arg: arguments) {
byte[] encodedArg = new byte[]{};
if(arg instanceof byte[]) {
SolidityType bytes32Type = SolidityType.getType(SolidityType.BYTES32);
encodedArg = bytes32Type.encode(arg);
} else if(arg instanceof RskAddress) {
SolidityType addressType = SolidityType.getType(SolidityType.ADDRESS);
byte[] encodedAddress = addressType.encode(((RskAddress) arg).toHexString());
encodedArg = org.bouncycastle.util.Arrays.copyOfRange(encodedAddress, 12, encodedAddress.length);
} else if(arg instanceof BigInteger) {
SolidityType uint256Type = SolidityType.getType(SolidityType.UINT);
encodedArg = uint256Type.encode(arg);
}

encodedArguments = ByteUtil.merge(encodedArguments, encodedArg);
}

return encodedArguments;
}

public static CallTransaction.Invocation getTxInvocation(Transaction newTx) {
String abi = "[{\"constant\":false,\"inputs\":[{\"name\":\"preimage\",\"type\":\"bytes32\"}, {\"name\":\"amount\",\"type\":\"uint256\"}, {\"name\":\"address\",\"type\":\"address\"}, {\"name\":\"timelock\",\"type\":\"uint256\"}],\"name\":\"claim\",\"outputs\":[],\"payable\":false,\"type\":\"function\"}]";
CallTransaction.Contract contract = new CallTransaction.Contract(abi);
return contract.parseInvocation(newTx.getData());
}

public static boolean isClaimTxAndValid(Transaction newTx,
RepositorySnapshot currentRepository,
Coin txCost,
Constants constants,
SignatureCache signatureCache) {
byte[] functionSelector = Arrays.copyOfRange(newTx.getData(), 0, 4);
if(newTx.getReceiveAddress().toHexString().equalsIgnoreCase(constants.getEtherSwapContractAddress())
&& Arrays.equals(functionSelector, Constants.CLAIM_FUNCTION_SIGNATURE)) {

byte[] swapHash = calculateSwapHash(newTx, signatureCache);
DataWord swapRecord = currentRepository.getStorageValue(newTx.getReceiveAddress(), DataWord.valueOf(swapHash));

if(swapRecord != null){
CallTransaction.Invocation invocation = getTxInvocation(newTx);
BigInteger amount = (BigInteger) invocation.args[1];
Coin balanceWithClaim = currentRepository.getBalance(newTx.getSender(signatureCache))
.add(Coin.valueOf(amount.longValue()));

return txCost.compareTo(balanceWithClaim) <= 0;
}
}

return false;
}
}
36 changes: 28 additions & 8 deletions rskj-core/src/main/java/org/ethereum/config/Constants.java
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
import org.bouncycastle.util.encoders.Hex;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.config.blockchain.upgrades.ConsensusRule;
import org.ethereum.core.CallTransaction;

import java.math.BigDecimal;
import java.math.BigInteger;
@@ -45,6 +46,11 @@ public class Constants {
private static final BigInteger SECP256K1N = new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16);
private static final BigInteger TRANSACTION_GAS_CAP = BigDecimal.valueOf(Math.pow(2, 60)).toBigInteger();
private static final BigInteger RSKIP156_DIF_BOUND_DIVISOR = BigInteger.valueOf(400);
public static final byte[] CLAIM_FUNCTION_SIGNATURE = CallTransaction.Function.fromSignature(
"claim",
new String[]{"bytes32", "uint256", "address", "uint256"},
new String[]{}
).encodeSignature();

private static final long DEFAULT_MAX_TIMESTAMPS_DIFF_IN_SECS = 5L * 60; // 5 mins
private static final long TESTNET_MAX_TIMESTAMPS_DIFF_IN_SECS = 120L * 60; // 120 mins
@@ -59,6 +65,7 @@ public class Constants {
private final int newBlockMaxSecondsInTheFuture;
public final BridgeConstants bridgeConstants;
private final ActivationConfig activationConfig;
private final String etherSwapContractAddress;

public Constants(
byte chainId,
@@ -70,7 +77,8 @@ public Constants(
int newBlockMaxSecondsInTheFuture,
BridgeConstants bridgeConstants,
ActivationConfig activationConfig,
BlockDifficulty minimumDifficultyForRskip290) {
BlockDifficulty minimumDifficultyForRskip290,
String etherSwapContractAddress) {
this.chainId = chainId;
this.seedCowAccounts = seedCowAccounts;
this.durationLimit = durationLimit;
@@ -81,6 +89,7 @@ public Constants(
this.bridgeConstants = bridgeConstants;
this.activationConfig = activationConfig;
this.minimumDifficultyForRskip290 = minimumDifficultyForRskip290;
this.etherSwapContractAddress = etherSwapContractAddress;
}

public Constants(
@@ -92,7 +101,8 @@ public Constants(
BigInteger difficultyBoundDivisor,
int newBlockMaxSecondsInTheFuture,
BridgeConstants bridgeConstants,
BlockDifficulty minimumDifficultyForRskip290) {
BlockDifficulty minimumDifficultyForRskip290,
String etherSwapContractAddress) {
this(chainId,
seedCowAccounts,
durationLimit,
@@ -102,7 +112,8 @@ public Constants(
newBlockMaxSecondsInTheFuture,
bridgeConstants,
null,
minimumDifficultyForRskip290
minimumDifficultyForRskip290,
etherSwapContractAddress
);
}

@@ -225,6 +236,10 @@ public static int getMaxBitcoinMergedMiningMerkleProofLength() {
return 960;
}

public String getEtherSwapContractAddress() {
return etherSwapContractAddress;
}

public static Constants mainnet() {
return new Constants(
MAINNET_CHAIN_ID,
@@ -235,7 +250,8 @@ public static Constants mainnet() {
BigInteger.valueOf(50),
60,
BridgeMainNetConstants.getInstance(),
new BlockDifficulty(new BigInteger("550000000"))
new BlockDifficulty(new BigInteger("550000000")),
null
);
}

@@ -249,7 +265,8 @@ public static Constants devnetWithFederation(List<BtcECKey> federationPublicKeys
BigInteger.valueOf(50),
540,
new BridgeDevNetConstants(federationPublicKeys),
new BlockDifficulty(new BigInteger("550000000"))
new BlockDifficulty(new BigInteger("550000000")),
null
);
}

@@ -264,7 +281,8 @@ public static Constants testnet(ActivationConfig activationConfig) {
540,
BridgeTestNetConstants.getInstance(),
activationConfig,
new BlockDifficulty(new BigInteger("550000000"))
new BlockDifficulty(new BigInteger("550000000")),
null
);
}

@@ -278,7 +296,8 @@ public static Constants regtest() {
BigInteger.valueOf(2048),
0,
BridgeRegTestConstants.getInstance(),
new BlockDifficulty(new BigInteger("550000000"))
new BlockDifficulty(new BigInteger("550000000")),
"77045E71a7A2c50903d88e564cD72fab11e82051"
);
}

@@ -292,7 +311,8 @@ public static Constants regtestWithFederation(List<BtcECKey> genesisFederationPu
BigInteger.valueOf(2048),
0,
new BridgeRegTestConstants(genesisFederationPublicKeys),
new BlockDifficulty(new BigInteger("550000000"))
new BlockDifficulty(new BigInteger("550000000")),
null
);
}
}
43 changes: 35 additions & 8 deletions rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import co.rsk.metrics.profilers.ProfilerFactory;
import co.rsk.panic.PanicProcessor;
import co.rsk.rpc.modules.trace.ProgramSubtrace;
import co.rsk.util.ContractUtil;
import org.ethereum.config.Constants;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.config.blockchain.upgrades.ConsensusRule;
@@ -99,6 +100,7 @@ public class TransactionExecutor {
private final SignatureCache signatureCache;

private boolean localCall = false;
private boolean txWasPaid = false;

public TransactionExecutor(
Constants constants, ActivationConfig activationConfig, Transaction tx, int txindex, RskAddress coinbase,
@@ -167,17 +169,11 @@ private boolean init() {
}


Coin totalCost = tx.getValue();

if (basicTxCost > 0 ) {
// add gas cost only for priced transactions
Coin txGasCost = tx.getGasPrice().multiply(BigInteger.valueOf(txGasLimit));
totalCost = totalCost.add(txGasCost);
}
Coin totalCost = getTxTotalCost();

Coin senderBalance = track.getBalance(tx.getSender(signatureCache));

if (!isCovers(senderBalance, totalCost)) {
if (!isCovers(senderBalance, totalCost) && !ContractUtil.isClaimTxAndValid(tx, track, totalCost,constants, signatureCache)) {

logger.warn("Not enough cash: Require: {}, Sender cash: {}, tx {}", totalCost, senderBalance, tx.getHash());
logger.warn("Transaction Data: {}", tx);
@@ -271,6 +267,14 @@ private void execute() {

track.increaseNonce(tx.getSender(signatureCache));

Coin totalCost = getTxTotalCost();
Coin senderBalance = track.getBalance(tx.getSender(signatureCache));

if(isCovers(senderBalance, totalCost)) {
payTransaction();
txWasPaid = true;
}

long txGasLimit = GasCost.toGas(tx.getGasLimit());
Coin txGasCost = tx.getGasPrice().multiply(BigInteger.valueOf(txGasLimit));
track.addBalance(tx.getSender(signatureCache), txGasCost.negate());
@@ -502,6 +506,9 @@ public TransactionReceipt getReceipt() {
}

private void finalization() {
if(!txWasPaid) {
payTransaction();
}
// RSK if local call gas balances must not be changed
if (localCall) {
// there's no need to save any change
@@ -682,4 +689,24 @@ public long getGasUsed() {
}

public Coin getPaidFees() { return paidFees; }

private void payTransaction() {
long txGasLimit = GasCost.toGas(tx.getGasLimit());
Coin txGasCost = tx.getGasPrice().multiply(BigInteger.valueOf(txGasLimit));
track.addBalance(tx.getSender(signatureCache), txGasCost.negate());

logger.trace("Paying: txGasCost: [{}], gasPrice: [{}], gasLimit: [{}]", txGasCost, tx.getGasPrice(), txGasLimit);
}

private Coin getTxTotalCost() {
long txGasLimit = GasCost.toGas(tx.getGasLimit());
Coin totalCost = tx.getValue();

if (basicTxCost > 0 ) {
Coin txGasCost = tx.getGasPrice().multiply(BigInteger.valueOf(txGasLimit));
totalCost = totalCost.add(txGasCost);
}

return totalCost;
}
}
Loading

0 comments on commit d268a16

Please sign in to comment.