diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java index 950ebcc828f..c90d0be1ddd 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java @@ -68,9 +68,13 @@ public String wireProtocolQueueSize() { public JsonNode traceTransaction(String transactionHash, Map traceOptions) { logger.trace("debug_traceTransaction({}, {})", transactionHash, traceOptions); - if (traceOptions != null && !traceOptions.isEmpty()) { - // TODO: implement the logic that takes into account trace options. - logger.warn("Received {} trace options. For now trace options are being ignored", traceOptions); + TraceOptions options = new TraceOptions(traceOptions); + + if (!options.getUnsupportedOptions().isEmpty()) { + // TODO: implement the logic that takes into account the remaining trace options. + logger.warn( + "Received {} unsupported trace options.", + options.getUnsupportedOptions()); } byte[] hash = stringHexToByteArray(transactionHash); @@ -86,7 +90,7 @@ public JsonNode traceTransaction(String transactionHash, Map tra Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); txInfo.setTransaction(tx); - ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(); + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); return programTraceProcessor.getProgramTraceAsJsonNode(tx.getHash()); @@ -96,9 +100,13 @@ public JsonNode traceTransaction(String transactionHash, Map tra public JsonNode traceBlock(String blockHash, Map traceOptions) { logger.trace("debug_traceBlockByHash({}, {})", blockHash, traceOptions); - if (traceOptions != null && !traceOptions.isEmpty()) { - // TODO: implement the logic that takes into account trace options. - logger.warn("Received {} trace options. For now trace options are being ignored", traceOptions); + TraceOptions options = new TraceOptions(traceOptions); + + if (!options.getUnsupportedOptions().isEmpty()) { + // TODO: implement the logic that takes into account the remaining trace options. + logger.warn( + "Received {} unsupported trace options.", + options.getUnsupportedOptions()); } byte[] bHash = stringHexToByteArray(blockHash); @@ -110,7 +118,7 @@ public JsonNode traceBlock(String blockHash, Map traceOptions) { Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); - ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(); + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); List txHashes = block.getTransactionsList().stream() diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java new file mode 100644 index 00000000000..6fac1c24916 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java @@ -0,0 +1,34 @@ +/* + * This file is part of RskJ + * Copyright (C) 2017 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.rpc.modules.debug; + +public enum DisableOption { + DISABLE_MEMORY("disableMemory", "memory"), + DISABLE_STACK("disableStack", "stack"), + DISABLE_STORAGE("disableStorage", "storage"); + + public final String option; + public final String value; + + DisableOption(String option, String value) { + this.option = option; + this.value = value; + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java new file mode 100644 index 00000000000..b5d3fa3cc40 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java @@ -0,0 +1,68 @@ +/* + * This file is part of RskJ + * Copyright (C) 2017 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.rpc.modules.debug; + +import java.util.*; +import java.util.stream.Collectors; + +public class TraceOptions { + + private final List supportedOptions; + private final Set disabledFields; + private final Set unsupportedOptions; + + public TraceOptions() { + supportedOptions = Arrays.stream(DisableOption.values()).map(option -> option.option) + .collect(Collectors.toList()); + + this.disabledFields = new HashSet<>(); + this.unsupportedOptions = new HashSet<>(); + } + + public TraceOptions(Map traceOptions) { + this(); + + if (traceOptions == null || traceOptions.isEmpty()) return; + + // Disabled Fields Parsing + + for (DisableOption disableOption : DisableOption.values()) { + if (Boolean.parseBoolean(traceOptions.get(disableOption.option))) { + this.disabledFields.add(disableOption.value); + } + } + + // Unsupported Options + + traceOptions.keySet() + .stream() + .filter(key -> supportedOptions.stream().noneMatch(option -> option.equals(key))) + .forEach(unsupportedOptions::add); + } + + public Set getDisabledFields() { + return Collections.unmodifiableSet(disabledFields); + } + + public Set getUnsupportedOptions() { + return Collections.unmodifiableSet(unsupportedOptions); + } + +} diff --git a/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceBC.java b/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceBC.java index dbb129d721f..3368a2dce25 100644 --- a/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceBC.java +++ b/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceBC.java @@ -65,6 +65,7 @@ class Secp256k1ServiceBC implements Secp256k1Service { @Override public ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash, boolean compressed) { check(recId >= 0, "recId must be positive"); + check(recId <= 3, "recId must be less than or equal to 3"); check(sig.getR().signum() >= 0, "r must be positive"); check(sig.getS().signum() >= 0, "s must be positive"); check(messageHash != null, "messageHash must not be null"); diff --git a/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceNative.java b/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceNative.java index b8aeda40aa5..bf964579182 100644 --- a/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceNative.java +++ b/rskj-core/src/main/java/org/ethereum/crypto/signature/Secp256k1ServiceNative.java @@ -41,6 +41,7 @@ public class Secp256k1ServiceNative extends Secp256k1ServiceBC { @Override public ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash, boolean compressed) { check(recId >= 0, "recId must be positive"); + check(recId <= 3, "recId must be less than or equal to 3"); check(sig.getR().signum() >= 0, "r must be positive"); check(sig.getS().signum() >= 0, "s must be positive"); check(messageHash != null, "messageHash must not be null"); diff --git a/rskj-core/src/main/java/org/ethereum/vm/trace/Op.java b/rskj-core/src/main/java/org/ethereum/vm/trace/Op.java index 56dcb36a945..1a001f1de19 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/trace/Op.java +++ b/rskj-core/src/main/java/org/ethereum/vm/trace/Op.java @@ -19,6 +19,7 @@ package org.ethereum.vm.trace; +import com.fasterxml.jackson.annotation.JsonFilter; import org.ethereum.util.ByteUtil; import org.ethereum.vm.OpCode; import org.ethereum.vm.program.Memory; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Map; +@JsonFilter("opFilter") public class Op { private OpCode op; diff --git a/rskj-core/src/main/java/org/ethereum/vm/trace/ProgramTraceProcessor.java b/rskj-core/src/main/java/org/ethereum/vm/trace/ProgramTraceProcessor.java index 4849381b0f2..747b820de90 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/trace/ProgramTraceProcessor.java +++ b/rskj-core/src/main/java/org/ethereum/vm/trace/ProgramTraceProcessor.java @@ -20,14 +20,14 @@ package org.ethereum.vm.trace; import co.rsk.crypto.Keccak256; +import co.rsk.rpc.modules.debug.TraceOptions; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; /** @@ -35,10 +35,18 @@ */ public class ProgramTraceProcessor { - private static final ObjectMapper OBJECT_MAPPER = makeObjectMapper(); - private final Map traces = new HashMap<>(); + private final TraceOptions traceOptions; + + public ProgramTraceProcessor() { + traceOptions = new TraceOptions(); + } + + public ProgramTraceProcessor(TraceOptions options) { + traceOptions = options; + } + public void processProgramTrace(ProgramTrace programTrace, Keccak256 txHash) { this.traces.put(txHash, programTrace); } @@ -53,7 +61,11 @@ public JsonNode getProgramTracesAsJsonNode(List txHashes) { .filter(Objects::nonNull) .collect(Collectors.toList()); - return OBJECT_MAPPER.valueToTree(txTraces); + SimpleFilterProvider filterProvider = new SimpleFilterProvider(); + filterProvider.addFilter("opFilter", + SimpleBeanPropertyFilter.serializeAllExcept(traceOptions.getDisabledFields())); + + return makeObjectMapper().setFilterProvider(filterProvider).valueToTree(txTraces); } public JsonNode getProgramTraceAsJsonNode(Keccak256 txHash) { @@ -63,7 +75,11 @@ public JsonNode getProgramTraceAsJsonNode(Keccak256 txHash) { return null; } - return OBJECT_MAPPER.valueToTree(trace); + SimpleFilterProvider filterProvider = new SimpleFilterProvider(); + filterProvider.addFilter("opFilter", + SimpleBeanPropertyFilter.serializeAllExcept(traceOptions.getDisabledFields())); + + return makeObjectMapper().setFilterProvider(filterProvider).valueToTree(trace); } private static ObjectMapper makeObjectMapper() { diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java index 4b7cd6451a9..417db6f63a1 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/DebugModuleImplTest.java @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ + package co.rsk.rpc.modules.debug; import co.rsk.net.MessageHandler; @@ -36,8 +37,9 @@ import org.junit.Before; import org.junit.Test; -import java.io.IOException; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import static org.ethereum.rpc.TypeConverter.stringHexToByteArray; @@ -52,7 +54,7 @@ public class DebugModuleImplTest { private DebugModuleImpl debugModule; @Before - public void setup(){ + public void setup() { blockStore = Web3Mocks.getMockBlockStore(); receiptStore = Web3Mocks.getMockReceiptStore(); messageHandler = Web3Mocks.getMockMessageHandler(); @@ -61,7 +63,7 @@ public void setup(){ } @Test - public void debug_wireProtocolQueueSize_basic() throws IOException { + public void debug_wireProtocolQueueSize_basic() { String result = debugModule.wireProtocolQueueSize(); try { TypeConverter.JSonHexToLong(result); @@ -71,7 +73,7 @@ public void debug_wireProtocolQueueSize_basic() throws IOException { } @Test - public void debug_wireProtocolQueueSize_value() throws IOException { + public void debug_wireProtocolQueueSize_value() { when(messageHandler.getMessageQueueSize()).thenReturn(5L); String result = debugModule.wireProtocolQueueSize(); try { @@ -83,7 +85,7 @@ public void debug_wireProtocolQueueSize_value() throws IOException { } @Test - public void debug_traceTransaction_retrieveUnknownTransactionAsNull() throws Exception { + public void debug_traceTransaction_retrieveUnknownTransactionAsNull() { byte[] hash = stringHexToByteArray("0x00"); when(receiptStore.getInMainChain(hash, blockStore)).thenReturn(Optional.empty()); @@ -212,13 +214,16 @@ public void debug_traceTransaction_retrieveSimpleAccountTransferWithTraceOptions Assert.assertEquals(resultWithNoOptions, resultWithEmptyOptions); - JsonNode resultWithNonEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), Collections.singletonMap("disableStorage", "true")); + Map traceOptions = new HashMap<>(); + traceOptions.put("disableStorage", "true"); + + JsonNode resultWithNonEmptyOptions = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions); Assert.assertEquals(resultWithNoOptions, resultWithNonEmptyOptions); } @Test - public void debug_traceBlock_retrieveUnknownBlockAsNull() throws Exception { + public void debug_traceBlock_retrieveUnknownBlockAsNull() { byte[] hash = stringHexToByteArray("0x00"); when(blockStore.getBlockByHash(hash)).thenReturn(null); @@ -256,4 +261,58 @@ public void debug_traceBlock_retrieveSimpleContractsCreationTrace() throws Excep Assert.assertTrue(structLogs.size() > 0); }); } + + @Test + public void debug_traceTransaction_retrieveSimpleContractInvocationTrace_traceOptions_disableAllFields_OK() throws Exception { + DslParser parser = DslParser.fromResource("dsl/contracts02.txt"); + ReceiptStore receiptStore = new ReceiptStoreImpl(new HashMapDB()); + World world = new World(receiptStore); + + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + Transaction transaction = world.getTransactionByName("tx02"); + + DebugModuleImpl debugModule = new DebugModuleImpl(world.getBlockStore(), receiptStore, messageHandler, world.getBlockExecutor()); + + Map traceOptions = new HashMap<>(); + traceOptions.put("disableStack", "true"); + traceOptions.put("disableMemory", "true"); + traceOptions.put("disableStorage", "true"); + + JsonNode witnessResult = debugModule.traceTransaction(transaction.getHash().toJsonString(), null); + JsonNode result = debugModule.traceTransaction(transaction.getHash().toJsonString(), traceOptions); + + // Sanity Check + + Assert.assertNotNull(witnessResult); + Assert.assertTrue(witnessResult.isObject()); + + ObjectNode oWitnessResult = (ObjectNode) witnessResult; + Assert.assertTrue(oWitnessResult.get("error").textValue().isEmpty()); + Assert.assertTrue(oWitnessResult.get("result").textValue().isEmpty()); + JsonNode witnessStructLogs = oWitnessResult.get("structLogs"); + Assert.assertTrue(witnessStructLogs.isArray()); + Assert.assertTrue(witnessStructLogs.size() > 0); + + Assert.assertNotNull(result); + Assert.assertTrue(result.isObject()); + + ObjectNode oResult = (ObjectNode) result; + Assert.assertTrue(oResult.get("error").textValue().isEmpty()); + Assert.assertTrue(oResult.get("result").textValue().isEmpty()); + JsonNode structLogs = oResult.get("structLogs"); + Assert.assertTrue(structLogs.isArray()); + Assert.assertTrue(structLogs.size() > 0); + + // Check Filters + + Assert.assertNotEquals(witnessResult, result); + Assert.assertFalse(witnessStructLogs.findValues("stack").isEmpty()); + Assert.assertFalse(witnessStructLogs.findValues("memory").isEmpty()); + Assert.assertFalse(witnessStructLogs.findValues("storage").isEmpty()); + Assert.assertTrue(structLogs.findValues("stack").isEmpty()); + Assert.assertTrue(structLogs.findValues("memory").isEmpty()); + Assert.assertTrue(structLogs.findValues("storage").isEmpty()); + } } diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java new file mode 100644 index 00000000000..1cf81fb380c --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/debug/TraceOptionsTest.java @@ -0,0 +1,143 @@ +/* + * This file is part of RskJ + * Copyright (C) 2017 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.rpc.modules.debug; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class TraceOptionsTest { + + @Test + public void testTraceOptions_allFieldsSetAsDisabled_disabledFieldsShouldReturnAllFields() { + // Given + Map traceOptions = new HashMap<>(); + traceOptions.put("disableStorage", "true"); + traceOptions.put("disableMemory", "true"); + traceOptions.put("disableStack", "true"); + + // When + TraceOptions options = new TraceOptions(traceOptions); + + // Then + Assert.assertEquals(3, options.getDisabledFields().size()); + Assert.assertTrue(options.getDisabledFields().contains("storage")); + Assert.assertTrue(options.getDisabledFields().contains("memory")); + Assert.assertTrue(options.getDisabledFields().contains("stack")); + } + + @Test + public void testTraceOptions_anyFieldsSetAsDisabled_disabledFieldsShouldReturnEmptySet() { + // Given + Map traceOptions = new HashMap<>(); + traceOptions.put("disableStorages", "false"); + traceOptions.put("disablesMemory", "false"); + traceOptions.put("disableStack", "false"); + + // When + TraceOptions options = new TraceOptions(traceOptions); + + // Then + Assert.assertEquals(0, options.getDisabledFields().size()); + Assert.assertFalse(options.getDisabledFields().contains("storage")); + Assert.assertFalse(options.getDisabledFields().contains("memory")); + Assert.assertFalse(options.getDisabledFields().contains("stack")); + } + + @Test + public void testTraceOptions_someFieldsSetAsDisabled_disabledFieldsShouldReturnDisabledOnes() { + // Given + Map traceOptions = new HashMap<>(); + traceOptions.put("disableStorage", "true"); + traceOptions.put("disableMemory", "false"); + traceOptions.put("disableStack", "true"); + + // When + TraceOptions options = new TraceOptions(traceOptions); + + // Then + Assert.assertEquals(2, options.getDisabledFields().size()); + Assert.assertTrue(options.getDisabledFields().contains("storage")); + Assert.assertFalse(options.getDisabledFields().contains("memory")); + Assert.assertTrue(options.getDisabledFields().contains("stack")); + } + + @Test + public void testTraceOptions_nullTraceOptionsGiven_disabledFieldsAndUnsupportedOptionsShouldReturnEmptySet() { + // When + TraceOptions options = new TraceOptions(null); + + // Then + Assert.assertEquals(0, options.getDisabledFields().size()); + Assert.assertEquals(0, options.getUnsupportedOptions().size()); + } + + @Test + public void testTraceOptions_emptyTraceOptionsGiven_disabledFieldsAndUnsupportedOptionsShouldReturnEmptySet() { + // When + TraceOptions options = new TraceOptions(Collections.emptyMap()); + + // Then + Assert.assertEquals(0, options.getDisabledFields().size()); + Assert.assertEquals(0, options.getUnsupportedOptions().size()); + } + + @Test + public void testTraceOptions_unsupportedOptionsGiven_unsupportedOptionsShouldReturnAllOfThem() { + // Given + Map traceOptions = new HashMap<>(); + traceOptions.put("unsupportedOption.1", "1"); + traceOptions.put("unsupportedOption.2", null); + + // When + TraceOptions options = new TraceOptions(traceOptions); + + // Then + Assert.assertEquals(2, options.getUnsupportedOptions().size()); + Assert.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption.1")); + Assert.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption.2")); + } + + @Test + public void testTraceOptions_mixOfSupportedAndUnsupportedOptionsGiven_disabledFieldsAndUnsupportedOptionsShouldReturnOK() { + // Given + Map traceOptions = new HashMap<>(); + traceOptions.put("disableMemory", "true"); + traceOptions.put("disableStorage", "True"); // True != true but should also work + traceOptions.put("unsupportedOption.1", "1"); + traceOptions.put("unsupportedOption.2", null); + + // When + TraceOptions options = new TraceOptions(traceOptions); + + // Then + Assert.assertEquals(2, options.getDisabledFields().size()); + Assert.assertTrue(options.getDisabledFields().contains("storage")); + Assert.assertTrue(options.getDisabledFields().contains("memory")); + + Assert.assertEquals(2, options.getUnsupportedOptions().size()); + Assert.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption.1")); + Assert.assertTrue(options.getUnsupportedOptions().contains("unsupportedOption.2")); + } + +} diff --git a/rskj-core/src/test/java/org/ethereum/crypto/signature/Secp256k1ServiceTest.java b/rskj-core/src/test/java/org/ethereum/crypto/signature/Secp256k1ServiceTest.java index cc2987942c9..4b15ec9316e 100644 --- a/rskj-core/src/test/java/org/ethereum/crypto/signature/Secp256k1ServiceTest.java +++ b/rskj-core/src/test/java/org/ethereum/crypto/signature/Secp256k1ServiceTest.java @@ -222,7 +222,7 @@ public void testRecoverFromSignature_fixed_values_garbage() throws SignatureExce byte[] s = Arrays.concatenate(new byte[]{1}, ByteUtil.bigIntegerToBytes(this.s, 64)); byte[] r = Arrays.concatenate(new byte[]{1}, ByteUtil.bigIntegerToBytes(this.r, 64)); ECDSASignature signature = ECDSASignature.fromComponents(r, s, v); - ECKey key = this.getSecp256k1().recoverFromSignature(v, signature, messageHash, true); + ECKey key = this.getSecp256k1().recoverFromSignature(v - 27, signature, messageHash, true); assertNull(key); }