Skip to content

Commit

Permalink
Merge pull request #1285 from rsksmart/forkdetectiondata-performance
Browse files Browse the repository at this point in the history
ForkDetectionData retrieval performance
  • Loading branch information
aeidelman authored Aug 19, 2020
2 parents 8d8492c + 0da798f commit 4e5b6b8
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 31 deletions.
31 changes: 31 additions & 0 deletions rskj-core/src/main/java/co/rsk/util/ListArrayUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,35 @@ public static byte[] nullToEmpty(@Nullable byte[] array) {

return array;
}

public static int lastIndexOfSubList(byte[] source, byte[] target) {
if (source == null || target == null) {
return -1;
}
if (target.length == 0) {
return source.length;
}
if (source.length < target.length) {
return -1;
}

final int max = source.length - target.length;

for (int i = max; i >= 0; i--) {
boolean found = true;

for (int j = 0; j < target.length; j++) {
if (source[i + j] != target[j]) {
found = false;
break;
}
}

if (found) {
return i;
}
}

return -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@

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

/**
Expand Down Expand Up @@ -170,10 +169,7 @@ public boolean isValid(BlockHeader header) {

byte[] expectedCoinbaseMessageBytes = org.bouncycastle.util.Arrays.concatenate(RskMiningConstants.RSK_TAG, header.getHashForMergedMining());

List<Byte> bitcoinMergedMiningCoinbaseTransactionTailAsList = ListArrayUtil.asByteList(bitcoinMergedMiningCoinbaseTransactionTail);
List<Byte> expectedCoinbaseMessageBytesAsList = ListArrayUtil.asByteList(expectedCoinbaseMessageBytes);

int rskTagPosition = Collections.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTailAsList, expectedCoinbaseMessageBytesAsList);
int rskTagPosition = ListArrayUtil.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTail, expectedCoinbaseMessageBytes);
if (rskTagPosition == -1) {
logger.warn("bitcoin coinbase transaction tail message does not contain expected" +
" RSKBLOCK:RskBlockHeaderHash. Expected: {} . Actual: {} .",
Expand All @@ -192,8 +188,7 @@ public boolean isValid(BlockHeader header) {
return false;
}

List<Byte> rskTagAsList = ListArrayUtil.asByteList(RskMiningConstants.RSK_TAG);
int lastTag = Collections.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTailAsList, rskTagAsList);
int lastTag = ListArrayUtil.lastIndexOfSubList(bitcoinMergedMiningCoinbaseTransactionTail, RskMiningConstants.RSK_TAG);
if (rskTagPosition !=lastTag) {
logger.warn("The valid RSK tag is not the last RSK tag. Tail: {}.", Arrays.toString(bitcoinMergedMiningCoinbaseTransactionTail));
return false;
Expand Down
60 changes: 39 additions & 21 deletions rskj-core/src/main/java/org/ethereum/core/BlockHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@
*/
package org.ethereum.core;

import co.rsk.config.RskMiningConstants;
import co.rsk.core.BlockDifficulty;
import co.rsk.core.Coin;
import co.rsk.core.RskAddress;
import co.rsk.crypto.Keccak256;
import co.rsk.util.ListArrayUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ArrayUtils;
import org.ethereum.crypto.HashUtil;
import org.ethereum.util.RLP;
import org.ethereum.util.Utils;

import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static java.lang.System.arraycopy;
Expand Down Expand Up @@ -490,13 +490,7 @@ public boolean isUMMBlock() {
}

public byte[] getHashForMergedMining() {
byte[] encodedBlock = getEncoded(false, false);
byte[] hashForMergedMining = HashUtil.keccak256(encodedBlock);

if (isUMMBlock()) {
byte[] leftHash = Arrays.copyOf(hashForMergedMining, UMM_LEAVES_LENGTH);
hashForMergedMining = this.getHashRootForMergedMining(leftHash);
}
byte[] hashForMergedMining = this.getBaseHashForMergedMining();

if (includeForkDetectionData) {
byte[] mergedMiningForkDetectionData = hasMiningFields() ?
Expand Down Expand Up @@ -539,28 +533,34 @@ public String getParentPrintableHash() {
public byte[] getMiningForkDetectionData() {
if(includeForkDetectionData) {
if (hasMiningFields() && miningForkDetectionData.length == 0) {
byte[] encodedBlock = getEncoded(false, false);
byte[] hashForMergedMining = HashUtil.keccak256(encodedBlock);

byte[] hashForMergedMiningPrefix = Arrays.copyOfRange(
hashForMergedMining,
0,
HASH_FOR_MERGED_MINING_PREFIX_LENGTH
);
byte[] hashForMergedMining = getBaseHashForMergedMining();

byte[] coinbaseTransaction = getBitcoinMergedMiningCoinbaseTransaction();

List<Byte> hashForMergedMiningPrefixAsList = Arrays.asList(ArrayUtils.toObject(hashForMergedMiningPrefix));
List<Byte> coinbaseAsList = Arrays.asList(ArrayUtils.toObject(coinbaseTransaction));
byte[] mergeMiningTagPrefix = Arrays.copyOf(RskMiningConstants.RSK_TAG, RskMiningConstants.RSK_TAG.length + HASH_FOR_MERGED_MINING_PREFIX_LENGTH);
arraycopy(hashForMergedMining, 0, mergeMiningTagPrefix, RskMiningConstants.RSK_TAG.length, HASH_FOR_MERGED_MINING_PREFIX_LENGTH);

int position = Collections.lastIndexOfSubList(coinbaseAsList, hashForMergedMiningPrefixAsList);
int position = ListArrayUtil.lastIndexOfSubList(coinbaseTransaction, mergeMiningTagPrefix);
if (position == -1) {
throw new IllegalStateException(
String.format("Mining fork detection data could not be found. Header: %s", getPrintableHash())
);
}

int from = position + HASH_FOR_MERGED_MINING_PREFIX_LENGTH;
int from = position + RskMiningConstants.RSK_TAG.length + HASH_FOR_MERGED_MINING_PREFIX_LENGTH;
int to = from + FORK_DETECTION_DATA_LENGTH;

if (coinbaseTransaction.length < to) {
throw new IllegalStateException(
String.format(
"Invalid fork detection data length. Expected: %d. Got: %d. Header: %s",
FORK_DETECTION_DATA_LENGTH,
coinbaseTransaction.length - from,
getPrintableHash()
)
);
}

miningForkDetectionData = Arrays.copyOfRange(coinbaseTransaction, from, to);
}

Expand All @@ -570,6 +570,24 @@ public byte[] getMiningForkDetectionData() {
return new byte[0];
}

/**
* Compute the base hash for merged mining, taking into account whether the block is a umm block.
* This base hash is later modified to include the forkdetectiondata in its last 12 bytes
*
* @return The computed hash for merged mining
*/
private byte[] getBaseHashForMergedMining() {
byte[] encodedBlock = getEncoded(false, false);
byte[] hashForMergedMining = HashUtil.keccak256(encodedBlock);

if (isUMMBlock()) {
byte[] leftHash = Arrays.copyOfRange(hashForMergedMining, 0, UMM_LEAVES_LENGTH);
hashForMergedMining = getHashRootForMergedMining(leftHash);
}

return hashForMergedMining;
}

public boolean isParentOf(BlockHeader header) {
return this.getHash().equals(header.getParentHash());
}
Expand Down
7 changes: 4 additions & 3 deletions rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,16 @@ public void getHashForMergedMiningWithForkDetectionDataAndIncludedOnAndMergedMin
BlockHeader header = createBlockHeaderWithMergedMiningFields(new byte[0], true, new byte[0]);

byte[] encodedBlock = header.getEncoded(false, false);
byte[] hashForMergedMining = Arrays.copyOfRange(HashUtil.keccak256(encodedBlock), 0, 20);
byte[] hashForMergedMiningPrefix = Arrays.copyOfRange(HashUtil.keccak256(encodedBlock), 0, 20);
byte[] forkDetectionData = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
byte[] coinbase = concatenate(hashForMergedMining, forkDetectionData);
byte[] hashForMergedMining = concatenate(hashForMergedMiningPrefix, forkDetectionData);
byte[] coinbase = concatenate(RskMiningConstants.RSK_TAG, hashForMergedMining);
header.setBitcoinMergedMiningCoinbaseTransaction(coinbase);
header.seal();

byte[] hashForMergedMiningResult = header.getHashForMergedMining();

assertThat(coinbase, is(hashForMergedMiningResult));
assertThat(hashForMergedMining, is(hashForMergedMiningResult));
}

@Test
Expand Down
140 changes: 140 additions & 0 deletions rskj-core/src/test/java/co/rsk/util/ListArrayUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,144 @@ public void testNonNullGetLength() {
byte[] array = new byte[1];
Assert.assertEquals(1, ListArrayUtil.getLength(array));
}

@Test
public void testLastIndexOfSublistEmptyArrays() {
byte[] source = new byte[] {};
byte[] target = new byte[] {};

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals( 0, res);
}

@Test
public void testLastIndexOfSublistSearchEmpty() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(5, res);
}

@Test
public void testLastIndexOfSublistFindsMatch1() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 3, 4 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(2, res);
}

@Test
public void testLastIndexOfSublistFindsMatch2() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 4, 5 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(3, res);
}

@Test
public void testLastIndexOfSublistSameArray() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 1, 2, 3, 4, 5 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(0, res);
}

@Test
public void testLastIndexOfSublistTargetLongerThanSource() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 1, 2, 3, 4, 5, 6 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(-1, res);
}

@Test
public void testLastIndexOfSublistPartialOverlapOnBeginning() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 0, 1, 2 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(-1, res);
}

@Test
public void testLastIndexOfSublistPartialOverlapOnEnd() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 4, 5, 6 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(-1, res);
}

@Test
public void testLastIndexOfSublist6ArraysWithNoSharedElements() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = new byte[] { 6, 7, 8, 9, 10 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(-1, res);
}

@Test
public void testLastIndexOfSublistNullSource() {
byte[] source = null;
byte[] target = new byte[] { 6, 7, 8, 9, 10 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(-1, res);
}

@Test
public void testLastIndexOfSublistNullTarget() {
byte[] source = new byte[] { 1, 2, 3, 4, 5 };
byte[] target = null;

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(-1, res);
}

@Test
public void testLastIndexOfSublistMatchesSecondOcurrence() {
byte[] source = new byte[] { 3, 4, 5, 3, 4, 5 };
byte[] target = new byte[] { 3, 4, 5 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(3, res);
}

@Test
public void testLastIndexOfSublistMatchesThirdOcurrence() {
byte[] source = new byte[] { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
byte[] target = new byte[] { 1, 2, 3 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(6, res);
}

@Test
public void testLastIndexOfSublistMatchesLastOcurrence() {
byte[] source = new byte[] { 1, 2, 3, 3, 4, 5, 1, 2, 3 };
byte[] target = new byte[] { 1, 2, 3 };

int res = ListArrayUtil.lastIndexOfSubList(source, target);

Assert.assertEquals(6, res);
}
}

0 comments on commit 4e5b6b8

Please sign in to comment.