mirror of https://github.com/hyperledger/besu
Added NodeSmartContractV2PermissioningController (#1435)
* Added NodeSmartContractV2PermissioningController Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>pull/1473/head
parent
74bc50207a
commit
112e7535cc
@ -0,0 +1,37 @@ |
||||
/* |
||||
* 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.condition.perm; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.NodeSmartContractPermissioningV2Transactions; |
||||
|
||||
public class NodeSmartContractPermissioningV2Conditions { |
||||
|
||||
private final NodeSmartContractPermissioningV2Transactions transactions; |
||||
|
||||
public NodeSmartContractPermissioningV2Conditions( |
||||
final NodeSmartContractPermissioningV2Transactions transactions) { |
||||
this.transactions = transactions; |
||||
} |
||||
|
||||
public Condition connectionIsAllowed(final String address, final Node node) { |
||||
return new WaitForTrueResponse(transactions.isConnectionAllowed(address, node)); |
||||
} |
||||
|
||||
public Condition connectionIsForbidden(final String address, final Node node) { |
||||
return new WaitForFalseResponse(transactions.isConnectionAllowed(address, node)); |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
/* |
||||
* 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.transaction.perm; |
||||
|
||||
import static org.web3j.utils.Numeric.toHexString; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.core.Hash; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; |
||||
import org.hyperledger.besu.ethereum.permissioning.NodeSmartContractV2PermissioningController; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.account.Account; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.web3j.abi.FunctionEncoder; |
||||
import org.web3j.abi.datatypes.Function; |
||||
import org.web3j.crypto.RawTransaction; |
||||
import org.web3j.crypto.TransactionEncoder; |
||||
|
||||
public class NodeSmartContractPermissioningAllowNodeV2Transaction implements Transaction<Hash> { |
||||
|
||||
private final Account sender; |
||||
private final Address contractAddress; |
||||
private final Node node; |
||||
|
||||
public NodeSmartContractPermissioningAllowNodeV2Transaction( |
||||
final Account sender, final Address contractAddress, final Node node) { |
||||
this.sender = sender; |
||||
this.contractAddress = contractAddress; |
||||
this.node = node; |
||||
} |
||||
|
||||
@Override |
||||
public Hash execute(final NodeRequests node) { |
||||
final String signedTransactionData = signedTransactionData(); |
||||
try { |
||||
String hash = |
||||
node.eth().ethSendRawTransaction(signedTransactionData).send().getTransactionHash(); |
||||
return Hash.fromHexString(hash); |
||||
} catch (final IOException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private String signedTransactionData() { |
||||
final EnodeURL enodeURL = EnodeURL.fromURI(((RunnableNode) node).enodeUrl()); |
||||
final Bytes payload = createPayload(enodeURL); |
||||
|
||||
RawTransaction transaction = |
||||
RawTransaction.createTransaction( |
||||
sender.getNextNonce(), |
||||
BigInteger.valueOf(1_000L), |
||||
BigInteger.valueOf(3_000_000L), |
||||
contractAddress.toString(), |
||||
payload.toString()); |
||||
|
||||
return toHexString( |
||||
TransactionEncoder.signMessage(transaction, sender.web3jCredentialsOrThrow())); |
||||
} |
||||
|
||||
private Bytes createPayload(final EnodeURL enodeUrl) { |
||||
try { |
||||
final String hexNodeIdString = enodeUrl.getNodeId().toUnprefixedHexString(); |
||||
final byte[] ip = NodeSmartContractV2PermissioningController.encodeIp(enodeUrl.getIp()); |
||||
final int port = enodeUrl.getListeningPortOrZero(); |
||||
|
||||
final Function addNodeFunction = |
||||
FunctionEncoder.makeFunction( |
||||
"addEnode", |
||||
List.of("string", "bytes16", "uint16"), |
||||
List.of(hexNodeIdString, ip, port), |
||||
Collections.emptyList()); |
||||
return Bytes.fromHexString(FunctionEncoder.encode(addNodeFunction)); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("Error adding node to allowlist", e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,83 @@ |
||||
/* |
||||
* 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.transaction.perm; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; |
||||
import org.hyperledger.besu.ethereum.permissioning.NodeSmartContractV2PermissioningController; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.web3j.abi.FunctionEncoder; |
||||
import org.web3j.abi.datatypes.Function; |
||||
import org.web3j.protocol.core.DefaultBlockParameterName; |
||||
|
||||
public class NodeSmartContractPermissioningConnectionIsAllowedV2Transaction |
||||
implements Transaction<Boolean> { |
||||
|
||||
private final Address contractAddress; |
||||
private final Node node; |
||||
|
||||
public NodeSmartContractPermissioningConnectionIsAllowedV2Transaction( |
||||
final Address contractAddress, final Node node) { |
||||
this.contractAddress = contractAddress; |
||||
this.node = node; |
||||
} |
||||
|
||||
@Override |
||||
public Boolean execute(final NodeRequests node) { |
||||
try { |
||||
final String value = |
||||
node.eth().ethCall(payload(), DefaultBlockParameterName.LATEST).send().getValue(); |
||||
return Bytes.fromHexString(value) |
||||
.equals(NodeSmartContractV2PermissioningController.TRUE_RESPONSE); |
||||
} catch (final IOException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private org.web3j.protocol.core.methods.request.Transaction payload() { |
||||
final EnodeURL enodeURL = EnodeURL.fromURI(((RunnableNode) node).enodeUrl()); |
||||
final Bytes payload = createPayload(enodeURL); |
||||
|
||||
return org.web3j.protocol.core.methods.request.Transaction.createFunctionCallTransaction( |
||||
null, null, null, null, contractAddress.toString(), payload.toString()); |
||||
} |
||||
|
||||
private Bytes createPayload(final EnodeURL enodeUrl) { |
||||
try { |
||||
final String hexNodeIdString = enodeUrl.getNodeId().toUnprefixedHexString(); |
||||
final byte[] ip = NodeSmartContractV2PermissioningController.encodeIp(enodeUrl.getIp()); |
||||
final int port = enodeUrl.getListeningPortOrZero(); |
||||
|
||||
final Function connectionAllowedFunction = |
||||
FunctionEncoder.makeFunction( |
||||
"connectionAllowed", |
||||
List.of("string", "bytes16", "uint16"), |
||||
List.of(hexNodeIdString, ip, port), |
||||
Collections.emptyList()); |
||||
return Bytes.fromHexString(FunctionEncoder.encode(connectionAllowedFunction)); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("Error calling connectionAllowed", e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
/* |
||||
* 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.transaction.perm; |
||||
|
||||
import static org.web3j.utils.Numeric.toHexString; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.core.Hash; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; |
||||
import org.hyperledger.besu.ethereum.permissioning.NodeSmartContractV2PermissioningController; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.account.Account; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.web3j.abi.FunctionEncoder; |
||||
import org.web3j.abi.datatypes.Function; |
||||
import org.web3j.crypto.RawTransaction; |
||||
import org.web3j.crypto.TransactionEncoder; |
||||
|
||||
public class NodeSmartContractPermissioningForbidNodeV2Transaction implements Transaction<Hash> { |
||||
|
||||
private final Account sender; |
||||
private final Address contractAddress; |
||||
private final Node node; |
||||
|
||||
public NodeSmartContractPermissioningForbidNodeV2Transaction( |
||||
final Account sender, final Address contractAddress, final Node node) { |
||||
this.sender = sender; |
||||
this.contractAddress = contractAddress; |
||||
this.node = node; |
||||
} |
||||
|
||||
@Override |
||||
public Hash execute(final NodeRequests node) { |
||||
final String signedTransactionData = signedTransactionData(); |
||||
try { |
||||
String hash = |
||||
node.eth().ethSendRawTransaction(signedTransactionData).send().getTransactionHash(); |
||||
return Hash.fromHexString(hash); |
||||
} catch (final IOException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private String signedTransactionData() { |
||||
final EnodeURL enodeURL = EnodeURL.fromURI(((RunnableNode) node).enodeUrl()); |
||||
final Bytes payload = createPayload(enodeURL); |
||||
|
||||
RawTransaction transaction = |
||||
RawTransaction.createTransaction( |
||||
sender.getNextNonce(), |
||||
BigInteger.valueOf(1000), |
||||
BigInteger.valueOf(3_000_000L), |
||||
contractAddress.toString(), |
||||
payload.toString()); |
||||
|
||||
return toHexString( |
||||
TransactionEncoder.signMessage(transaction, sender.web3jCredentialsOrThrow())); |
||||
} |
||||
|
||||
private Bytes createPayload(final EnodeURL enodeUrl) { |
||||
try { |
||||
final String hexNodeIdString = enodeUrl.getNodeId().toUnprefixedHexString(); |
||||
final byte[] ip = NodeSmartContractV2PermissioningController.encodeIp(enodeUrl.getIp()); |
||||
final int port = enodeUrl.getListeningPortOrZero(); |
||||
|
||||
final Function removeNodeFunction = |
||||
FunctionEncoder.makeFunction( |
||||
"removeEnode", |
||||
List.of("string", "bytes16", "uint16"), |
||||
List.of(hexNodeIdString, ip, port), |
||||
Collections.emptyList()); |
||||
return Bytes.fromHexString(FunctionEncoder.encode(removeNodeFunction)); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("Error removing node from allowlist", e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
/* |
||||
* 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.transaction.perm; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.core.Hash; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
|
||||
public class NodeSmartContractPermissioningV2Transactions { |
||||
|
||||
private final Accounts accounts; |
||||
|
||||
public NodeSmartContractPermissioningV2Transactions(final Accounts accounts) { |
||||
this.accounts = accounts; |
||||
} |
||||
|
||||
public Transaction<Hash> allowNode(final String contractAddress, final Node node) { |
||||
return new NodeSmartContractPermissioningAllowNodeV2Transaction( |
||||
accounts.getPrimaryBenefactor(), Address.fromHexString(contractAddress), node); |
||||
} |
||||
|
||||
public Transaction<Hash> forbidNode(final String contractAddress, final Node node) { |
||||
return new NodeSmartContractPermissioningForbidNodeV2Transaction( |
||||
accounts.getPrimaryBenefactor(), Address.fromHexString(contractAddress), node); |
||||
} |
||||
|
||||
public Transaction<Boolean> isConnectionAllowed(final String contractAddress, final Node node) { |
||||
return new NodeSmartContractPermissioningConnectionIsAllowedV2Transaction( |
||||
Address.fromHexString(contractAddress), node); |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
pragma solidity >=0.4.0 <0.6.0; |
||||
// THIS CONTRACT IS FOR TESTING PURPOSES ONLY |
||||
// DO NOT USE THIS CONTRACT IN PRODUCTION APPLICATIONS |
||||
|
||||
contract SimpleNodePermissioning { |
||||
/* |
||||
Node port is not being considered because ATs use random ports |
||||
*/ |
||||
struct Enode { |
||||
string enodeId; |
||||
bytes16 enodeHost; |
||||
} |
||||
mapping(bytes => Enode) private allowlist; |
||||
|
||||
// Port is being ignored |
||||
function connectionAllowed(string memory enodeId, bytes16 ip, uint16) public view returns (bool) { |
||||
return enodeAllowed(enodeId, ip); |
||||
} |
||||
|
||||
function enodeAllowed(string memory enodeId, bytes16 sourceEnodeIp) private view returns (bool){ |
||||
bytes memory key = computeKey(enodeId, sourceEnodeIp); |
||||
// if enode was found, host (bytes) is greater than zero |
||||
return allowlist[key].enodeHost > 0; |
||||
} |
||||
|
||||
function addEnode(string memory enodeId, bytes16 enodeIp, uint16) public { |
||||
Enode memory newEnode = Enode(enodeId, enodeIp); |
||||
bytes memory key = computeKey(enodeId, enodeIp); |
||||
allowlist[key] = newEnode; |
||||
} |
||||
|
||||
function removeEnode(string memory enodeId, bytes16 enodeIp, uint16) public { |
||||
bytes memory key = computeKey(enodeId, enodeIp); |
||||
Enode memory zeros = Enode("", bytes16(0)); |
||||
// replace enode with "zeros" |
||||
allowlist[key] = zeros; |
||||
} |
||||
|
||||
function computeKey(string memory enodeId, bytes16 enodeIp) private pure returns (bytes memory) { |
||||
return abi.encode(enodeId, enodeIp); |
||||
} |
||||
} |
@ -0,0 +1,109 @@ |
||||
/* |
||||
* 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.permissioning; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class NodeSmartContractPermissioningV2AcceptanceTest |
||||
extends NodeSmartContractPermissioningV2AcceptanceTestBase { |
||||
|
||||
private Node bootnode; |
||||
private Node permissionedNode; |
||||
private Node allowedNode; |
||||
private Node forbiddenNode; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
bootnode = bootnode("bootnode"); |
||||
forbiddenNode = node("forbidden-node"); |
||||
allowedNode = node("allowed-node"); |
||||
permissionedNode = permissionedNode("permissioned-node"); |
||||
|
||||
permissionedCluster.start(bootnode, forbiddenNode, allowedNode, permissionedNode); |
||||
|
||||
// updating permissioning smart contract with allowed nodes
|
||||
|
||||
permissionedNode.execute(allowNode(bootnode)); |
||||
permissionedNode.verify(connectionIsAllowed(bootnode)); |
||||
|
||||
permissionedNode.execute(allowNode(allowedNode)); |
||||
permissionedNode.verify(connectionIsAllowed(allowedNode)); |
||||
|
||||
permissionedNode.execute(allowNode(permissionedNode)); |
||||
permissionedNode.verify(connectionIsAllowed(permissionedNode)); |
||||
} |
||||
|
||||
@Test |
||||
public void permissionedNodeShouldPeerOnlyWithAllowedNodes() { |
||||
bootnode.verify(net.awaitPeerCount(3)); |
||||
allowedNode.verify(net.awaitPeerCount(3)); |
||||
forbiddenNode.verify(net.awaitPeerCount(2)); |
||||
permissionedNode.verify(net.awaitPeerCount(2)); |
||||
} |
||||
|
||||
@Test |
||||
public void permissionedNodeShouldDisconnectFromNodeNotPermittedAnymore() { |
||||
permissionedNode.verify(admin.addPeer(bootnode)); |
||||
permissionedNode.verify(admin.addPeer(allowedNode)); |
||||
permissionedNode.verify(net.awaitPeerCount(2)); |
||||
|
||||
permissionedNode.execute(forbidNode(allowedNode)); |
||||
permissionedNode.verify(connectionIsForbidden(allowedNode)); |
||||
|
||||
permissionedNode.verify(net.awaitPeerCount(1)); |
||||
} |
||||
|
||||
@Test |
||||
public void permissionedNodeShouldConnectToNewlyPermittedNode() { |
||||
permissionedNode.verify(admin.addPeer(bootnode)); |
||||
permissionedNode.verify(admin.addPeer(allowedNode)); |
||||
permissionedNode.verify(net.awaitPeerCount(2)); |
||||
|
||||
permissionedNode.execute(allowNode(forbiddenNode)); |
||||
permissionedNode.verify(connectionIsAllowed(forbiddenNode)); |
||||
permissionedNode.verify(admin.addPeer(forbiddenNode)); |
||||
|
||||
permissionedNode.verify(net.awaitPeerCount(3)); |
||||
} |
||||
|
||||
@Test |
||||
public void permissioningUpdatesPropagateThroughNetwork() { |
||||
permissionedNode.verify(admin.addPeer(bootnode)); |
||||
permissionedNode.verify(admin.addPeer(allowedNode)); |
||||
permissionedNode.verify(net.awaitPeerCount(2)); |
||||
|
||||
// permissioning changes in peer should propagate to permissioned node
|
||||
allowedNode.execute(allowNode(forbiddenNode)); |
||||
allowedNode.verify(connectionIsAllowed(forbiddenNode)); |
||||
permissionedNode.verify(connectionIsAllowed(forbiddenNode)); |
||||
|
||||
permissionedNode.verify(admin.addPeer(forbiddenNode)); |
||||
permissionedNode.verify(net.awaitPeerCount(3)); |
||||
} |
||||
|
||||
@Test |
||||
public void onChainPermissioningAllowlistShouldPersistAcrossRestarts() { |
||||
permissionedCluster.stop(); |
||||
permissionedCluster.start(bootnode, forbiddenNode, allowedNode, permissionedNode); |
||||
|
||||
permissionedNode.verify(connectionIsAllowed(allowedNode)); |
||||
permissionedNode.verify(connectionIsAllowed(bootnode)); |
||||
permissionedNode.verify(connectionIsAllowed(permissionedNode)); |
||||
permissionedNode.verify(connectionIsForbidden(forbiddenNode)); |
||||
} |
||||
} |
@ -0,0 +1,136 @@ |
||||
/* |
||||
* 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.permissioning; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Hash; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.perm.NodeSmartContractPermissioningV2Conditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.permissioning.PermissionedNodeBuilder; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.NodeSmartContractPermissioningV2Transactions; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigInteger; |
||||
|
||||
class NodeSmartContractPermissioningV2AcceptanceTestBase extends AcceptanceTestBase { |
||||
|
||||
private final NodeSmartContractPermissioningV2Transactions smartContractNodePermissioningV2; |
||||
private final NodeSmartContractPermissioningV2Conditions |
||||
nodeSmartContractPermissioningConditionsV2; |
||||
|
||||
protected static final String CONTRACT_ADDRESS = "0x0000000000000000000000000000000000009999"; |
||||
protected static final String GENESIS_FILE = |
||||
"/permissioning/simple_permissioning_v2_genesis.json"; |
||||
|
||||
protected final Cluster permissionedCluster; |
||||
|
||||
protected NodeSmartContractPermissioningV2AcceptanceTestBase() { |
||||
super(); |
||||
smartContractNodePermissioningV2 = new NodeSmartContractPermissioningV2Transactions(accounts); |
||||
nodeSmartContractPermissioningConditionsV2 = |
||||
new NodeSmartContractPermissioningV2Conditions(smartContractNodePermissioningV2); |
||||
|
||||
this.permissionedCluster = permissionedCluster(); |
||||
} |
||||
|
||||
private Cluster permissionedCluster() { |
||||
final ClusterConfiguration clusterConfiguration = |
||||
new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); |
||||
return new Cluster(clusterConfiguration, net); |
||||
} |
||||
|
||||
protected Node permissionedNode(final String name, final Node... localConfigAllowedNodes) { |
||||
return permissionedNode(name, GENESIS_FILE, localConfigAllowedNodes); |
||||
} |
||||
|
||||
protected Node permissionedNode( |
||||
final String name, final String genesisFile, final Node... localConfigAllowedNodes) { |
||||
PermissionedNodeBuilder permissionedNodeBuilder = |
||||
this.permissionedNodeBuilder |
||||
.name(name) |
||||
.genesisFile(genesisFile) |
||||
.nodesContractV2Enabled(CONTRACT_ADDRESS); |
||||
if (localConfigAllowedNodes != null && localConfigAllowedNodes.length > 0) { |
||||
permissionedNodeBuilder.nodesPermittedInConfig(localConfigAllowedNodes); |
||||
} |
||||
return permissionedNodeBuilder.build(); |
||||
} |
||||
|
||||
protected Node bootnode(final String name) { |
||||
return bootnode(name, GENESIS_FILE); |
||||
} |
||||
|
||||
protected Node bootnode(final String name, final String genesisFile) { |
||||
try { |
||||
return besu.createCustomGenesisNode(name, genesisFile, true); |
||||
} catch (IOException e) { |
||||
throw new RuntimeException("Error creating node", e); |
||||
} |
||||
} |
||||
|
||||
protected Node node(final String name) { |
||||
try { |
||||
return besu.createCustomGenesisNode(name, GENESIS_FILE, false); |
||||
} catch (IOException e) { |
||||
throw new RuntimeException("Error creating node", e); |
||||
} |
||||
} |
||||
|
||||
protected Node miner(final String name) { |
||||
try { |
||||
return besu.createCustomGenesisNode(name, GENESIS_FILE, false, true); |
||||
} catch (IOException e) { |
||||
throw new RuntimeException("Error creating node", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void tearDownAcceptanceTestBase() { |
||||
permissionedCluster.stop(); |
||||
super.tearDownAcceptanceTestBase(); |
||||
} |
||||
|
||||
protected Transaction<Hash> allowNode(final Node node) { |
||||
return smartContractNodePermissioningV2.allowNode(CONTRACT_ADDRESS, node); |
||||
} |
||||
|
||||
protected Transaction<Hash> forbidNode(final Node node) { |
||||
return smartContractNodePermissioningV2.forbidNode(CONTRACT_ADDRESS, node); |
||||
} |
||||
|
||||
protected Condition connectionIsForbidden(final Node node) { |
||||
return nodeSmartContractPermissioningConditionsV2.connectionIsForbidden(CONTRACT_ADDRESS, node); |
||||
} |
||||
|
||||
protected Condition connectionIsAllowed(final Node node) { |
||||
return nodeSmartContractPermissioningConditionsV2.connectionIsAllowed(CONTRACT_ADDRESS, node); |
||||
} |
||||
|
||||
protected void waitForBlockHeight(final Node node, final long blockchainHeight) { |
||||
WaitUtils.waitFor( |
||||
120, |
||||
() -> |
||||
assertThat(node.execute(ethTransactions.blockNumber())) |
||||
.isGreaterThanOrEqualTo(BigInteger.valueOf(blockchainHeight))); |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
{ |
||||
"config": { |
||||
"chainId": 999, |
||||
"homesteadBlock": 0, |
||||
"daoForkBlock": 0, |
||||
"eip150Block": 0, |
||||
"eip155Block": 0, |
||||
"eip158Block": 0, |
||||
"byzantiumBlock": 0, |
||||
"constantinopleBlock": 0, |
||||
"constantinopleFixBlock": 0, |
||||
"ethash": { |
||||
"fixeddifficulty": 100 |
||||
} |
||||
}, |
||||
"nonce": "0x42", |
||||
"timestamp": "0x0", |
||||
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", |
||||
"gasLimit": "0x1000000", |
||||
"difficulty": "0x10000", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"coinbase": "0x0000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"fe3b557e8fb62b89f4916b721be55ceb828dbd73": { |
||||
"privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", |
||||
"comment": "private key and this comment are ignoredS. In a real chain, the private key should NOT be stored", |
||||
"balance": "0xad78ebc5ac6200000" |
||||
}, |
||||
"627306090abaB3A6e1400e9345bC60c78a8BEf57": { |
||||
"privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", |
||||
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", |
||||
"balance": "90000000000000000000000" |
||||
}, |
||||
"0x0000000000000000000000000000000000009999": { |
||||
"comment": "Simple node permissioning smart contract V2", |
||||
"balance": "0", |
||||
"code": "608060405234801561001057600080fd5b50600436106100415760003560e01c80634df2430d146100465780636868e0d114610144578063d0515df61461022a575b600080fd5b61012a6004803603606081101561005c57600080fd5b810190808035906020019064010000000081111561007957600080fd5b82018360208201111561008b57600080fd5b803590602001918460018302840111640100000000831117156100ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080356fffffffffffffffffffffffffffffffff19169060200190929190803561ffff169060200190929190505050610310565b604051808215151515815260200191505060405180910390f35b6102286004803603606081101561015a57600080fd5b810190808035906020019064010000000081111561017757600080fd5b82018360208201111561018957600080fd5b803590602001918460018302840111640100000000831117156101ab57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080356fffffffffffffffffffffffffffffffff19169060200190929190803561ffff169060200190929190505050610325565b005b61030e6004803603606081101561024057600080fd5b810190808035906020019064010000000081111561025d57600080fd5b82018360208201111561026f57600080fd5b8035906020019184600183028401116401000000008311171561029157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080356fffffffffffffffffffffffffffffffff19169060200190929190803561ffff169060200190929190505050610439565b005b600061031c848461053a565b90509392505050565b606061033184846105e6565b905061033b6106a3565b6040518060400160405280604051806020016040528060008152508152602001600060801b6fffffffffffffffffffffffffffffffff19168152509050806000836040518082805190602001908083835b602083106103af578051825260208201915060208101905060208303925061038c565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060008201518160000190805190602001906103fe9291906106d0565b5060208201518160010160006101000a8154816fffffffffffffffffffffffffffffffff021916908360801c02179055509050505050505050565b6104416106a3565b6040518060400160405280858152602001846fffffffffffffffffffffffffffffffff19168152509050606061047785856105e6565b9050816000826040518082805190602001908083835b602083106104b0578051825260208201915060208101905060208303925061048d565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060008201518160000190805190602001906104ff9291906106d0565b5060208201518160010160006101000a8154816fffffffffffffffffffffffffffffffff021916908360801c02179055509050505050505050565b6000606061054884846105e6565b9050600060801b6000826040518082805190602001908083835b602083106105855780518252602082019150602081019050602083039250610562565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060010160009054906101000a900460801b6fffffffffffffffffffffffffffffffff19161191505092915050565b606082826040516020018080602001836fffffffffffffffffffffffffffffffff19166fffffffffffffffffffffffffffffffff19168152602001828103825284818151815260200191508051906020019080838360005b8381101561065957808201518184015260208101905061063e565b50505050905090810190601f1680156106865780820380516001836020036101000a031916815260200191505b509350505050604051602081830303815290604052905092915050565b60405180604001604052806060815260200160006fffffffffffffffffffffffffffffffff191681525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061071157805160ff191683800117855561073f565b8280016001018555821561073f579182015b8281111561073e578251825591602001919060010190610723565b5b50905061074c9190610750565b5090565b61077291905b8082111561076e576000816000905550600101610756565b5090565b9056fea265627a7a7230582059e21fece50f9faa9389e7ddaac9b6e34e20f4283c820bef12ea74bb18ecd7a664736f6c63430005090032", |
||||
"storage": {} |
||||
}, |
||||
"0x0000000000000000000000000000000000008888": { |
||||
"comment": "Simple account permissioning smart contract", |
||||
"balance": "0", |
||||
"code": "608060405260043610610067576000357c010000000000000000000000000000000000000000000000000000000090048063936421d51461006c578063c4740a95146101aa578063de8fa431146101fb578063e1ab6e8e14610226578063e89b0e1e1461028f575b600080fd5b34801561007857600080fd5b50610190600480360360c081101561008f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190929190803590602001909291908035906020019064010000000081111561010a57600080fd5b82018360208201111561011c57600080fd5b8035906020019184600183028401116401000000008311171561013e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102e0565b604051808215151515815260200191505060405180910390f35b3480156101b657600080fd5b506101f9600480360360208110156101cd57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506102f7565b005b34801561020757600080fd5b506102106103b6565b6040518082815260200191505060405180910390f35b34801561023257600080fd5b506102756004803603602081101561024957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506103c0565b604051808215151515815260200191505060405180910390f35b34801561029b57600080fd5b506102de600480360360208110156102b257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610429565b005b60006102eb876103c0565b90509695505050505050565b6000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156103b35760008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550600160008154809291906001900391905055505b50565b6000600154905090565b60008060015414156103d55760019050610424565b6000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1690505b919050565b6000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615156104e55760016000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506001600081548092919060010191905055505b5056fea165627a7a72305820b2abf4388b5665d346f59eb2f3cdf3209a76e6dca7252764df886e9eef811bd50029", |
||||
"storage": {} |
||||
} |
||||
}, |
||||
"number": "0x0", |
||||
"gasUsed": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
@ -0,0 +1,109 @@ |
||||
/* |
||||
* 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.permissioning; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; |
||||
import org.hyperledger.besu.ethereum.permissioning.node.NodePermissioningProvider; |
||||
import org.hyperledger.besu.ethereum.transaction.CallParameter; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; |
||||
import org.hyperledger.besu.metrics.BesuMetricCategory; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
import org.hyperledger.besu.plugin.services.metrics.Counter; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public abstract class AbstractNodeSmartContractPermissioningController |
||||
implements NodePermissioningProvider { |
||||
|
||||
protected final Address contractAddress; |
||||
protected final TransactionSimulator transactionSimulator; |
||||
|
||||
private final Counter checkCounter; |
||||
private final Counter checkCounterPermitted; |
||||
private final Counter checkCounterUnpermitted; |
||||
|
||||
/** |
||||
* Creates a permissioning controller attached to a blockchain |
||||
* |
||||
* @param contractAddress The address at which the permissioning smart contract resides |
||||
* @param transactionSimulator A transaction simulator with attached blockchain and world state |
||||
* @param metricsSystem The metrics provider that is to be reported to |
||||
*/ |
||||
protected AbstractNodeSmartContractPermissioningController( |
||||
final Address contractAddress, |
||||
final TransactionSimulator transactionSimulator, |
||||
final MetricsSystem metricsSystem) { |
||||
this.contractAddress = contractAddress; |
||||
this.transactionSimulator = transactionSimulator; |
||||
|
||||
this.checkCounter = |
||||
metricsSystem.createCounter( |
||||
BesuMetricCategory.PERMISSIONING, |
||||
"node_smart_contract_check_count", |
||||
"Number of times the node smart contract permissioning provider has been checked"); |
||||
this.checkCounterPermitted = |
||||
metricsSystem.createCounter( |
||||
BesuMetricCategory.PERMISSIONING, |
||||
"node_smart_contract_check_count_permitted", |
||||
"Number of times the node smart contract permissioning provider has been checked and returned permitted"); |
||||
this.checkCounterUnpermitted = |
||||
metricsSystem.createCounter( |
||||
BesuMetricCategory.PERMISSIONING, |
||||
"node_smart_contract_check_count_unpermitted", |
||||
"Number of times the node smart contract permissioning provider has been checked and returned unpermitted"); |
||||
} |
||||
|
||||
/** |
||||
* Check whether a given connection from the source to destination enode should be permitted |
||||
* |
||||
* @param sourceEnode The enode url of the node initiating the connection |
||||
* @param destinationEnode The enode url of the node receiving the connection |
||||
* @return boolean of whether or not to permit the connection to occur |
||||
*/ |
||||
@Override |
||||
public boolean isPermitted(final EnodeURL sourceEnode, final EnodeURL destinationEnode) { |
||||
this.checkCounter.inc(); |
||||
|
||||
if (!isContractDeployed()) { |
||||
throw new IllegalStateException("Permissioning contract does not exist"); |
||||
} |
||||
|
||||
if (checkSmartContractRules(sourceEnode, destinationEnode)) { |
||||
this.checkCounterPermitted.inc(); |
||||
return true; |
||||
} else { |
||||
this.checkCounterUnpermitted.inc(); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private boolean isContractDeployed() { |
||||
final Optional<Boolean> contractExists = |
||||
transactionSimulator.doesAddressExistAtHead(contractAddress); |
||||
|
||||
return contractExists.isPresent() && contractExists.get(); |
||||
} |
||||
|
||||
abstract boolean checkSmartContractRules( |
||||
final EnodeURL sourceEnode, final EnodeURL destinationEnode); |
||||
|
||||
protected CallParameter buildCallParameters(final Bytes payload) { |
||||
// Call parameters for simulation don't need other parameters, only the address and the payload
|
||||
return new CallParameter(null, contractAddress, -1, null, null, payload); |
||||
} |
||||
} |
@ -0,0 +1,123 @@ |
||||
/* |
||||
* 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.permissioning; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; |
||||
import org.hyperledger.besu.ethereum.transaction.CallParameter; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.util.List; |
||||
|
||||
import com.google.common.annotations.VisibleForTesting; |
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.web3j.abi.FunctionEncoder; |
||||
import org.web3j.abi.TypeEncoder; |
||||
import org.web3j.abi.datatypes.Bool; |
||||
import org.web3j.abi.datatypes.Function; |
||||
|
||||
/** |
||||
* Controller that can read from a smart contract that exposes the EEA node permissioning v2 call |
||||
* connectionAllowed(string,bytes16,uint16) |
||||
*/ |
||||
public class NodeSmartContractV2PermissioningController |
||||
extends AbstractNodeSmartContractPermissioningController { |
||||
|
||||
public static final Bytes TRUE_RESPONSE = Bytes.fromHexString(TypeEncoder.encode(new Bool(true))); |
||||
public static final Bytes FALSE_RESPONSE = |
||||
Bytes.fromHexString(TypeEncoder.encode(new Bool(false))); |
||||
|
||||
public NodeSmartContractV2PermissioningController( |
||||
final Address contractAddress, |
||||
final TransactionSimulator transactionSimulator, |
||||
final MetricsSystem metricsSystem) { |
||||
super(contractAddress, transactionSimulator, metricsSystem); |
||||
} |
||||
|
||||
@Override |
||||
boolean checkSmartContractRules(final EnodeURL sourceEnode, final EnodeURL destinationEnode) { |
||||
return isPermitted(sourceEnode) && isPermitted(destinationEnode); |
||||
} |
||||
|
||||
private boolean isPermitted(final EnodeURL enode) { |
||||
final Bytes payload = createPayload(enode); |
||||
final CallParameter callParams = buildCallParameters(payload); |
||||
|
||||
return transactionSimulator.processAtHead(callParams).map(this::parseResult).orElse(false); |
||||
} |
||||
|
||||
private Bytes createPayload(final EnodeURL enodeUrl) { |
||||
try { |
||||
final String hexNodeIdString = enodeUrl.getNodeId().toUnprefixedHexString(); |
||||
final byte[] ip = encodeIp(enodeUrl.getIp()); |
||||
final int port = enodeUrl.getListeningPortOrZero(); |
||||
|
||||
final Function connectionAllowedFunction = |
||||
FunctionEncoder.makeFunction( |
||||
"connectionAllowed", |
||||
List.of("string", "bytes16", "uint16"), |
||||
List.of(hexNodeIdString, ip, port), |
||||
List.of(Bool.TYPE_NAME)); |
||||
return Bytes.fromHexString(FunctionEncoder.encode(connectionAllowedFunction)); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException( |
||||
"Error building payload to call node permissioning smart contract", e); |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* IPv4-Compatible IPv6 Address That hybrid address consists of 10 "0" bytes, followed by two "1" |
||||
* bytes, followed by the original 4-bytes IPv4 address (RFC 4291 Section 2.5.5.1 - |
||||
* https://tools.ietf.org/html/rfc4291#section-2.5.5)
|
||||
*/ |
||||
@VisibleForTesting |
||||
public static byte[] encodeIp(final InetAddress addr) { |
||||
// InetAddress deals with giving us the right number of bytes
|
||||
final byte[] address = addr.getAddress(); |
||||
final byte[] res = new byte[16]; |
||||
if (address.length == 4) { |
||||
// lead with 10 bytes of 0's then 2 bytes of 1's
|
||||
res[10] = (byte) 0xFF; |
||||
res[11] = (byte) 0xFF; |
||||
// then the ipv4
|
||||
System.arraycopy(address, 0, res, 12, 4); |
||||
} else { |
||||
System.arraycopy(address, 0, res, 0, address.length); |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
private boolean parseResult(final TransactionSimulatorResult result) { |
||||
switch (result.getResult().getStatus()) { |
||||
case INVALID: |
||||
throw new IllegalStateException("Invalid node permissioning smart contract call"); |
||||
case FAILED: |
||||
throw new IllegalStateException("Failed node permissioning smart contract call"); |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
if (result.getOutput().equals(TRUE_RESPONSE)) { |
||||
return true; |
||||
} else if (result.getOutput().equals(FALSE_RESPONSE)) { |
||||
return false; |
||||
} else { |
||||
throw new IllegalStateException("Unexpected result from node permissioning smart contract"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,173 @@ |
||||
/* |
||||
* 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.permissioning; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.times; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Address; |
||||
import org.hyperledger.besu.ethereum.core.BlockDataGenerator; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor.Result; |
||||
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; |
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL; |
||||
import org.hyperledger.besu.ethereum.transaction.CallParameter; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; |
||||
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; |
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class NodeSmartContractV2PermissioningControllerTest { |
||||
|
||||
/* |
||||
Payloads created using Remix to call method connectionAllowed(string, bytes16, uint16) |
||||
*/ |
||||
private static final Bytes SOURCE_ENODE_EXPECTED_PAYLOAD = |
||||
Bytes.fromHexString( |
||||
"0x4df2430d000000000000000000000000000000000000000000000000000000000000006000000000000000000000ffff7f00000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765f00000000000000000000000000000000000000000000000000000000000000806663626539663833323138343837623363306235303837383139333838306536633235636664383637303863306130626630636139316630636536333337343661383932666532343061666135623961383830623862636134386538613232373034656639333766646461326437636336336534643431656431623431376165"); |
||||
private static final Bytes DESTINATION_ENODE_EXPECTED_PAYLOAD = |
||||
Bytes.fromHexString( |
||||
"0x4df2430d000000000000000000000000000000000000000000000000000000000000006000000000000000000000ffff7f000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009dd400000000000000000000000000000000000000000000000000000000000000803335343863383762393932306666313661613462646366303163383566323531313761323961653135373464373539626164343863633934363364386539663763336331643165396662306432386537333839383935316639306530323731346162623737306664366432326539303337313838326134353635383830306539"); |
||||
|
||||
private static final EnodeURL SOURCE_ENODE_IPV4 = |
||||
EnodeURL.fromString( |
||||
"enode://fcbe9f83218487b3c0b50878193880e6c25cfd86708c0a0bf0ca91f0ce633746a892fe240afa5b9a880b8bca48e8a22704ef937fdda2d7cc63e4d41ed1b417ae@127.0.0.1:30303"); |
||||
private static final EnodeURL DESTINATION_ENODE_IPV4 = |
||||
EnodeURL.fromString( |
||||
"enode://3548c87b9920ff16aa4bdcf01c85f25117a29ae1574d759bad48cc9463d8e9f7c3c1d1e9fb0d28e73898951f90e02714abb770fd6d22e90371882a45658800e9@127.0.0.1:40404"); |
||||
private static final EnodeURL SOURCE_ENODE_IPV6 = |
||||
EnodeURL.fromString( |
||||
"enode://fcbe9f83218487b3c0b50878193880e6c25cfd86708c0a0bf0ca91f0ce633746a892fe240afa5b9a880b8bca48e8a22704ef937fdda2d7cc63e4d41ed1b417ae@[::ffff:7f00:0001]:30303"); |
||||
private static final EnodeURL DESTINATION_ENODE_IPV6 = |
||||
EnodeURL.fromString( |
||||
"enode://3548c87b9920ff16aa4bdcf01c85f25117a29ae1574d759bad48cc9463d8e9f7c3c1d1e9fb0d28e73898951f90e02714abb770fd6d22e90371882a45658800e9@[::ffff:7f00:0001]:40404"); |
||||
|
||||
private final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); |
||||
private final Address contractAddress = Address.ZERO; |
||||
private final TransactionSimulator transactionSimulator = mock(TransactionSimulator.class); |
||||
private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); |
||||
|
||||
private NodeSmartContractV2PermissioningController permissioningController; |
||||
|
||||
@Before |
||||
public void beforeEach() { |
||||
permissioningController = |
||||
new NodeSmartContractV2PermissioningController( |
||||
contractAddress, transactionSimulator, metricsSystem); |
||||
} |
||||
|
||||
@Test |
||||
public void nonExpectedCallOutputThrowsIllegalState() { |
||||
final TransactionSimulatorResult txSimulatorResult = |
||||
transactionSimulatorResult(Bytes.random(10), ValidationResult.valid()); |
||||
|
||||
when(transactionSimulator.processAtHead(eq(callParams(SOURCE_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
|
||||
assertThatIllegalStateException() |
||||
.isThrownBy( |
||||
() -> |
||||
permissioningController.checkSmartContractRules( |
||||
SOURCE_ENODE_IPV4, DESTINATION_ENODE_IPV4)); |
||||
} |
||||
|
||||
@Test |
||||
public void falseCallOutputReturnsNotPermitted() { |
||||
final TransactionSimulatorResult txSimulatorResult = |
||||
transactionSimulatorResult( |
||||
NodeSmartContractV2PermissioningController.FALSE_RESPONSE, ValidationResult.valid()); |
||||
|
||||
when(transactionSimulator.processAtHead(eq(callParams(SOURCE_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
|
||||
boolean isPermitted = |
||||
permissioningController.checkSmartContractRules(SOURCE_ENODE_IPV4, DESTINATION_ENODE_IPV4); |
||||
assertThat(isPermitted).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void failedCallOutputReturnsNotPermitted() { |
||||
final TransactionSimulatorResult txSimulatorResult = |
||||
transactionSimulatorResult( |
||||
NodeSmartContractV2PermissioningController.TRUE_RESPONSE, |
||||
ValidationResult.invalid(TransactionInvalidReason.INTERNAL_ERROR)); |
||||
|
||||
when(transactionSimulator.processAtHead(eq(callParams(SOURCE_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
|
||||
boolean isPermitted = |
||||
permissioningController.checkSmartContractRules(SOURCE_ENODE_IPV4, DESTINATION_ENODE_IPV4); |
||||
assertThat(isPermitted).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void expectedPayloadWhenCheckingPermissioningWithIPV4() { |
||||
final TransactionSimulatorResult txSimulatorResult = |
||||
transactionSimulatorResult( |
||||
NodeSmartContractV2PermissioningController.TRUE_RESPONSE, ValidationResult.valid()); |
||||
|
||||
when(transactionSimulator.processAtHead(eq(callParams(SOURCE_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
when(transactionSimulator.processAtHead(eq(callParams(DESTINATION_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
|
||||
boolean isPermitted = |
||||
permissioningController.checkSmartContractRules(SOURCE_ENODE_IPV4, DESTINATION_ENODE_IPV4); |
||||
assertThat(isPermitted).isTrue(); |
||||
|
||||
verify(transactionSimulator, times(2)).processAtHead(any()); |
||||
} |
||||
|
||||
@Test |
||||
public void expectedPayloadWhenCheckingPermissioningWithIPV6() { |
||||
final TransactionSimulatorResult txSimulatorResult = |
||||
transactionSimulatorResult( |
||||
NodeSmartContractV2PermissioningController.TRUE_RESPONSE, ValidationResult.valid()); |
||||
|
||||
when(transactionSimulator.processAtHead(eq(callParams(SOURCE_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
when(transactionSimulator.processAtHead(eq(callParams(DESTINATION_ENODE_EXPECTED_PAYLOAD)))) |
||||
.thenReturn(Optional.of(txSimulatorResult)); |
||||
|
||||
boolean isPermitted = |
||||
permissioningController.checkSmartContractRules(SOURCE_ENODE_IPV6, DESTINATION_ENODE_IPV6); |
||||
assertThat(isPermitted).isTrue(); |
||||
|
||||
verify(transactionSimulator, times(2)).processAtHead(any()); |
||||
} |
||||
|
||||
private CallParameter callParams(final Bytes payload) { |
||||
return new CallParameter(null, contractAddress, -1, null, null, payload); |
||||
} |
||||
|
||||
private TransactionSimulatorResult transactionSimulatorResult( |
||||
final Bytes output, final ValidationResult<TransactionInvalidReason> validationResult) { |
||||
return new TransactionSimulatorResult( |
||||
blockDataGenerator.transaction(), |
||||
Result.successful(blockDataGenerator.logs(1, 1), 0L, 0L, output, validationResult)); |
||||
} |
||||
} |
Loading…
Reference in new issue