mirror of https://github.com/hyperledger/besu
Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>pull/7386/head
commit
032c7fd2d5
@ -0,0 +1,193 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.ethereum; |
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
||||||
|
|
||||||
|
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; |
||||||
|
import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||||
|
import okhttp3.Call; |
||||||
|
import okhttp3.MediaType; |
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
import okhttp3.Request; |
||||||
|
import okhttp3.RequestBody; |
||||||
|
import okhttp3.Response; |
||||||
|
import org.web3j.protocol.core.methods.response.EthBlock; |
||||||
|
|
||||||
|
public class PragueAcceptanceTestHelper { |
||||||
|
protected static final MediaType MEDIA_TYPE_JSON = |
||||||
|
MediaType.parse("application/json; charset=utf-8"); |
||||||
|
|
||||||
|
private final OkHttpClient httpClient; |
||||||
|
private final ObjectMapper mapper; |
||||||
|
private final BesuNode besuNode; |
||||||
|
private final EthTransactions ethTransactions; |
||||||
|
|
||||||
|
private long blockTimeStamp = 0; |
||||||
|
|
||||||
|
PragueAcceptanceTestHelper(final BesuNode besuNode, final EthTransactions ethTransactions) { |
||||||
|
this.besuNode = besuNode; |
||||||
|
this.ethTransactions = ethTransactions; |
||||||
|
httpClient = new OkHttpClient(); |
||||||
|
mapper = new ObjectMapper(); |
||||||
|
} |
||||||
|
|
||||||
|
public void buildNewBlock() throws IOException { |
||||||
|
final EthBlock.Block block = besuNode.execute(ethTransactions.block()); |
||||||
|
|
||||||
|
blockTimeStamp += 1; |
||||||
|
final Call buildBlockRequest = |
||||||
|
createEngineCall(createForkChoiceRequest(block.getHash(), blockTimeStamp)); |
||||||
|
|
||||||
|
final String payloadId; |
||||||
|
try (final Response buildBlockResponse = buildBlockRequest.execute()) { |
||||||
|
payloadId = |
||||||
|
mapper |
||||||
|
.readTree(buildBlockResponse.body().string()) |
||||||
|
.get("result") |
||||||
|
.get("payloadId") |
||||||
|
.asText(); |
||||||
|
|
||||||
|
assertThat(payloadId).isNotEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
waitFor(500); |
||||||
|
|
||||||
|
final Call getPayloadRequest = createEngineCall(createGetPayloadRequest(payloadId)); |
||||||
|
|
||||||
|
final ObjectNode executionPayload; |
||||||
|
final String newBlockHash; |
||||||
|
final String parentBeaconBlockRoot; |
||||||
|
try (final Response getPayloadResponse = getPayloadRequest.execute()) { |
||||||
|
assertThat(getPayloadResponse.code()).isEqualTo(200); |
||||||
|
|
||||||
|
executionPayload = |
||||||
|
(ObjectNode) |
||||||
|
mapper |
||||||
|
.readTree(getPayloadResponse.body().string()) |
||||||
|
.get("result") |
||||||
|
.get("executionPayload"); |
||||||
|
|
||||||
|
newBlockHash = executionPayload.get("blockHash").asText(); |
||||||
|
parentBeaconBlockRoot = executionPayload.remove("parentBeaconBlockRoot").asText(); |
||||||
|
|
||||||
|
assertThat(newBlockHash).isNotEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
final Call newPayloadRequest = |
||||||
|
createEngineCall( |
||||||
|
createNewPayloadRequest(executionPayload.toString(), parentBeaconBlockRoot)); |
||||||
|
try (final Response newPayloadResponse = newPayloadRequest.execute()) { |
||||||
|
assertThat(newPayloadResponse.code()).isEqualTo(200); |
||||||
|
} |
||||||
|
|
||||||
|
final Call moveChainAheadRequest = createEngineCall(createForkChoiceRequest(newBlockHash)); |
||||||
|
|
||||||
|
try (final Response moveChainAheadResponse = moveChainAheadRequest.execute()) { |
||||||
|
assertThat(moveChainAheadResponse.code()).isEqualTo(200); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Call createEngineCall(final String request) { |
||||||
|
return httpClient.newCall( |
||||||
|
new Request.Builder() |
||||||
|
.url(besuNode.engineRpcUrl().get()) |
||||||
|
.post(RequestBody.create(request, MEDIA_TYPE_JSON)) |
||||||
|
.build()); |
||||||
|
} |
||||||
|
|
||||||
|
private String createForkChoiceRequest(final String blockHash) { |
||||||
|
return createForkChoiceRequest(blockHash, null); |
||||||
|
} |
||||||
|
|
||||||
|
private String createForkChoiceRequest(final String parentBlockHash, final Long timeStamp) { |
||||||
|
final Optional<Long> maybeTimeStamp = Optional.ofNullable(timeStamp); |
||||||
|
|
||||||
|
String forkChoiceRequest = |
||||||
|
"{" |
||||||
|
+ " \"jsonrpc\": \"2.0\"," |
||||||
|
+ " \"method\": \"engine_forkchoiceUpdatedV3\"," |
||||||
|
+ " \"params\": [" |
||||||
|
+ " {" |
||||||
|
+ " \"headBlockHash\": \"" |
||||||
|
+ parentBlockHash |
||||||
|
+ "\"," |
||||||
|
+ " \"safeBlockHash\": \"" |
||||||
|
+ parentBlockHash |
||||||
|
+ "\"," |
||||||
|
+ " \"finalizedBlockHash\": \"" |
||||||
|
+ parentBlockHash |
||||||
|
+ "\"" |
||||||
|
+ " }"; |
||||||
|
|
||||||
|
if (maybeTimeStamp.isPresent()) { |
||||||
|
forkChoiceRequest += |
||||||
|
" ,{" |
||||||
|
+ " \"timestamp\": \"" |
||||||
|
+ Long.toHexString(maybeTimeStamp.get()) |
||||||
|
+ "\"," |
||||||
|
+ " \"prevRandao\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"," |
||||||
|
+ " \"suggestedFeeRecipient\": \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\"," |
||||||
|
+ " \"withdrawals\": []," |
||||||
|
+ " \"parentBeaconBlockRoot\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"" |
||||||
|
+ " }"; |
||||||
|
} |
||||||
|
|
||||||
|
forkChoiceRequest += " ]," + " \"id\": 67" + "}"; |
||||||
|
|
||||||
|
return forkChoiceRequest; |
||||||
|
} |
||||||
|
|
||||||
|
private String createGetPayloadRequest(final String payloadId) { |
||||||
|
return "{" |
||||||
|
+ " \"jsonrpc\": \"2.0\"," |
||||||
|
+ " \"method\": \"engine_getPayloadV4\"," |
||||||
|
+ " \"params\": [\"" |
||||||
|
+ payloadId |
||||||
|
+ "\"]," |
||||||
|
+ " \"id\": 67" |
||||||
|
+ "}"; |
||||||
|
} |
||||||
|
|
||||||
|
private String createNewPayloadRequest( |
||||||
|
final String executionPayload, final String parentBeaconBlockRoot) { |
||||||
|
return "{" |
||||||
|
+ " \"jsonrpc\": \"2.0\"," |
||||||
|
+ " \"method\": \"engine_newPayloadV4\"," |
||||||
|
+ " \"params\": [" |
||||||
|
+ executionPayload |
||||||
|
+ ",[]," |
||||||
|
+ "\"" |
||||||
|
+ parentBeaconBlockRoot |
||||||
|
+ "\"" |
||||||
|
+ "]," |
||||||
|
+ " \"id\": 67" |
||||||
|
+ "}"; |
||||||
|
} |
||||||
|
|
||||||
|
private static void waitFor(final long durationMilliSeconds) { |
||||||
|
try { |
||||||
|
Thread.sleep(durationMilliSeconds); |
||||||
|
} catch (InterruptedException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.ethereum; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.SECP256K1; |
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.TransactionType; |
||||||
|
import org.hyperledger.besu.datatypes.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.core.SetCodeAuthorization; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; |
||||||
|
import org.hyperledger.besu.tests.acceptance.dsl.account.Account; |
||||||
|
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount; |
||||||
|
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.apache.tuweni.bytes.Bytes32; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.web3j.protocol.core.methods.response.TransactionReceipt; |
||||||
|
|
||||||
|
public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { |
||||||
|
private static final String GENESIS_FILE = "/dev/dev_prague.json"; |
||||||
|
private static final SECP256K1 secp256k1 = new SECP256K1(); |
||||||
|
|
||||||
|
public static final Address SEND_ALL_ETH_CONTRACT_ADDRESS = |
||||||
|
Address.fromHexStringStrict("0000000000000000000000000000000000009999"); |
||||||
|
|
||||||
|
private final Account authorizer = |
||||||
|
accounts.createAccount( |
||||||
|
Address.fromHexStringStrict("8da48afC965480220a3dB9244771bd3afcB5d895")); |
||||||
|
public static final Bytes AUTHORIZER_PRIVATE_KEY = |
||||||
|
Bytes.fromHexString("11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1"); |
||||||
|
|
||||||
|
private final Account transactionSponsor = |
||||||
|
accounts.createAccount( |
||||||
|
Address.fromHexStringStrict("a05b21E5186Ce93d2a226722b85D6e550Ac7D6E3")); |
||||||
|
public static final Bytes TRANSACTION_SPONSOR_PRIVATE_KEY = |
||||||
|
Bytes.fromHexString("3a4ff6d22d7502ef2452368165422861c01a0f72f851793b372b87888dc3c453"); |
||||||
|
|
||||||
|
private BesuNode besuNode; |
||||||
|
private PragueAcceptanceTestHelper testHelper; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
void setUp() throws IOException { |
||||||
|
besuNode = besu.createExecutionEngineGenesisNode("besuNode", GENESIS_FILE); |
||||||
|
cluster.start(besuNode); |
||||||
|
|
||||||
|
testHelper = new PragueAcceptanceTestHelper(besuNode, ethTransactions); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* At the beginning of the test both the authorizer and the transaction sponsor have a balance of |
||||||
|
* 90000 ETH. The authorizer creates an authorization for a contract that send all its ETH to any |
||||||
|
* given address. The transaction sponsor created a 7702 transaction with it and sends all the ETH |
||||||
|
* from the authorizer to itself. The authorizer balance should be 0 and the transaction sponsor |
||||||
|
* balance should be 180000 ETH minus the transaction costs. |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException { |
||||||
|
|
||||||
|
// 7702 transaction
|
||||||
|
final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization = |
||||||
|
SetCodeAuthorization.builder() |
||||||
|
.chainId(BigInteger.valueOf(20211)) |
||||||
|
.address(SEND_ALL_ETH_CONTRACT_ADDRESS) |
||||||
|
.signAndBuild( |
||||||
|
secp256k1.createKeyPair( |
||||||
|
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); |
||||||
|
|
||||||
|
final Transaction tx = |
||||||
|
Transaction.builder() |
||||||
|
.type(TransactionType.SET_CODE) |
||||||
|
.chainId(BigInteger.valueOf(20211)) |
||||||
|
.nonce(0) |
||||||
|
.maxPriorityFeePerGas(Wei.of(1000000000)) |
||||||
|
.maxFeePerGas(Wei.fromHexString("0x02540BE400")) |
||||||
|
.gasLimit(1000000) |
||||||
|
.to(Address.fromHexStringStrict(authorizer.getAddress())) |
||||||
|
.value(Wei.ZERO) |
||||||
|
.payload(Bytes32.leftPad(Bytes.fromHexString(transactionSponsor.getAddress()))) |
||||||
|
.accessList(List.of()) |
||||||
|
.setCodeTransactionPayloads(List.of(authorization)) |
||||||
|
.signAndBuild( |
||||||
|
secp256k1.createKeyPair( |
||||||
|
secp256k1.createPrivateKey( |
||||||
|
TRANSACTION_SPONSOR_PRIVATE_KEY.toUnsignedBigInteger()))); |
||||||
|
|
||||||
|
final String txHash = |
||||||
|
besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString())); |
||||||
|
testHelper.buildNewBlock(); |
||||||
|
|
||||||
|
Optional<TransactionReceipt> maybeTransactionReceipt = |
||||||
|
besuNode.execute(ethTransactions.getTransactionReceipt(txHash)); |
||||||
|
assertThat(maybeTransactionReceipt).isPresent(); |
||||||
|
|
||||||
|
cluster.verify(authorizer.balanceEquals(0)); |
||||||
|
|
||||||
|
final String gasPriceWithout0x = |
||||||
|
maybeTransactionReceipt.get().getEffectiveGasPrice().substring(2); |
||||||
|
final BigInteger txCost = |
||||||
|
maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16)); |
||||||
|
BigInteger expectedSponsorBalance = new BigInteger("180000000000000000000000").subtract(txCost); |
||||||
|
cluster.verify(transactionSponsor.balanceEquals(Amount.wei(expectedSponsorBalance))); |
||||||
|
} |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,74 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.cli.subcommands.storage; |
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; |
||||||
|
import static org.mockito.Mockito.atLeastOnce; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
|
||||||
|
import org.hyperledger.besu.cli.CommandTestAbstract; |
||||||
|
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
class TrieLogSubCommandTest extends CommandTestAbstract { |
||||||
|
|
||||||
|
@Test |
||||||
|
void limitTrieLogsDefaultDisabledForAllSubcommands() { |
||||||
|
assertTrieLogSubcommand("prune"); |
||||||
|
assertTrieLogSubcommand("count"); |
||||||
|
assertTrieLogSubcommand("import"); |
||||||
|
assertTrieLogSubcommand("export"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void limitTrieLogsDisabledForAllSubcommands() { |
||||||
|
assertTrieLogSubcommandWithExplicitLimitEnabled("prune"); |
||||||
|
assertTrieLogSubcommandWithExplicitLimitEnabled("count"); |
||||||
|
assertTrieLogSubcommandWithExplicitLimitEnabled("import"); |
||||||
|
assertTrieLogSubcommandWithExplicitLimitEnabled("export"); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertTrieLogSubcommand(final String trieLogSubcommand) { |
||||||
|
parseCommand("storage", "trie-log", trieLogSubcommand); |
||||||
|
assertConfigurationIsDisabledBySubcommand(); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertTrieLogSubcommandWithExplicitLimitEnabled(final String trieLogSubcommand) { |
||||||
|
parseCommand("--bonsai-limit-trie-logs-enabled=true", "storage", "trie-log", trieLogSubcommand); |
||||||
|
assertConfigurationIsDisabledBySubcommand(); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertConfigurationIsDisabledBySubcommand() { |
||||||
|
verify(mockControllerBuilder, atLeastOnce()) |
||||||
|
.dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); |
||||||
|
final List<DataStorageConfiguration> configs = |
||||||
|
dataStorageConfigurationArgumentCaptor.getAllValues(); |
||||||
|
assertThat(configs.get(0).getBonsaiLimitTrieLogsEnabled()).isTrue(); |
||||||
|
assertThat(configs.get(1).getBonsaiLimitTrieLogsEnabled()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void limitTrieLogsDefaultEnabledForBesuMainCommand() { |
||||||
|
parseCommand(); |
||||||
|
verify(mockControllerBuilder, atLeastOnce()) |
||||||
|
.dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); |
||||||
|
final List<DataStorageConfiguration> configs = |
||||||
|
dataStorageConfigurationArgumentCaptor.getAllValues(); |
||||||
|
assertThat(configs).allMatch(DataStorageConfiguration::getBonsaiLimitTrieLogsEnabled); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.datatypes; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.SECPSignature; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
/** |
||||||
|
* SetCodeAuthorization is a data structure that represents the authorization to set code on a EOA |
||||||
|
* account. |
||||||
|
*/ |
||||||
|
public interface SetCodeAuthorization { |
||||||
|
/** |
||||||
|
* Return the chain id. |
||||||
|
* |
||||||
|
* @return chain id |
||||||
|
*/ |
||||||
|
BigInteger chainId(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the address of the account which code will be used. |
||||||
|
* |
||||||
|
* @return address |
||||||
|
*/ |
||||||
|
Address address(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the signature. |
||||||
|
* |
||||||
|
* @return signature |
||||||
|
*/ |
||||||
|
SECPSignature signature(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the authorizer address. |
||||||
|
* |
||||||
|
* @return authorizer address of the EOA which will load the code into its account |
||||||
|
*/ |
||||||
|
Optional<Address> authorizer(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a valid nonce or empty otherwise. A nonce is valid if the size of the list is exactly 1 |
||||||
|
* |
||||||
|
* @return all the optional nonce |
||||||
|
*/ |
||||||
|
Optional<Long> nonce(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the recovery id. |
||||||
|
* |
||||||
|
* @return byte |
||||||
|
*/ |
||||||
|
byte v(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the r value of the signature. |
||||||
|
* |
||||||
|
* @return r value |
||||||
|
*/ |
||||||
|
BigInteger r(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the s value of the signature. |
||||||
|
* |
||||||
|
* @return s value |
||||||
|
*/ |
||||||
|
BigInteger s(); |
||||||
|
} |
@ -0,0 +1,260 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.core; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.KeyPair; |
||||||
|
import org.hyperledger.besu.crypto.SECPSignature; |
||||||
|
import org.hyperledger.besu.crypto.SignatureAlgorithm; |
||||||
|
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.Hash; |
||||||
|
import org.hyperledger.besu.ethereum.core.encoding.SetCodeTransactionEncoder; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.Supplier; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator; |
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||||
|
import com.google.common.base.Suppliers; |
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
|
||||||
|
public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetCodeAuthorization { |
||||||
|
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM = |
||||||
|
Suppliers.memoize(SignatureAlgorithmFactory::getInstance); |
||||||
|
|
||||||
|
public static final Bytes MAGIC = Bytes.fromHexString("05"); |
||||||
|
|
||||||
|
private final BigInteger chainId; |
||||||
|
private final Address address; |
||||||
|
private final Optional<Long> nonce; |
||||||
|
private final SECPSignature signature; |
||||||
|
private Optional<Address> authorizer = Optional.empty(); |
||||||
|
private boolean isAuthorityComputed = false; |
||||||
|
|
||||||
|
/** |
||||||
|
* An access list entry as defined in EIP-7702 |
||||||
|
* |
||||||
|
* @param chainId can be either the current chain id or zero |
||||||
|
* @param address the address from which the code will be set into the EOA account |
||||||
|
* @param nonce an optional nonce after which this auth expires |
||||||
|
* @param signature the signature of the EOA account which will be used to set the code |
||||||
|
*/ |
||||||
|
public SetCodeAuthorization( |
||||||
|
final BigInteger chainId, |
||||||
|
final Address address, |
||||||
|
final Optional<Long> nonce, |
||||||
|
final SECPSignature signature) { |
||||||
|
this.chainId = chainId; |
||||||
|
this.address = address; |
||||||
|
this.nonce = nonce; |
||||||
|
this.signature = signature; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create access list entry. |
||||||
|
* |
||||||
|
* @param chainId can be either the current chain id or zero |
||||||
|
* @param address the address from which the code will be set into the EOA account |
||||||
|
* @param nonces the list of nonces |
||||||
|
* @param v the recovery id |
||||||
|
* @param r the r value of the signature |
||||||
|
* @param s the s value of the signature |
||||||
|
* @return SetCodeTransactionEntry |
||||||
|
*/ |
||||||
|
@JsonCreator |
||||||
|
public static org.hyperledger.besu.datatypes.SetCodeAuthorization createSetCodeAuthorizationEntry( |
||||||
|
@JsonProperty("chainId") final BigInteger chainId, |
||||||
|
@JsonProperty("address") final Address address, |
||||||
|
@JsonProperty("nonce") final List<Long> nonces, |
||||||
|
@JsonProperty("v") final byte v, |
||||||
|
@JsonProperty("r") final BigInteger r, |
||||||
|
@JsonProperty("s") final BigInteger s) { |
||||||
|
return new SetCodeAuthorization( |
||||||
|
chainId, |
||||||
|
address, |
||||||
|
Optional.ofNullable(nonces.get(0)), |
||||||
|
SIGNATURE_ALGORITHM.get().createSignature(r, s, v)); |
||||||
|
} |
||||||
|
|
||||||
|
@JsonProperty("chainId") |
||||||
|
@Override |
||||||
|
public BigInteger chainId() { |
||||||
|
return chainId; |
||||||
|
} |
||||||
|
|
||||||
|
@JsonProperty("address") |
||||||
|
@Override |
||||||
|
public Address address() { |
||||||
|
return address; |
||||||
|
} |
||||||
|
|
||||||
|
@JsonProperty("signature") |
||||||
|
@Override |
||||||
|
public SECPSignature signature() { |
||||||
|
return signature; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Optional<Address> authorizer() { |
||||||
|
if (!isAuthorityComputed) { |
||||||
|
authorizer = computeAuthority(); |
||||||
|
isAuthorityComputed = true; |
||||||
|
} |
||||||
|
|
||||||
|
return authorizer; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Optional<Long> nonce() { |
||||||
|
return nonce; |
||||||
|
} |
||||||
|
|
||||||
|
@JsonProperty("v") |
||||||
|
@Override |
||||||
|
public byte v() { |
||||||
|
return signature.getRecId(); |
||||||
|
} |
||||||
|
|
||||||
|
@JsonProperty("r") |
||||||
|
@Override |
||||||
|
public BigInteger r() { |
||||||
|
return signature.getR(); |
||||||
|
} |
||||||
|
|
||||||
|
@JsonProperty("s") |
||||||
|
@Override |
||||||
|
public BigInteger s() { |
||||||
|
return signature.getS(); |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<Address> computeAuthority() { |
||||||
|
BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); |
||||||
|
SetCodeTransactionEncoder.encodeSingleSetCodeWithoutSignature(this, rlpOutput); |
||||||
|
|
||||||
|
final Hash hash = Hash.hash(Bytes.concatenate(MAGIC, rlpOutput.encoded())); |
||||||
|
|
||||||
|
return SIGNATURE_ALGORITHM |
||||||
|
.get() |
||||||
|
.recoverPublicKeyFromSignature(hash, signature) |
||||||
|
.map(Address::extract); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create set code authorization with a builder. |
||||||
|
* |
||||||
|
* @return SetCodeAuthorization.Builder |
||||||
|
*/ |
||||||
|
public static Builder builder() { |
||||||
|
return new Builder(); |
||||||
|
} |
||||||
|
|
||||||
|
/** Builder for SetCodeAuthorization. */ |
||||||
|
public static class Builder { |
||||||
|
private BigInteger chainId = BigInteger.ZERO; |
||||||
|
private Address address; |
||||||
|
private Optional<Long> nonce = Optional.empty(); |
||||||
|
private SECPSignature signature; |
||||||
|
|
||||||
|
/** Create a new builder. */ |
||||||
|
protected Builder() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the optional chain id. |
||||||
|
* |
||||||
|
* @param chainId the chain id |
||||||
|
* @return this builder |
||||||
|
*/ |
||||||
|
public Builder chainId(final BigInteger chainId) { |
||||||
|
this.chainId = chainId; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the address of the authorized smart contract. |
||||||
|
* |
||||||
|
* @param address the address |
||||||
|
* @return this builder |
||||||
|
*/ |
||||||
|
public Builder address(final Address address) { |
||||||
|
this.address = address; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the optional nonce. |
||||||
|
* |
||||||
|
* @param nonce the optional nonce. |
||||||
|
* @return this builder |
||||||
|
*/ |
||||||
|
public Builder nonces(final Optional<Long> nonce) { |
||||||
|
this.nonce = nonce; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the signature of the authorizer account. |
||||||
|
* |
||||||
|
* @param signature the signature |
||||||
|
* @return this builder |
||||||
|
*/ |
||||||
|
public Builder signature(final SECPSignature signature) { |
||||||
|
this.signature = signature; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sign the authorization with the given key pair and return the authorization. |
||||||
|
* |
||||||
|
* @param keyPair the key pair |
||||||
|
* @return SetCodeAuthorization |
||||||
|
*/ |
||||||
|
public org.hyperledger.besu.datatypes.SetCodeAuthorization signAndBuild(final KeyPair keyPair) { |
||||||
|
final BytesValueRLPOutput output = new BytesValueRLPOutput(); |
||||||
|
output.startList(); |
||||||
|
output.writeBigIntegerScalar(chainId); |
||||||
|
output.writeBytes(address); |
||||||
|
output.startList(); |
||||||
|
nonce.ifPresent(output::writeLongScalar); |
||||||
|
output.endList(); |
||||||
|
output.endList(); |
||||||
|
|
||||||
|
signature( |
||||||
|
SIGNATURE_ALGORITHM |
||||||
|
.get() |
||||||
|
.sign(Hash.hash(Bytes.concatenate(MAGIC, output.encoded())), keyPair)); |
||||||
|
return build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build the authorization. |
||||||
|
* |
||||||
|
* @return SetCodeAuthorization |
||||||
|
*/ |
||||||
|
public org.hyperledger.besu.datatypes.SetCodeAuthorization build() { |
||||||
|
if (address == null) { |
||||||
|
throw new IllegalStateException("Address must be set"); |
||||||
|
} |
||||||
|
|
||||||
|
if (signature == null) { |
||||||
|
throw new IllegalStateException("Signature must be set"); |
||||||
|
} |
||||||
|
|
||||||
|
return new SetCodeAuthorization(chainId, address, nonce, signature); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.core.encoding; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.SECPSignature; |
||||||
|
import org.hyperledger.besu.crypto.SignatureAlgorithm; |
||||||
|
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||||
|
import org.hyperledger.besu.datatypes.AccessListEntry; |
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.TransactionType; |
||||||
|
import org.hyperledger.besu.datatypes.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.core.SetCodeAuthorization; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.RLPInput; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.Supplier; |
||||||
|
|
||||||
|
import com.google.common.base.Suppliers; |
||||||
|
|
||||||
|
public class SetCodeTransactionDecoder { |
||||||
|
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM = |
||||||
|
Suppliers.memoize(SignatureAlgorithmFactory::getInstance); |
||||||
|
|
||||||
|
private SetCodeTransactionDecoder() { |
||||||
|
// private constructor
|
||||||
|
} |
||||||
|
|
||||||
|
public static Transaction decode(final RLPInput input) { |
||||||
|
input.enterList(); |
||||||
|
final BigInteger chainId = input.readBigIntegerScalar(); |
||||||
|
final Transaction.Builder builder = |
||||||
|
Transaction.builder() |
||||||
|
.type(TransactionType.SET_CODE) |
||||||
|
.chainId(chainId) |
||||||
|
.nonce(input.readLongScalar()) |
||||||
|
.maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar())) |
||||||
|
.maxFeePerGas(Wei.of(input.readUInt256Scalar())) |
||||||
|
.gasLimit(input.readLongScalar()) |
||||||
|
.to(input.readBytes(v -> v.isEmpty() ? null : Address.wrap(v))) |
||||||
|
.value(Wei.of(input.readUInt256Scalar())) |
||||||
|
.payload(input.readBytes()) |
||||||
|
.accessList( |
||||||
|
input.readList( |
||||||
|
accessListEntryRLPInput -> { |
||||||
|
accessListEntryRLPInput.enterList(); |
||||||
|
final AccessListEntry accessListEntry = |
||||||
|
new AccessListEntry( |
||||||
|
Address.wrap(accessListEntryRLPInput.readBytes()), |
||||||
|
accessListEntryRLPInput.readList(RLPInput::readBytes32)); |
||||||
|
accessListEntryRLPInput.leaveList(); |
||||||
|
return accessListEntry; |
||||||
|
})) |
||||||
|
.setCodeTransactionPayloads( |
||||||
|
input.readList( |
||||||
|
setCodeTransactionPayloadsRLPInput -> |
||||||
|
decodeInnerPayload(setCodeTransactionPayloadsRLPInput))); |
||||||
|
|
||||||
|
final byte recId = (byte) input.readUnsignedByteScalar(); |
||||||
|
final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); |
||||||
|
final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger(); |
||||||
|
|
||||||
|
input.leaveList(); |
||||||
|
|
||||||
|
final Transaction transaction = |
||||||
|
builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build(); |
||||||
|
|
||||||
|
return transaction; |
||||||
|
} |
||||||
|
|
||||||
|
public static org.hyperledger.besu.datatypes.SetCodeAuthorization decodeInnerPayload( |
||||||
|
final RLPInput input) { |
||||||
|
input.enterList(); |
||||||
|
final BigInteger chainId = input.readBigIntegerScalar(); |
||||||
|
final Address address = Address.wrap(input.readBytes()); |
||||||
|
|
||||||
|
Optional<Long> nonce = Optional.empty(); |
||||||
|
|
||||||
|
if (!input.nextIsList()) { |
||||||
|
throw new IllegalArgumentException("Optional nonce must be an list, but isn't"); |
||||||
|
} |
||||||
|
|
||||||
|
final long noncesSize = input.nextSize(); |
||||||
|
|
||||||
|
input.enterList(); |
||||||
|
if (noncesSize == 1) { |
||||||
|
nonce = Optional.ofNullable(input.readLongScalar()); |
||||||
|
} else if (noncesSize > 1) { |
||||||
|
throw new IllegalArgumentException("Nonce list may only have 1 member, if any"); |
||||||
|
} |
||||||
|
input.leaveList(); |
||||||
|
|
||||||
|
final byte yParity = (byte) input.readUnsignedByteScalar(); |
||||||
|
final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); |
||||||
|
final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger(); |
||||||
|
|
||||||
|
input.leaveList(); |
||||||
|
|
||||||
|
final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, yParity); |
||||||
|
|
||||||
|
return new SetCodeAuthorization(chainId, address, nonce, signature); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.core.encoding; |
||||||
|
|
||||||
|
import static org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder.writeAccessList; |
||||||
|
import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignatureAndRecoveryId; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.SetCodeAuthorization; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.RLPOutput; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
|
||||||
|
public class SetCodeTransactionEncoder { |
||||||
|
|
||||||
|
private SetCodeTransactionEncoder() { |
||||||
|
// private constructor
|
||||||
|
} |
||||||
|
|
||||||
|
public static void encodeSetCodeInner( |
||||||
|
final List<SetCodeAuthorization> payloads, final RLPOutput rlpOutput) { |
||||||
|
rlpOutput.startList(); |
||||||
|
payloads.forEach(payload -> encodeSingleSetCode(payload, rlpOutput)); |
||||||
|
rlpOutput.endList(); |
||||||
|
} |
||||||
|
|
||||||
|
public static void encodeSingleSetCodeWithoutSignature( |
||||||
|
final SetCodeAuthorization payload, final RLPOutput rlpOutput) { |
||||||
|
rlpOutput.startList(); |
||||||
|
encodeAuthorizationDetails(payload, rlpOutput); |
||||||
|
rlpOutput.endList(); |
||||||
|
} |
||||||
|
|
||||||
|
public static void encodeSingleSetCode( |
||||||
|
final SetCodeAuthorization payload, final RLPOutput rlpOutput) { |
||||||
|
rlpOutput.startList(); |
||||||
|
encodeAuthorizationDetails(payload, rlpOutput); |
||||||
|
rlpOutput.writeIntScalar(payload.signature().getRecId()); |
||||||
|
rlpOutput.writeBigIntegerScalar(payload.signature().getR()); |
||||||
|
rlpOutput.writeBigIntegerScalar(payload.signature().getS()); |
||||||
|
rlpOutput.endList(); |
||||||
|
} |
||||||
|
|
||||||
|
private static void encodeAuthorizationDetails( |
||||||
|
final SetCodeAuthorization payload, final RLPOutput rlpOutput) { |
||||||
|
rlpOutput.writeBigIntegerScalar(payload.chainId()); |
||||||
|
rlpOutput.writeBytes(payload.address().copy()); |
||||||
|
rlpOutput.startList(); |
||||||
|
payload.nonce().ifPresent(rlpOutput::writeLongScalar); |
||||||
|
rlpOutput.endList(); |
||||||
|
} |
||||||
|
|
||||||
|
public static void encode(final Transaction transaction, final RLPOutput out) { |
||||||
|
out.startList(); |
||||||
|
out.writeBigIntegerScalar(transaction.getChainId().orElseThrow()); |
||||||
|
out.writeLongScalar(transaction.getNonce()); |
||||||
|
out.writeUInt256Scalar(transaction.getMaxPriorityFeePerGas().orElseThrow()); |
||||||
|
out.writeUInt256Scalar(transaction.getMaxFeePerGas().orElseThrow()); |
||||||
|
out.writeLongScalar(transaction.getGasLimit()); |
||||||
|
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY)); |
||||||
|
out.writeUInt256Scalar(transaction.getValue()); |
||||||
|
out.writeBytes(transaction.getPayload()); |
||||||
|
writeAccessList(out, transaction.getAccessList()); |
||||||
|
encodeSetCodeInner( |
||||||
|
transaction |
||||||
|
.getAuthorizationList() |
||||||
|
.orElseThrow( |
||||||
|
() -> |
||||||
|
new IllegalStateException( |
||||||
|
"Developer error: the transaction should be guaranteed to have a set code payload here")), |
||||||
|
out); |
||||||
|
writeSignatureAndRecoveryId(transaction, out); |
||||||
|
out.endList(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,88 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.mainnet; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.evm.account.Account; |
||||||
|
import org.hyperledger.besu.evm.account.AccountState; |
||||||
|
import org.hyperledger.besu.evm.account.MutableAccount; |
||||||
|
import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; |
||||||
|
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
public class AuthorityProcessor { |
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AuthorityProcessor.class); |
||||||
|
|
||||||
|
private final Optional<BigInteger> maybeChainId; |
||||||
|
|
||||||
|
public AuthorityProcessor(final Optional<BigInteger> maybeChainId) { |
||||||
|
this.maybeChainId = maybeChainId; |
||||||
|
} |
||||||
|
|
||||||
|
public void addContractToAuthority( |
||||||
|
final WorldUpdater worldUpdater, |
||||||
|
final AuthorizedCodeService authorizedCodeService, |
||||||
|
final Transaction transaction) { |
||||||
|
|
||||||
|
transaction |
||||||
|
.getAuthorizationList() |
||||||
|
.get() |
||||||
|
.forEach( |
||||||
|
payload -> |
||||||
|
payload |
||||||
|
.authorizer() |
||||||
|
.ifPresent( |
||||||
|
authorityAddress -> { |
||||||
|
LOG.trace("Set code authority: {}", authorityAddress); |
||||||
|
|
||||||
|
if (maybeChainId.isPresent() |
||||||
|
&& !payload.chainId().equals(BigInteger.ZERO) |
||||||
|
&& !maybeChainId.get().equals(payload.chainId())) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
final Optional<MutableAccount> maybeAccount = |
||||||
|
Optional.ofNullable(worldUpdater.getAccount(authorityAddress)); |
||||||
|
final long accountNonce = |
||||||
|
maybeAccount.map(AccountState::getNonce).orElse(0L); |
||||||
|
|
||||||
|
if (payload.nonce().isPresent() |
||||||
|
&& !payload.nonce().get().equals(accountNonce)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (authorizedCodeService.hasAuthorizedCode(authorityAddress)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Optional<Account> codeAccount = |
||||||
|
Optional.ofNullable(worldUpdater.get(payload.address())); |
||||||
|
final Bytes code; |
||||||
|
if (codeAccount.isPresent()) { |
||||||
|
code = codeAccount.get().getCode(); |
||||||
|
} else { |
||||||
|
code = Bytes.EMPTY; |
||||||
|
} |
||||||
|
|
||||||
|
authorizedCodeService.addAuthorizedCode(authorityAddress, code); |
||||||
|
})); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,199 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.mainnet.parallelization; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||||
|
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.BlockProcessor; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; |
||||||
|
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; |
||||||
|
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; |
||||||
|
import org.hyperledger.besu.evm.operation.BlockHashOperation; |
||||||
|
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||||
|
import org.hyperledger.besu.metrics.BesuMetricCategory; |
||||||
|
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||||
|
import org.hyperledger.besu.plugin.services.metrics.Counter; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
public class MainnetParallelBlockProcessor extends MainnetBlockProcessor { |
||||||
|
|
||||||
|
private final Optional<MetricsSystem> metricsSystem; |
||||||
|
private final Optional<Counter> confirmedParallelizedTransactionCounter; |
||||||
|
private final Optional<Counter> conflictingButCachedTransactionCounter; |
||||||
|
|
||||||
|
public MainnetParallelBlockProcessor( |
||||||
|
final MainnetTransactionProcessor transactionProcessor, |
||||||
|
final TransactionReceiptFactory transactionReceiptFactory, |
||||||
|
final Wei blockReward, |
||||||
|
final MiningBeneficiaryCalculator miningBeneficiaryCalculator, |
||||||
|
final boolean skipZeroBlockRewards, |
||||||
|
final ProtocolSchedule protocolSchedule, |
||||||
|
final MetricsSystem metricsSystem) { |
||||||
|
super( |
||||||
|
transactionProcessor, |
||||||
|
transactionReceiptFactory, |
||||||
|
blockReward, |
||||||
|
miningBeneficiaryCalculator, |
||||||
|
skipZeroBlockRewards, |
||||||
|
protocolSchedule); |
||||||
|
this.metricsSystem = Optional.of(metricsSystem); |
||||||
|
this.confirmedParallelizedTransactionCounter = |
||||||
|
Optional.of( |
||||||
|
this.metricsSystem |
||||||
|
.get() |
||||||
|
.createCounter( |
||||||
|
BesuMetricCategory.BLOCK_PROCESSING, |
||||||
|
"parallelized_transactions_counter", |
||||||
|
"Counter for the number of parallelized transactions during block processing")); |
||||||
|
|
||||||
|
this.conflictingButCachedTransactionCounter = |
||||||
|
Optional.of( |
||||||
|
this.metricsSystem |
||||||
|
.get() |
||||||
|
.createCounter( |
||||||
|
BesuMetricCategory.BLOCK_PROCESSING, |
||||||
|
"conflicted_transactions_counter", |
||||||
|
"Counter for the number of conflicted transactions during block processing")); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Optional<PreprocessingContext> runBlockPreProcessing( |
||||||
|
final MutableWorldState worldState, |
||||||
|
final PrivateMetadataUpdater privateMetadataUpdater, |
||||||
|
final BlockHeader blockHeader, |
||||||
|
final List<Transaction> transactions, |
||||||
|
final Address miningBeneficiary, |
||||||
|
final BlockHashOperation.BlockHashLookup blockHashLookup, |
||||||
|
final Wei blobGasPrice) { |
||||||
|
if ((worldState instanceof DiffBasedWorldState)) { |
||||||
|
ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor = |
||||||
|
new ParallelizedConcurrentTransactionProcessor(transactionProcessor); |
||||||
|
// runAsyncBlock, if activated, facilitates the non-blocking parallel execution of
|
||||||
|
// transactions in the background through an optimistic strategy.
|
||||||
|
parallelizedConcurrentTransactionProcessor.runAsyncBlock( |
||||||
|
worldState, |
||||||
|
blockHeader, |
||||||
|
transactions, |
||||||
|
miningBeneficiary, |
||||||
|
blockHashLookup, |
||||||
|
blobGasPrice, |
||||||
|
privateMetadataUpdater); |
||||||
|
return Optional.of( |
||||||
|
new ParallelizedPreProcessingContext(parallelizedConcurrentTransactionProcessor)); |
||||||
|
} |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected TransactionProcessingResult getTransactionProcessingResult( |
||||||
|
final Optional<PreprocessingContext> preProcessingContext, |
||||||
|
final MutableWorldState worldState, |
||||||
|
final WorldUpdater blockUpdater, |
||||||
|
final PrivateMetadataUpdater privateMetadataUpdater, |
||||||
|
final BlockHeader blockHeader, |
||||||
|
final Wei blobGasPrice, |
||||||
|
final Address miningBeneficiary, |
||||||
|
final Transaction transaction, |
||||||
|
final int location, |
||||||
|
final BlockHashOperation.BlockHashLookup blockHashLookup) { |
||||||
|
|
||||||
|
TransactionProcessingResult transactionProcessingResult = null; |
||||||
|
|
||||||
|
if (preProcessingContext.isPresent()) { |
||||||
|
final ParallelizedPreProcessingContext parallelizedPreProcessingContext = |
||||||
|
(ParallelizedPreProcessingContext) preProcessingContext.get(); |
||||||
|
transactionProcessingResult = |
||||||
|
parallelizedPreProcessingContext |
||||||
|
.getParallelizedConcurrentTransactionProcessor() |
||||||
|
.applyParallelizedTransactionResult( |
||||||
|
worldState, |
||||||
|
miningBeneficiary, |
||||||
|
transaction, |
||||||
|
location, |
||||||
|
confirmedParallelizedTransactionCounter, |
||||||
|
conflictingButCachedTransactionCounter) |
||||||
|
.orElse(null); |
||||||
|
} |
||||||
|
|
||||||
|
if (transactionProcessingResult == null) { |
||||||
|
return super.getTransactionProcessingResult( |
||||||
|
preProcessingContext, |
||||||
|
worldState, |
||||||
|
blockUpdater, |
||||||
|
privateMetadataUpdater, |
||||||
|
blockHeader, |
||||||
|
blobGasPrice, |
||||||
|
miningBeneficiary, |
||||||
|
transaction, |
||||||
|
location, |
||||||
|
blockHashLookup); |
||||||
|
} else { |
||||||
|
return transactionProcessingResult; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class ParallelizedPreProcessingContext implements PreprocessingContext { |
||||||
|
final ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor; |
||||||
|
|
||||||
|
public ParallelizedPreProcessingContext( |
||||||
|
final ParallelizedConcurrentTransactionProcessor |
||||||
|
parallelizedConcurrentTransactionProcessor) { |
||||||
|
this.parallelizedConcurrentTransactionProcessor = parallelizedConcurrentTransactionProcessor; |
||||||
|
} |
||||||
|
|
||||||
|
public ParallelizedConcurrentTransactionProcessor |
||||||
|
getParallelizedConcurrentTransactionProcessor() { |
||||||
|
return parallelizedConcurrentTransactionProcessor; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static class ParallelBlockProcessorBuilder |
||||||
|
implements ProtocolSpecBuilder.BlockProcessorBuilder { |
||||||
|
|
||||||
|
final MetricsSystem metricsSystem; |
||||||
|
|
||||||
|
public ParallelBlockProcessorBuilder(final MetricsSystem metricsSystem) { |
||||||
|
this.metricsSystem = metricsSystem; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BlockProcessor apply( |
||||||
|
final MainnetTransactionProcessor transactionProcessor, |
||||||
|
final TransactionReceiptFactory transactionReceiptFactory, |
||||||
|
final Wei blockReward, |
||||||
|
final MiningBeneficiaryCalculator miningBeneficiaryCalculator, |
||||||
|
final boolean skipZeroBlockRewards, |
||||||
|
final ProtocolSchedule protocolSchedule) { |
||||||
|
return new MainnetParallelBlockProcessor( |
||||||
|
transactionProcessor, |
||||||
|
transactionReceiptFactory, |
||||||
|
blockReward, |
||||||
|
miningBeneficiaryCalculator, |
||||||
|
skipZeroBlockRewards, |
||||||
|
protocolSchedule, |
||||||
|
metricsSystem); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,268 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.mainnet.parallelization; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||||
|
import org.hyperledger.besu.ethereum.core.MutableWorldState; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; |
||||||
|
import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; |
||||||
|
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoopBonsaiCachedMerkleTrieLoader; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||||
|
import org.hyperledger.besu.evm.operation.BlockHashOperation; |
||||||
|
import org.hyperledger.besu.evm.tracing.OperationTracer; |
||||||
|
import org.hyperledger.besu.evm.worldstate.WorldView; |
||||||
|
import org.hyperledger.besu.plugin.services.metrics.Counter; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.concurrent.CompletableFuture; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
import java.util.concurrent.Executor; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting; |
||||||
|
|
||||||
|
/** |
||||||
|
* Optimizes transaction processing by executing transactions in parallel within a given block. |
||||||
|
* Transactions are executed optimistically in a non-blocking manner. After execution, the class
|
||||||
|
* checks for potential conflicts among transactions to ensure data integrity before applying the |
||||||
|
* results to the world state. |
||||||
|
*/ |
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||||
|
public class ParallelizedConcurrentTransactionProcessor { |
||||||
|
|
||||||
|
private static final int NCPU = Runtime.getRuntime().availableProcessors(); |
||||||
|
private static final Executor executor = Executors.newFixedThreadPool(NCPU); |
||||||
|
|
||||||
|
private final MainnetTransactionProcessor transactionProcessor; |
||||||
|
|
||||||
|
private final TransactionCollisionDetector transactionCollisionDetector; |
||||||
|
|
||||||
|
private final Map<Integer, ParallelizedTransactionContext> |
||||||
|
parallelizedTransactionContextByLocation = new ConcurrentHashMap<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a PreloadConcurrentTransactionProcessor with a specified transaction processor. This |
||||||
|
* processor is responsible for the individual processing of transactions. |
||||||
|
* |
||||||
|
* @param transactionProcessor The transaction processor for processing individual transactions. |
||||||
|
*/ |
||||||
|
public ParallelizedConcurrentTransactionProcessor( |
||||||
|
final MainnetTransactionProcessor transactionProcessor) { |
||||||
|
this.transactionProcessor = transactionProcessor; |
||||||
|
this.transactionCollisionDetector = new TransactionCollisionDetector(); |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting |
||||||
|
public ParallelizedConcurrentTransactionProcessor( |
||||||
|
final MainnetTransactionProcessor transactionProcessor, |
||||||
|
final TransactionCollisionDetector transactionCollisionDetector) { |
||||||
|
this.transactionProcessor = transactionProcessor; |
||||||
|
this.transactionCollisionDetector = transactionCollisionDetector; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Initiates the parallel and optimistic execution of transactions within a block by creating a |
||||||
|
* copy of the world state for each transaction. This method processes transactions in a |
||||||
|
* non-blocking manner. Transactions are executed against their respective copies of the world |
||||||
|
* state, ensuring that the original world state passed as a parameter remains unmodified during |
||||||
|
* this process. |
||||||
|
* |
||||||
|
* @param worldState Mutable world state intended for applying transaction results. This world |
||||||
|
* state is not modified directly; instead, copies are made for transaction execution. |
||||||
|
* @param blockHeader Header of the current block containing the transactions. |
||||||
|
* @param transactions List of transactions to be processed. |
||||||
|
* @param miningBeneficiary Address of the beneficiary to receive mining rewards. |
||||||
|
* @param blockHashLookup Function for block hash lookup. |
||||||
|
* @param blobGasPrice Gas price for blob transactions. |
||||||
|
* @param privateMetadataUpdater Updater for private transaction metadata. |
||||||
|
*/ |
||||||
|
public void runAsyncBlock( |
||||||
|
final MutableWorldState worldState, |
||||||
|
final BlockHeader blockHeader, |
||||||
|
final List<Transaction> transactions, |
||||||
|
final Address miningBeneficiary, |
||||||
|
final BlockHashOperation.BlockHashLookup blockHashLookup, |
||||||
|
final Wei blobGasPrice, |
||||||
|
final PrivateMetadataUpdater privateMetadataUpdater) { |
||||||
|
for (int i = 0; i < transactions.size(); i++) { |
||||||
|
final Transaction transaction = transactions.get(i); |
||||||
|
final int transactionLocation = i; |
||||||
|
/* |
||||||
|
* All transactions are executed in the background by copying the world state of the block on which the transactions need to be executed, ensuring that each one has its own accumulator. |
||||||
|
*/ |
||||||
|
CompletableFuture.runAsync( |
||||||
|
() -> |
||||||
|
runTransaction( |
||||||
|
worldState, |
||||||
|
blockHeader, |
||||||
|
transactionLocation, |
||||||
|
transaction, |
||||||
|
miningBeneficiary, |
||||||
|
blockHashLookup, |
||||||
|
blobGasPrice, |
||||||
|
privateMetadataUpdater), |
||||||
|
executor); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting |
||||||
|
public void runTransaction( |
||||||
|
final MutableWorldState worldState, |
||||||
|
final BlockHeader blockHeader, |
||||||
|
final int transactionLocation, |
||||||
|
final Transaction transaction, |
||||||
|
final Address miningBeneficiary, |
||||||
|
final BlockHashOperation.BlockHashLookup blockHashLookup, |
||||||
|
final Wei blobGasPrice, |
||||||
|
final PrivateMetadataUpdater privateMetadataUpdater) { |
||||||
|
try (final DiffBasedWorldState roundWorldState = |
||||||
|
new BonsaiWorldState( |
||||||
|
(BonsaiWorldState) worldState, new NoopBonsaiCachedMerkleTrieLoader())) { |
||||||
|
roundWorldState.freeze(); // make the clone frozen
|
||||||
|
final ParallelizedTransactionContext.Builder contextBuilder = |
||||||
|
new ParallelizedTransactionContext.Builder(); |
||||||
|
final DiffBasedWorldStateUpdateAccumulator<?> roundWorldStateUpdater = |
||||||
|
(DiffBasedWorldStateUpdateAccumulator<?>) roundWorldState.updater(); |
||||||
|
final TransactionProcessingResult result = |
||||||
|
transactionProcessor.processTransaction( |
||||||
|
roundWorldStateUpdater, |
||||||
|
blockHeader, |
||||||
|
transaction, |
||||||
|
miningBeneficiary, |
||||||
|
new OperationTracer() { |
||||||
|
@Override |
||||||
|
public void traceBeforeRewardTransaction( |
||||||
|
final WorldView worldView, |
||||||
|
final org.hyperledger.besu.datatypes.Transaction tx, |
||||||
|
final Wei miningReward) { |
||||||
|
/* |
||||||
|
* This part checks if the mining beneficiary's account was accessed before increasing its balance for rewards. |
||||||
|
* Indeed, if the transaction has interacted with the address to read or modify it, |
||||||
|
* it means that the value is necessary for the proper execution of the transaction and will therefore be considered in collision detection. |
||||||
|
* If this is not the case, we can ignore this address during conflict detection. |
||||||
|
*/ |
||||||
|
if (transactionCollisionDetector |
||||||
|
.getAddressesTouchedByTransaction( |
||||||
|
transaction, Optional.of(roundWorldStateUpdater)) |
||||||
|
.contains(miningBeneficiary)) { |
||||||
|
contextBuilder.isMiningBeneficiaryTouchedPreRewardByTransaction(true); |
||||||
|
} |
||||||
|
contextBuilder.miningBeneficiaryReward(miningReward); |
||||||
|
} |
||||||
|
}, |
||||||
|
blockHashLookup, |
||||||
|
true, |
||||||
|
TransactionValidationParams.processingBlock(), |
||||||
|
privateMetadataUpdater, |
||||||
|
blobGasPrice); |
||||||
|
|
||||||
|
// commit the accumulator in order to apply all the modifications
|
||||||
|
roundWorldState.getAccumulator().commit(); |
||||||
|
|
||||||
|
contextBuilder |
||||||
|
.transactionAccumulator(roundWorldState.getAccumulator()) |
||||||
|
.transactionProcessingResult(result); |
||||||
|
|
||||||
|
final ParallelizedTransactionContext parallelizedTransactionContext = contextBuilder.build(); |
||||||
|
if (!parallelizedTransactionContext.isMiningBeneficiaryTouchedPreRewardByTransaction()) { |
||||||
|
/* |
||||||
|
* If the address of the mining beneficiary has been touched only for adding rewards, |
||||||
|
* we remove it from the accumulator to avoid a false positive collision. |
||||||
|
* The balance will be increased during the sequential processing. |
||||||
|
*/ |
||||||
|
roundWorldStateUpdater.getAccountsToUpdate().remove(miningBeneficiary); |
||||||
|
} |
||||||
|
parallelizedTransactionContextByLocation.put( |
||||||
|
transactionLocation, parallelizedTransactionContext); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Applies the results of parallelized transactions to the world state after checking for |
||||||
|
* conflicts. |
||||||
|
* |
||||||
|
* <p>If a transaction was executed optimistically without any detected conflicts, its result is |
||||||
|
* directly applied to the world state. If there is a conflict, this method does not apply the |
||||||
|
* transaction's modifications directly to the world state. Instead, it caches the data read from |
||||||
|
* the database during the transaction's execution. This cached data is then used to optimize the |
||||||
|
* replay of the transaction by reducing the need for additional reads from the disk, thereby |
||||||
|
* making the replay process faster. This approach ensures that the integrity of the world state |
||||||
|
* is maintained while optimizing the performance of transaction processing. |
||||||
|
* |
||||||
|
* @param worldState Mutable world state intended for applying transaction results. |
||||||
|
* @param miningBeneficiary Address of the beneficiary for mining rewards. |
||||||
|
* @param transaction Transaction for which the result is to be applied. |
||||||
|
* @param transactionLocation Index of the transaction within the block. |
||||||
|
* @param confirmedParallelizedTransactionCounter Metric counter for confirmed parallelized |
||||||
|
* transactions |
||||||
|
* @param conflictingButCachedTransactionCounter Metric counter for conflicting but cached |
||||||
|
* transactions |
||||||
|
* @return Optional containing the transaction processing result if applied, or empty if the |
||||||
|
* transaction needs to be replayed due to a conflict. |
||||||
|
*/ |
||||||
|
public Optional<TransactionProcessingResult> applyParallelizedTransactionResult( |
||||||
|
final MutableWorldState worldState, |
||||||
|
final Address miningBeneficiary, |
||||||
|
final Transaction transaction, |
||||||
|
final int transactionLocation, |
||||||
|
final Optional<Counter> confirmedParallelizedTransactionCounter, |
||||||
|
final Optional<Counter> conflictingButCachedTransactionCounter) { |
||||||
|
final DiffBasedWorldState diffBasedWorldState = (DiffBasedWorldState) worldState; |
||||||
|
final DiffBasedWorldStateUpdateAccumulator blockAccumulator = |
||||||
|
(DiffBasedWorldStateUpdateAccumulator) diffBasedWorldState.updater(); |
||||||
|
final ParallelizedTransactionContext parallelizedTransactionContext = |
||||||
|
parallelizedTransactionContextByLocation.remove(transactionLocation); |
||||||
|
/* |
||||||
|
* If `parallelizedTransactionContext` is not null, it means that the transaction had time to complete in the background. |
||||||
|
*/ |
||||||
|
if (parallelizedTransactionContext != null) { |
||||||
|
final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator = |
||||||
|
parallelizedTransactionContext.transactionAccumulator(); |
||||||
|
final TransactionProcessingResult transactionProcessingResult = |
||||||
|
parallelizedTransactionContext.transactionProcessingResult(); |
||||||
|
final boolean hasCollision = |
||||||
|
transactionCollisionDetector.hasCollision( |
||||||
|
transaction, miningBeneficiary, parallelizedTransactionContext, blockAccumulator); |
||||||
|
if (transactionProcessingResult.isSuccessful() && !hasCollision) { |
||||||
|
blockAccumulator |
||||||
|
.getOrCreate(miningBeneficiary) |
||||||
|
.incrementBalance(parallelizedTransactionContext.miningBeneficiaryReward()); |
||||||
|
|
||||||
|
blockAccumulator.importStateChangesFromSource(transactionAccumulator); |
||||||
|
|
||||||
|
if (confirmedParallelizedTransactionCounter.isPresent()) |
||||||
|
confirmedParallelizedTransactionCounter.get().inc(); |
||||||
|
return Optional.of(transactionProcessingResult); |
||||||
|
} else { |
||||||
|
blockAccumulator.importPriorStateFromSource(transactionAccumulator); |
||||||
|
if (conflictingButCachedTransactionCounter.isPresent()) |
||||||
|
conflictingButCachedTransactionCounter.get().inc(); |
||||||
|
// If there is a conflict, we return an empty result to signal the block processor to
|
||||||
|
// re-execute the transaction.
|
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
} |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.mainnet.parallelization; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Wei; |
||||||
|
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||||
|
|
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
public final class ParallelizedTransactionContext { |
||||||
|
private final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator; |
||||||
|
private final TransactionProcessingResult transactionProcessingResult; |
||||||
|
private final boolean isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||||
|
private final Wei miningBeneficiaryReward; |
||||||
|
|
||||||
|
public ParallelizedTransactionContext( |
||||||
|
final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator, |
||||||
|
final TransactionProcessingResult transactionProcessingResult, |
||||||
|
final boolean isMiningBeneficiaryTouchedPreRewardByTransaction, |
||||||
|
final Wei miningBeneficiaryReward) { |
||||||
|
this.transactionAccumulator = transactionAccumulator; |
||||||
|
this.transactionProcessingResult = transactionProcessingResult; |
||||||
|
this.isMiningBeneficiaryTouchedPreRewardByTransaction = |
||||||
|
isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||||
|
this.miningBeneficiaryReward = miningBeneficiaryReward; |
||||||
|
} |
||||||
|
|
||||||
|
public DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator() { |
||||||
|
return transactionAccumulator; |
||||||
|
} |
||||||
|
|
||||||
|
public TransactionProcessingResult transactionProcessingResult() { |
||||||
|
return transactionProcessingResult; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isMiningBeneficiaryTouchedPreRewardByTransaction() { |
||||||
|
return isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||||
|
} |
||||||
|
|
||||||
|
public Wei miningBeneficiaryReward() { |
||||||
|
return miningBeneficiaryReward; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(final Object obj) { |
||||||
|
if (obj == this) return true; |
||||||
|
if (obj == null || obj.getClass() != this.getClass()) return false; |
||||||
|
var that = (ParallelizedTransactionContext) obj; |
||||||
|
return Objects.equals(this.transactionAccumulator, that.transactionAccumulator) |
||||||
|
&& Objects.equals(this.transactionProcessingResult, that.transactionProcessingResult) |
||||||
|
&& this.isMiningBeneficiaryTouchedPreRewardByTransaction |
||||||
|
== that.isMiningBeneficiaryTouchedPreRewardByTransaction |
||||||
|
&& Objects.equals(this.miningBeneficiaryReward, that.miningBeneficiaryReward); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return Objects.hash( |
||||||
|
transactionAccumulator, |
||||||
|
transactionProcessingResult, |
||||||
|
isMiningBeneficiaryTouchedPreRewardByTransaction, |
||||||
|
miningBeneficiaryReward); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "ParallelizedTransactionContext[" |
||||||
|
+ "transactionAccumulator=" |
||||||
|
+ transactionAccumulator |
||||||
|
+ ", " |
||||||
|
+ "transactionProcessingResult=" |
||||||
|
+ transactionProcessingResult |
||||||
|
+ ", " |
||||||
|
+ "isMiningBeneficiaryTouchedPreRewardByTransaction=" |
||||||
|
+ isMiningBeneficiaryTouchedPreRewardByTransaction |
||||||
|
+ ", " |
||||||
|
+ "miningBeneficiaryReward=" |
||||||
|
+ miningBeneficiaryReward |
||||||
|
+ ']'; |
||||||
|
} |
||||||
|
|
||||||
|
public static class Builder { |
||||||
|
private DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator; |
||||||
|
private TransactionProcessingResult transactionProcessingResult; |
||||||
|
private boolean isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||||
|
private Wei miningBeneficiaryReward = Wei.ZERO; |
||||||
|
|
||||||
|
public Builder transactionAccumulator( |
||||||
|
final DiffBasedWorldStateUpdateAccumulator<?> transactionAccumulator) { |
||||||
|
this.transactionAccumulator = transactionAccumulator; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder transactionProcessingResult( |
||||||
|
final TransactionProcessingResult transactionProcessingResult) { |
||||||
|
this.transactionProcessingResult = transactionProcessingResult; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder isMiningBeneficiaryTouchedPreRewardByTransaction( |
||||||
|
final boolean isMiningBeneficiaryTouchedPreRewardByTransaction) { |
||||||
|
this.isMiningBeneficiaryTouchedPreRewardByTransaction = |
||||||
|
isMiningBeneficiaryTouchedPreRewardByTransaction; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder miningBeneficiaryReward(final Wei miningBeneficiaryReward) { |
||||||
|
this.miningBeneficiaryReward = miningBeneficiaryReward; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public ParallelizedTransactionContext build() { |
||||||
|
return new ParallelizedTransactionContext( |
||||||
|
transactionAccumulator, |
||||||
|
transactionProcessingResult, |
||||||
|
isMiningBeneficiaryTouchedPreRewardByTransaction, |
||||||
|
miningBeneficiaryReward); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.mainnet.parallelization; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.ethereum.core.Transaction; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; |
||||||
|
|
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
public class TransactionCollisionDetector { |
||||||
|
|
||||||
|
/** |
||||||
|
* Determines if a transaction has a collision based on the addresses it touches. A collision |
||||||
|
* occurs if the transaction touches the mining beneficiary address or if there are common |
||||||
|
* addresses touched by both the transaction and other transactions within the same block. |
||||||
|
* |
||||||
|
* @param transaction The transaction to check for collisions. |
||||||
|
* @param miningBeneficiary The address of the mining beneficiary. |
||||||
|
* @param parallelizedTransactionContext The context containing the accumulator for the |
||||||
|
* transaction. |
||||||
|
* @param blockAccumulator The accumulator for the block. |
||||||
|
* @return true if there is a collision; false otherwise. |
||||||
|
*/ |
||||||
|
public boolean hasCollision( |
||||||
|
final Transaction transaction, |
||||||
|
final Address miningBeneficiary, |
||||||
|
final ParallelizedTransactionContext parallelizedTransactionContext, |
||||||
|
final DiffBasedWorldStateUpdateAccumulator<?> blockAccumulator) { |
||||||
|
final Set<Address> addressesTouchedByTransaction = |
||||||
|
getAddressesTouchedByTransaction( |
||||||
|
transaction, Optional.of(parallelizedTransactionContext.transactionAccumulator())); |
||||||
|
if (addressesTouchedByTransaction.contains(miningBeneficiary)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
final Set<Address> addressesTouchedByBlock = |
||||||
|
getAddressesTouchedByBlock(Optional.of(blockAccumulator)); |
||||||
|
final Iterator<Address> it = addressesTouchedByTransaction.iterator(); |
||||||
|
boolean commonAddressFound = false; |
||||||
|
while (it.hasNext() && !commonAddressFound) { |
||||||
|
if (addressesTouchedByBlock.contains(it.next())) { |
||||||
|
commonAddressFound = true; |
||||||
|
} |
||||||
|
} |
||||||
|
return commonAddressFound; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the set of addresses that were touched by a transaction. This includes the sender and |
||||||
|
* recipient of the transaction, as well as any addresses that were read from or written to by the |
||||||
|
* transaction's execution. |
||||||
|
* |
||||||
|
* @param transaction The transaction to analyze. |
||||||
|
* @param accumulator An optional accumulator containing state changes made by the transaction. |
||||||
|
* @return A set of addresses touched by the transaction. |
||||||
|
*/ |
||||||
|
public Set<Address> getAddressesTouchedByTransaction( |
||||||
|
final Transaction transaction, |
||||||
|
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator) { |
||||||
|
HashSet<Address> addresses = new HashSet<>(); |
||||||
|
addresses.add(transaction.getSender()); |
||||||
|
if (transaction.getTo().isPresent()) { |
||||||
|
addresses.add(transaction.getTo().get()); |
||||||
|
} |
||||||
|
accumulator.ifPresent( |
||||||
|
diffBasedWorldStateUpdateAccumulator -> { |
||||||
|
diffBasedWorldStateUpdateAccumulator |
||||||
|
.getAccountsToUpdate() |
||||||
|
.forEach((address, diffBasedValue) -> addresses.add(address)); |
||||||
|
addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses()); |
||||||
|
}); |
||||||
|
return addresses; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the set of addresses that were touched by all transactions within a block. This |
||||||
|
* method filters out addresses that were only read and not modified. |
||||||
|
* |
||||||
|
* @param accumulator An optional accumulator containing state changes made by the block. |
||||||
|
* @return A set of addresses that were modified by the block's transactions. |
||||||
|
*/ |
||||||
|
private Set<Address> getAddressesTouchedByBlock( |
||||||
|
final Optional<DiffBasedWorldStateUpdateAccumulator<?>> accumulator) { |
||||||
|
HashSet<Address> addresses = new HashSet<>(); |
||||||
|
accumulator.ifPresent( |
||||||
|
diffBasedWorldStateUpdateAccumulator -> { |
||||||
|
diffBasedWorldStateUpdateAccumulator |
||||||
|
.getAccountsToUpdate() |
||||||
|
.forEach( |
||||||
|
(address, diffBasedValue) -> { |
||||||
|
if (!diffBasedValue.isUnchanged()) { |
||||||
|
addresses.add(address); |
||||||
|
} |
||||||
|
}); |
||||||
|
addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses()); |
||||||
|
}); |
||||||
|
return addresses; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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.trie.diffbased.bonsai.cache; |
||||||
|
|
||||||
|
import org.hyperledger.besu.datatypes.Address; |
||||||
|
import org.hyperledger.besu.datatypes.Hash; |
||||||
|
import org.hyperledger.besu.datatypes.StorageSlotKey; |
||||||
|
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; |
||||||
|
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||||
|
|
||||||
|
public class NoopBonsaiCachedMerkleTrieLoader extends BonsaiCachedMerkleTrieLoader { |
||||||
|
|
||||||
|
public NoopBonsaiCachedMerkleTrieLoader() { |
||||||
|
super(new NoOpMetricsSystem()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void preLoadAccount( |
||||||
|
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, |
||||||
|
final Hash worldStateRootHash, |
||||||
|
final Address account) { |
||||||
|
// noop
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void preLoadStorageSlot( |
||||||
|
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, |
||||||
|
final Address account, |
||||||
|
final StorageSlotKey slotKey) { |
||||||
|
// noop
|
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue