-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for Ethereum Recovery Proposals #1004
Changes from all commits
1105610
b6998b8
e802229
b1872d8
edb6159
1165e47
bcf62e2
a83bc41
746db76
6473841
06b5375
a678f42
82c335d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/* | ||
* Copyright (c) [2016] [ <ether.camp> ] | ||
* This file is part of the ethereumJ library. | ||
* | ||
* The ethereumJ library is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* The ethereumJ library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.ethereum.config.blockchain; | ||
|
||
import org.apache.commons.lang3.tuple.Pair; | ||
import org.ethereum.config.BlockchainConfig; | ||
import org.ethereum.core.Block; | ||
import org.ethereum.core.BlockHeader; | ||
import org.ethereum.core.Repository; | ||
import org.ethereum.core.Transaction; | ||
import org.ethereum.erp.ErpExecutor; | ||
import org.ethereum.erp.ErpLoader; | ||
import org.ethereum.erp.ErpLoader.ErpMetadata; | ||
import org.ethereum.erp.StateChangeObject; | ||
import org.ethereum.validator.BlockHeaderRule; | ||
import org.ethereum.validator.BlockHeaderValidator; | ||
import org.ethereum.validator.ExtraDataPresenceRule; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.IOException; | ||
import java.math.BigInteger; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
|
||
import static java.util.stream.Collectors.toMap; | ||
|
||
/** | ||
* Created by Dan Phifer, 2018-02-01. | ||
*/ | ||
public class ErpConfig extends FrontierConfig /* TODO: Is FrontierConfig correct? */ { | ||
|
||
private final long EXTRA_DATA_AFFECTS_BLOCKS_NUMBER = 10; | ||
public static final Logger logger = LoggerFactory.getLogger("config"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
|
||
private BlockchainConfig parent; | ||
private Map<Long, ErpMetadata> erpDataByTargetBlock; | ||
private ErpLoader erpLoader; | ||
private ErpExecutor erpExecutor; | ||
|
||
public ErpConfig() { | ||
this(new HomesteadConfig(), new ErpLoader("/erps"), new ErpExecutor()); | ||
} | ||
|
||
public ErpConfig(BlockchainConfig parent, ErpLoader erpLoader, ErpExecutor erpExecutor) { | ||
this.erpLoader = erpLoader; | ||
this.erpExecutor = erpExecutor; | ||
this.parent = parent; | ||
this.constants = parent.getConstants(); | ||
|
||
try { | ||
initErpConfig(); | ||
} catch (IOException e) { | ||
// TODO: not sure what to do here. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throwing |
||
logger.error("Failed to load the ERPConfig", e); | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
void initErpConfig() throws IOException { | ||
// load the config block numbers | ||
final Collection<ErpMetadata> allErps = erpLoader.loadErpMetadata(); | ||
this.erpDataByTargetBlock = allErps | ||
.stream() | ||
.collect(toMap(ErpMetadata::getTargetBlock, Function.identity())); | ||
|
||
logger.info("Found %d ERPs", allErps.size()); | ||
|
||
// add the header validators for each known ERP | ||
final List<Pair<Long, BlockHeaderValidator>> headerValidators = headerValidators(); | ||
allErps.forEach(erpMetadata -> { | ||
BlockHeaderRule rule = new ExtraDataPresenceRule(erpMetadata.getErpMarker(), true); | ||
headerValidators.add(Pair.of(erpMetadata.getTargetBlock(), new BlockHeaderValidator(rule))); | ||
}); | ||
} | ||
|
||
/** | ||
* Miners should include marker for initial 10 blocks. Either "dao-hard-fork" or "" | ||
*/ | ||
@Override | ||
public byte[] getExtraData(byte[] minerExtraData, long blockNumber) { | ||
// TODO: is EXTRA_DATA_AFFECTS_BLOCKS_NUMBER needed? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may use this code if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so. I saw in the DaoHFConfig that the extra data was applied not just at the target block, but for an a total of 10 blocks. I wasn't sure why that was done or if the same logic would be needed in this case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose it was done to check if miners accept the fork. Adding data in ten blocks in a row is more confident than just one |
||
final ErpMetadata erpMetadata = erpDataByTargetBlock.get(blockNumber); | ||
return erpMetadata != null | ||
? erpMetadata.getErpMarker() | ||
: minerExtraData; | ||
} | ||
|
||
@Override | ||
public void hardForkTransfers(Block block, Repository repo) { | ||
final ErpMetadata erpMetadata = erpDataByTargetBlock.get(block.getNumber()); | ||
if (erpMetadata != null) { | ||
logger.info("Found ERP {} for block {}", erpMetadata.getId(), erpMetadata.getTargetBlock()); | ||
doHardForkTransfers(erpMetadata, repo); | ||
} | ||
} | ||
|
||
void doHardForkTransfers(ErpMetadata erpMetadata, Repository repo) { | ||
final StateChangeObject sco; | ||
try { | ||
sco = erpLoader.loadStateChangeObject(erpMetadata); | ||
} catch (IOException e) { | ||
logger.error("Failed to load state change object for {}", erpMetadata.getId(), e); | ||
throw new RuntimeException("Failed to load state change object for " + erpMetadata.getId(), e); | ||
} | ||
|
||
// TODO: Is this the right way to apply changes in batch? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's implemented in the right way |
||
final Repository track = repo.startTracking(); | ||
try { | ||
erpExecutor.applyStateChanges(sco, track); | ||
track.commit(); | ||
logger.info("Successfully applied ERP '{}' to block {}", erpMetadata.getId(), erpMetadata.getTargetBlock()); | ||
} | ||
catch (ErpExecutor.ErpExecutionException e) { | ||
track.rollback(); | ||
logger.error("Failed to apply ERP '{}' to block {}", erpMetadata.getId(), erpMetadata.getTargetBlock(), e); | ||
} | ||
catch (Exception e) { | ||
track.rollback(); | ||
logger.error("Failed to apply ERP '{}' to block {}", erpMetadata.getId(), erpMetadata.getTargetBlock(), e); | ||
throw e; | ||
} | ||
finally { | ||
track.close(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't have to do this, just omit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking it would actually be better to leave it. The startTracking method on the Repository interface returns another Repository; it makes no promise about the implementation. Whether the close method is a no-op or not is an implementation detail of the returned class, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, let's keep it there |
||
} | ||
} | ||
|
||
|
||
////////////////////////////////////////////// | ||
// TODO: These methods we included in DaoHFConfig, so I have included them here as well. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't have to implement them since they are implemented by parent class |
||
// I don't know enough about the Config setup to understand if they are needed or | ||
// if other methods are needed as well. | ||
////////////////////////////////////////////// | ||
|
||
|
||
@Override | ||
public BigInteger calcDifficulty(BlockHeader curBlock, BlockHeader parent) { | ||
return this.parent.calcDifficulty(curBlock, parent); | ||
} | ||
|
||
@Override | ||
public long getTransactionCost(Transaction tx) { | ||
return parent.getTransactionCost(tx); | ||
} | ||
|
||
@Override | ||
public boolean acceptTransactionSignature(Transaction tx) { | ||
return parent.acceptTransactionSignature(tx); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,5 +32,6 @@ public MainNetConfig() { | |
add(2_463_000, new Eip150HFConfig(new DaoHFConfig())); | ||
add(2_675_000, new Eip160HFConfig(new DaoHFConfig())); | ||
add(4_370_000, new ByzantiumConfig(new DaoHFConfig())); | ||
add(6_000_000, new ErpConfig()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take a look at JsonNetConfig you might want to handle |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/* | ||
* Copyright (c) [2016] [ <ether.camp> ] | ||
* This file is part of the ethereumJ library. | ||
* | ||
* The ethereumJ library is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* The ethereumJ library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.ethereum.erp; | ||
|
||
import org.ethereum.core.Repository; | ||
import org.ethereum.erp.StateChangeObject.StateChangeAction; | ||
|
||
import java.util.Arrays; | ||
|
||
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; | ||
import static org.ethereum.util.ByteUtil.toHexString; | ||
|
||
/** | ||
* The ERP Executor applies State Change Actions to a Repository. | ||
*/ | ||
public class ErpExecutor { | ||
public static final String WEI_TRANSFER = "weiTransfer"; | ||
public static final String STORE_CODE = "storeCode"; | ||
|
||
public static class ErpExecutionException extends Exception { | ||
ErpExecutionException(Throwable cause) { | ||
super(cause); | ||
} | ||
} | ||
|
||
public void applyStateChanges(StateChangeObject sco, Repository repo) throws ErpExecutionException { | ||
try { | ||
for (StateChangeAction action : sco.actions) { | ||
applyStateChangeAction(action, repo); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applying code could be a part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I will change to a custom deserializer, which will remove the RawStateChangeObject and will allow me to have separate action classes for each action type. so the interface will be |
||
} | ||
} | ||
catch (IllegalArgumentException | UnsupportedOperationException e) { | ||
throw new ErpExecutionException(e); | ||
} | ||
} | ||
|
||
void applyStateChangeAction(StateChangeAction action, Repository repo) { | ||
switch (action.type) { | ||
case WEI_TRANSFER: | ||
applyWeiTransfer(action, repo); | ||
break; | ||
case STORE_CODE: | ||
applyStoreCode(action, repo); | ||
break; | ||
default: | ||
throw new UnsupportedOperationException("ERP action type not supported. " + action.type); | ||
} | ||
} | ||
|
||
void applyStoreCode(StateChangeAction action, Repository repo) { | ||
if (Arrays.equals(action.toAddress, EMPTY_BYTE_ARRAY)) | ||
throw new IllegalArgumentException("storeCode cannot store code at an empty address"); | ||
|
||
if (!Arrays.equals(action.expectedCodeHash, repo.getCodeHash(action.toAddress))) | ||
throw new IllegalStateException(String.format("ERP storeCode did not find the expected hash. Expected %s, found %s", toHexString(action.expectedCodeHash), toHexString(repo.getCodeHash(action.toAddress)))); | ||
|
||
repo.saveCode(action.toAddress, action.code); | ||
} | ||
|
||
void applyWeiTransfer(StateChangeAction action, Repository repo) { | ||
if (Arrays.equals(action.toAddress, EMPTY_BYTE_ARRAY)) | ||
throw new IllegalArgumentException("weiTransfer cannot transfer to an empty address"); | ||
|
||
// is this needed here? Seems like this check should happen at a lower level (i.e. in the repo) | ||
if (repo.getBalance(action.fromAddress).compareTo(action.valueInWei) < 0) | ||
throw new IllegalStateException(String.format("ERP insufficient balance for weiTransfer. Sender balance of %s less than transfer amount of %s", repo.getBalance(action.fromAddress).toString(), action.valueInWei.toString())); | ||
|
||
repo.addBalance(action.fromAddress, action.valueInWei.negate()); | ||
repo.addBalance(action.toAddress, action.valueInWei); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* Copyright (c) [2016] [ <ether.camp> ] | ||
* This file is part of the ethereumJ library. | ||
* | ||
* The ethereumJ library is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* The ethereumJ library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.ethereum.erp; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.ethereum.util.ByteUtil; | ||
|
||
import java.io.File; | ||
import java.io.FilenameFilter; | ||
import java.io.IOException; | ||
import java.net.URL; | ||
import java.util.Collection; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
|
||
public class ErpLoader { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it could be simplified somehow. For example, there is no need for extra-classes like |
||
public static final String SCO_EXTENSION = ".sco.json"; | ||
private static final FilenameFilter SCO_FILE_FILTER = (dir, name) -> name.endsWith(SCO_EXTENSION); | ||
private final String resourceDir; | ||
|
||
public ErpLoader(String resourceDir) { | ||
this.resourceDir = resourceDir; | ||
} | ||
|
||
/** | ||
* A lightweight object that can be held in memory | ||
*/ | ||
public static class ErpMetadata { | ||
Long targetBlock; | ||
File resourceFile; | ||
String erpId; | ||
byte[] erpMarker; | ||
|
||
ErpMetadata(String erpId, Long targetBlock, File resourceFile) { | ||
this.erpId = erpId; | ||
this.targetBlock = targetBlock; | ||
this.resourceFile = resourceFile; | ||
this.erpMarker = ByteUtil.hexStringToBytes(asciiToHex(erpId)); | ||
} | ||
|
||
private static String asciiToHex(String asciiValue){ | ||
char[] chars = asciiValue.toCharArray(); | ||
StringBuilder hex = new StringBuilder(); | ||
for (int i = 0; i < chars.length; i++) { | ||
hex.append(Integer.toHexString((int) chars[i])); | ||
} | ||
return hex.toString(); | ||
} | ||
|
||
public byte[] getErpMarker() { | ||
return erpMarker; | ||
} | ||
|
||
public String getId() { | ||
return erpId; | ||
} | ||
|
||
public long getTargetBlock() { | ||
return targetBlock; | ||
} | ||
} | ||
|
||
/** | ||
* This method returns a collection of lightweight objects, but it parses each | ||
* file to pull out the erpId and the target block as well as sanity check the actions | ||
* | ||
* @return A collection of ERPs that are available | ||
* @throws IOException if any of the ERP files could not be loaded | ||
*/ | ||
public Collection<ErpMetadata> loadErpMetadata() throws IOException { | ||
// this is somewhat inefficient, but the goal is to ensure that all important | ||
// data is loaded from the SCO object itself | ||
List<ErpMetadata> allMetadata = new LinkedList<>(); | ||
for (File f : loadERPResourceFiles(this.resourceDir)) { | ||
final StateChangeObject sco = loadStateChangeObject(f); | ||
allMetadata.add(new ErpMetadata(sco.erpId, sco.targetBlock, f)); | ||
} | ||
return allMetadata; | ||
} | ||
|
||
/** | ||
* Loads the StateChangeObject from a file synchronously | ||
* @param metadata The ERP metadata object | ||
* @return A StateChangeObject that can be executed against a repo by the ErpExecutor | ||
* @throws IOException if the StateChangeObject cannot be loaded. | ||
*/ | ||
public StateChangeObject loadStateChangeObject(ErpMetadata metadata) throws IOException { | ||
return loadStateChangeObject(metadata.resourceFile); | ||
} | ||
|
||
File[] loadERPResourceFiles(String erpResourceDir) throws IOException { | ||
URL url = getClass().getResource(erpResourceDir); | ||
|
||
File[] files = url != null | ||
? new File(url.getPath()).listFiles(SCO_FILE_FILTER) | ||
: null; | ||
|
||
// an empty array is ok (which would mean there are no files in the directory) | ||
if (files == null) | ||
throw new IOException("The specified resource directory does not exists: " + erpResourceDir); | ||
|
||
return files; | ||
} | ||
|
||
StateChangeObject loadStateChangeObject(File f) throws IOException { | ||
return StateChangeObject.parse(loadRawStateChangeObject(f)); | ||
} | ||
|
||
RawStateChangeObject loadRawStateChangeObject(File f) throws IOException { | ||
ObjectMapper objectMapper = new ObjectMapper(); | ||
return objectMapper.readValue(f, RawStateChangeObject.class); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess
ErpConfig
should extendByzantiumConfig
. This assumption is based on further6_000_000
block number used in the context. Check ByzantiumConfig implementation for details