diff --git a/CHANGELOG.md b/CHANGELOG.md index 72837156174..918a80d177f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## 1.4.1 +- Added priv_getCode [\#250](https://github.com/hyperledger/besu/pull/408). Gets the bytecode associated with a private address. ### Bug Fixes diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidContractCode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidContractCode.java new file mode 100644 index 00000000000..574ace120da --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidContractCode.java @@ -0,0 +1,29 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.web3j.tx.Contract; + +public class ExpectValidContractCode implements PrivateContractCondition { + @Override + public void verify(final Contract contract) throws IOException { + assertThat(contract).isNotNull(); + assertThat(contract.isValid()).isEqualTo(true); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java index b48a9fca049..7969e2d3576 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java @@ -14,8 +14,10 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; +import java.io.IOException; + import org.web3j.tx.Contract; public interface PrivateContractCondition { - void verify(final Contract contract); + void verify(final Contract contract) throws IOException; } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java index 755b1339040..de9f36d08d7 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java @@ -22,4 +22,8 @@ public ExpectValidPrivateContractDeployedReceipt validPrivateContractDeployed( final String contractAddress, final String senderAddress) { return new ExpectValidPrivateContractDeployedReceipt(contractAddress, senderAddress); } + + public ExpectValidContractCode validContractCodeProvided() { + return new ExpectValidContractCode(); + } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java index edbcc275b8e..57858c423d9 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java @@ -36,7 +36,7 @@ public void setUp() throws Exception { } @Test - public void deployingMustGiveValidReceipt() { + public void deployingMustGiveValidReceiptAndCode() throws Exception { final String contractAddress = "0x89ce396d0f9f937ddfa71113e29b2081c4869555"; final EventEmitter eventEmitter = @@ -50,5 +50,7 @@ public void deployingMustGiveValidReceipt() { privateContractVerifier .validPrivateContractDeployed(contractAddress, minerNode.getAddress().toString()) .verify(eventEmitter); + + privateContractVerifier.validContractCodeProvided().verify(eventEmitter); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index ac115342ab7..acce6a1e31d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -46,6 +46,7 @@ public enum RpcMethod { PRIV_FIND_PRIVACY_GROUP("priv_findPrivacyGroup"), PRIV_DISTRIBUTE_RAW_TRANSACTION("priv_distributeRawTransaction"), PRIV_GET_EEA_TRANSACTION_COUNT("priv_getEeaTransactionCount"), + PRIV_GET_CODE("priv_getCode"), EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"), ETH_ACCOUNTS("eth_accounts"), ETH_BLOCK_NUMBER("eth_blockNumber"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java new file mode 100644 index 00000000000..8c1a7a946aa --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java @@ -0,0 +1,85 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; + +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class PrivGetCode extends AbstractBlockParameterMethod { + + private final BlockchainQueries blockchain; + private final PrivateStateRootResolver privateStateRootResolver; + private final WorldStateArchive privateWorldStateArchive; + + public PrivGetCode( + final BlockchainQueries blockchainQueries, + final WorldStateArchive privateWorldStateArchive, + final PrivateStateRootResolver privateStateRootResolver) { + super(blockchainQueries); + this.privateWorldStateArchive = privateWorldStateArchive; + this.blockchain = blockchainQueries; + this.privateStateRootResolver = privateStateRootResolver; + } + + @Override + public String getName() { + return RpcMethod.PRIV_GET_CODE.getMethodName(); + } + + @Override + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { + return request.getRequiredParameter(2, BlockParameter.class); + } + + @Override + protected Object resultByBlockNumber( + final JsonRpcRequestContext request, final long blockNumber) { + final String privacyGroupId = request.getRequiredParameter(0, String.class); + + final Address address = Address.fromHexString(request.getRequiredParameter(1, String.class)); + + final Hash latestStateRoot = + privateStateRootResolver.resolveLastStateRoot( + Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)), + blockchain.getBlockchain().getBlockByNumber(blockNumber).orElseThrow().getHash()); + + return privateWorldStateArchive + .get(latestStateRoot) + .flatMap( + pws -> + Optional.ofNullable(pws.get(address)).map(account -> account.getCode().toString())) + .map(c -> new JsonRpcSuccessResponse(request.getRequest().getId(), c)) + .orElse(new JsonRpcSuccessResponse(request.getRequest().getId(), null)); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + return (JsonRpcResponse) findResultByParamType(requestContext); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java index ab9bac5462d..7cfdada586e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivDeletePrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivDistributeRawTransaction; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivFindPrivacyGroup; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetCode; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetPrivacyPrecompileAddress; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetPrivateTransaction; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionCount; @@ -32,6 +33,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.privacy.PrivacyController; +import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; import java.util.Map; @@ -68,6 +70,10 @@ protected Map create( new PrivGetPrivateTransaction( getBlockchainQueries(), privacyController, enclavePublicKeyProvider), new PrivDistributeRawTransaction(privacyController, enclavePublicKeyProvider), - new PrivCall(getBlockchainQueries(), privacyController, enclavePublicKeyProvider)); + new PrivCall(getBlockchainQueries(), privacyController, enclavePublicKeyProvider), + new PrivGetCode( + getBlockchainQueries(), + getPrivacyParameters().getPrivateWorldStateArchive(), + new PrivateStateRootResolver(getPrivacyParameters().getPrivateStateStorage()))); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCodeTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCodeTest.java new file mode 100644 index 00000000000..c3dec0dc3d8 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCodeTest.java @@ -0,0 +1,151 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Account; +import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Hash; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.core.WorldState; +import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; +import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; +import org.hyperledger.besu.ethereum.privacy.Restriction; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import java.math.BigInteger; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PrivGetCodeTest { + + private final Address sender = + Address.fromHexString("0x0000000000000000000000000000000000000003"); + private static final SECP256K1.KeyPair KEY_PAIR = + SECP256K1.KeyPair.create( + SECP256K1.PrivateKey.create( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); + + private final Bytes privacyGroupId = + Bytes.fromBase64String("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); + + private final PrivateTransaction.Builder privateTransactionBuilder = + PrivateTransaction.builder() + .nonce(0) + .gasPrice(Wei.of(1000)) + .gasLimit(3000000) + .to(null) + .value(Wei.ZERO) + .payload( + Bytes.fromBase64String( + "0x608060405234801561001057600080fd5b5060d08061001f60003960" + + "00f3fe60806040526004361060485763ffffffff7c01000000" + + "00000000000000000000000000000000000000000000000000" + + "60003504166360fe47b18114604d5780636d4ce63c14607557" + + "5b600080fd5b348015605857600080fd5b5060736004803603" + + "6020811015606d57600080fd5b50356099565b005b34801560" + + "8057600080fd5b506087609e565b6040805191825251908190" + + "0360200190f35b600055565b6000549056fea165627a7a7230" + + "5820cb1d0935d14b589300b12fcd0ab849a7e9019c81da24d6" + + "daa4f6b2f003d1b0180029")) + .sender(sender) + .chainId(BigInteger.valueOf(2018)) + .privateFrom(Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo=")) + .restriction(Restriction.RESTRICTED); + + private PrivGetCode method; + + @Mock private BlockchainQueries mockBlockchainQueries; + @Mock private PrivacyParameters mockPrivacyParameters; + @Mock private Blockchain mockBlockchain; + @Mock private Block mockBlock; + @Mock private Hash mockHash; + @Mock private PrivateStateRootResolver mockResolver; + @Mock private WorldStateArchive mockArchive; + @Mock private WorldState mockWorldState; + @Mock private Account mockAccount; + + private final Hash lastStateRoot = + Hash.fromHexString("0x2121b68f1333e93bae8cd717a3ca68c9d7e7003f6b288c36dfc59b0f87be9590"); + + private final Bytes fakeAccountCode = Bytes.fromBase64String("ZXhhbXBsZQ=="); + + @Before + public void before() { + method = + new PrivGetCode( + mockBlockchainQueries, + mockPrivacyParameters.getPrivateWorldStateArchive(), + mockResolver); + } + + @Test + public void returnValidCodeWhenCalledOnValidContract() { + PrivateTransaction transaction = + privateTransactionBuilder.privacyGroupId(privacyGroupId).signAndBuild(KEY_PAIR); + + Address resultantContractAddress = + Address.privateContractAddress( + transaction.getSender(), transaction.getNonce(), privacyGroupId); + + when(mockBlockchainQueries.getBlockchain()).thenReturn(mockBlockchain); + when(mockBlockchain.getChainHeadBlock()).thenReturn(mockBlock); + when(mockBlock.getHash()).thenReturn(mockHash); + when(mockResolver.resolveLastStateRoot(any(Bytes32.class), any(Hash.class))) + .thenReturn(lastStateRoot); + when(mockPrivacyParameters.getPrivateWorldStateArchive()).thenReturn(mockArchive); + when(mockArchive.get(lastStateRoot)).thenReturn(Optional.of(mockWorldState)); + when(mockWorldState.get(resultantContractAddress)).thenReturn(mockAccount); + when(mockAccount.getCode()).thenReturn(fakeAccountCode); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + "priv_getCode", + new Object[] { + "Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=", + resultantContractAddress.toHexString(), + "latest" + })); + + final JsonRpcResponse response = method.response(request); + + assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class); + assertThat(((JsonRpcSuccessResponse) response).getResult()) + .isEqualTo(fakeAccountCode.toString()); + } +} diff --git a/gradle/versions.gradle b/gradle/versions.gradle index df1c6865703..3b971e2eef0 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -95,10 +95,10 @@ dependencyManagement { dependency 'org.springframework.security:spring-security-crypto:5.2.1.RELEASE' - dependency 'org.web3j:abi:4.5.11' - dependency 'org.web3j:besu:4.5.11' - dependency 'org.web3j:core:4.5.11' - dependency 'org.web3j:crypto:4.5.11' + dependency 'org.web3j:abi:4.5.15' + dependency 'org.web3j:besu:4.5.15' + dependency 'org.web3j:core:4.5.15' + dependency 'org.web3j:crypto:4.5.15' dependency 'org.xerial.snappy:snappy-java:1.1.7.3'