Coverage Summary for Class: GenesisLoaderImpl (org.ethereum.core.genesis)
Class |
Class, %
|
Method, %
|
Line, %
|
GenesisLoaderImpl |
100%
(1/1)
|
90%
(9/10)
|
76%
(79/104)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2019 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package org.ethereum.core.genesis;
20
21 import co.rsk.core.Coin;
22 import co.rsk.core.RskAddress;
23 import co.rsk.core.bc.BlockExecutor;
24 import co.rsk.db.StateRootHandler;
25 import co.rsk.trie.Trie;
26 import co.rsk.trie.TrieStore;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import org.bouncycastle.util.encoders.Hex;
29 import org.ethereum.config.blockchain.upgrades.ActivationConfig;
30 import org.ethereum.config.blockchain.upgrades.ConsensusRule;
31 import org.ethereum.core.AccountState;
32 import org.ethereum.core.Genesis;
33 import org.ethereum.core.GenesisHeader;
34 import org.ethereum.core.Repository;
35 import org.ethereum.crypto.HashUtil;
36 import org.ethereum.db.MutableRepository;
37 import org.ethereum.json.Utils;
38 import org.ethereum.util.ByteUtil;
39 import org.ethereum.util.RLP;
40 import org.ethereum.vm.DataWord;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import java.io.InputStream;
45 import java.math.BigInteger;
46 import java.util.HashMap;
47 import java.util.Map;
48
49 /**
50 * 1. Reads the genesis configuration from storage
51 * 2. Loads the genesis initial state and sets the expected state root
52 * 3. Stores the genesis initial state trie in the global storage
53 * 4. Registers the genesis state root in the state root handler
54 */
55 public class GenesisLoaderImpl implements GenesisLoader {
56 private static final byte[] EMPTY_LIST_HASH = HashUtil.keccak256(RLP.encodeList());
57 private static final Logger logger = LoggerFactory.getLogger(GenesisLoaderImpl.class);
58
59 private final ActivationConfig activationConfig;
60 private final StateRootHandler stateRootHandler;
61 private final TrieStore trieStore;
62
63 private final BigInteger initialNonce;
64 private final boolean isRsk;
65 private final InputStream resourceAsStream;
66 private final boolean useRskip92Encoding;
67 private final boolean isRskip126Enabled;
68
69 public GenesisLoaderImpl(
70 ActivationConfig activationConfig,
71 StateRootHandler stateRootHandler,
72 TrieStore trieStore,
73 String genesisFile,
74 BigInteger initialNonce,
75 boolean isRsk,
76 boolean useRskip92Encoding,
77 boolean isRskip126Enabled) {
78 this(
79 activationConfig,
80 stateRootHandler,
81 trieStore,
82 GenesisLoaderImpl.class.getResourceAsStream("/genesis/" + genesisFile),
83 initialNonce,
84 isRsk,
85 useRskip92Encoding,
86 isRskip126Enabled
87 );
88 }
89
90 public GenesisLoaderImpl(
91 ActivationConfig activationConfig,
92 StateRootHandler stateRootHandler,
93 TrieStore trieStore,
94 InputStream resourceAsStream,
95 BigInteger initialNonce,
96 boolean isRsk,
97 boolean useRskip92Encoding,
98 boolean isRskip126Enabled) {
99 this.activationConfig = activationConfig;
100 this.stateRootHandler = stateRootHandler;
101 this.trieStore = trieStore;
102
103 this.initialNonce = initialNonce;
104 this.isRsk = isRsk;
105 this.useRskip92Encoding = useRskip92Encoding;
106 this.isRskip126Enabled = isRskip126Enabled;
107 this.resourceAsStream = resourceAsStream;
108 }
109
110 @Override
111 public Genesis load() {
112 Genesis incompleteGenesis = readFromJson();
113 Trie genesisTrie = loadGenesisTrie(incompleteGenesis);
114 updateGenesisStateRoot(genesisTrie, incompleteGenesis);
115 return incompleteGenesis;
116 }
117
118 private Genesis readFromJson() {
119 try {
120 try {
121 GenesisJson genesisJson = new ObjectMapper().readValue(resourceAsStream, GenesisJson.class);
122 Genesis genesis = mapFromJson(genesisJson);
123 genesis.flushRLP();
124
125 return genesis;
126 } finally {
127 resourceAsStream.close();
128 }
129 } catch (Exception e) {
130 System.err.println("Genesis block configuration is corrupted or not found ./resources/genesis/...");
131 logger.error("Genesis block configuration is corrupted or not found ./resources/genesis/...", e);
132 System.exit(-1);
133 return null;
134 }
135 }
136
137 private Genesis mapFromJson(GenesisJson json) {
138 byte[] difficulty = Utils.parseData(json.difficulty);
139 byte[] coinbase = Utils.parseData(json.coinbase);
140
141 byte[] timestampBytes = Utils.parseData(json.timestamp);
142 long timestamp = ByteUtil.byteArrayToLong(timestampBytes);
143
144 byte[] parentHash = Utils.parseData(json.parentHash);
145 byte[] extraData = Utils.parseData(json.extraData);
146
147 byte[] gasLimitBytes = Utils.parseData(json.gasLimit);
148 long gasLimit = ByteUtil.byteArrayToLong(gasLimitBytes);
149
150 byte[] bitcoinMergedMiningHeader = null;
151 byte[] bitcoinMergedMiningMerkleProof = null;
152 byte[] bitcoinMergedMiningCoinbaseTransaction = null;
153 byte[] minGasPrice = null;
154
155 if (isRsk) {
156 bitcoinMergedMiningHeader = Utils.parseData(json.bitcoinMergedMiningHeader);
157 bitcoinMergedMiningMerkleProof = Utils.parseData(json.bitcoinMergedMiningMerkleProof);
158 bitcoinMergedMiningCoinbaseTransaction = Utils.parseData(json.bitcoinMergedMiningCoinbaseTransaction);
159 minGasPrice = Utils.parseData(json.getMinimumGasPrice());
160 }
161
162 Map<RskAddress, AccountState> accounts = new HashMap<>();
163 Map<RskAddress, byte[]> codes = new HashMap<>();
164 Map<RskAddress, Map<DataWord, byte[]>> storages = new HashMap<>();
165 Map<String, AllocatedAccount> alloc = json.getAlloc();
166 for (Map.Entry<String, AllocatedAccount> accountEntry : alloc.entrySet()) {
167 if(!"00".equals(accountEntry.getKey())) {
168 Coin balance = new Coin(new BigInteger(accountEntry.getValue().getBalance()));
169 BigInteger accountNonce;
170
171 if (accountEntry.getValue().getNonce() != null) {
172 accountNonce = new BigInteger(accountEntry.getValue().getNonce());
173 } else {
174 accountNonce = initialNonce;
175 }
176
177 AccountState acctState = new AccountState(accountNonce, balance);
178 Contract contract = accountEntry.getValue().getContract();
179
180 RskAddress address = new RskAddress(accountEntry.getKey());
181 if (contract != null) {
182 byte[] code = Hex.decode(contract.getCode());
183 codes.put(address, code);
184 Map<DataWord, byte[]> storage = new HashMap<>(contract.getData().size());
185 for (Map.Entry<String, String> storageData : contract.getData().entrySet()) {
186 storage.put(DataWord.valueFromHex(storageData.getKey()), Hex.decode(storageData.getValue()));
187 }
188 storages.put(address, storage);
189 }
190 accounts.put(address, acctState);
191 }
192 }
193
194 GenesisHeader header = new GenesisHeader(
195 parentHash,
196 EMPTY_LIST_HASH,
197 Genesis.getZeroHash(),
198 difficulty,
199 0,
200 ByteUtil.longToBytes(gasLimit),
201 0,
202 timestamp,
203 extraData,
204 bitcoinMergedMiningHeader,
205 bitcoinMergedMiningMerkleProof,
206 bitcoinMergedMiningCoinbaseTransaction,
207 minGasPrice,
208 useRskip92Encoding,
209 coinbase);
210 return new Genesis(isRskip126Enabled, accounts, codes, storages, header);
211 }
212
213 private Trie loadGenesisTrie(Genesis genesis) {
214 Repository repository = new MutableRepository(trieStore, new Trie(trieStore));
215 loadGenesisInitalState(repository, genesis);
216
217 setupPrecompiledContractsStorage(repository);
218
219 repository.commit();
220 repository.save();
221 return repository.getTrie();
222 }
223
224 private void updateGenesisStateRoot(Trie genesisTrie, Genesis genesis) {
225 genesis.setStateRoot(stateRootHandler.convert(genesis.getHeader(), genesisTrie).getBytes());
226 genesis.flushRLP();
227 stateRootHandler.register(genesis.getHeader(), genesisTrie);
228 }
229
230 /**
231 * When created, contracts are marked in storage for consistency.
232 * Here, we apply this logic to precompiled contracts depending on consensus rules
233 */
234 private void setupPrecompiledContractsStorage(Repository repository) {
235 ActivationConfig.ForBlock genesisActivations = activationConfig.forBlock(0);
236 if (genesisActivations.isActivating(ConsensusRule.RSKIP126)) {
237 BlockExecutor.maintainPrecompiledContractStorageRoots(repository, genesisActivations);
238 }
239 }
240
241 public static void loadGenesisInitalState(Repository repository, Genesis genesis) {
242 // first we need to create the accounts, which creates also the associated ContractDetails
243 for (RskAddress accounts : genesis.getAccounts().keySet()) {
244 repository.createAccount(accounts);
245 }
246
247 // second we create contracts whom only have code modifying the preexisting ContractDetails instance
248 for (Map.Entry<RskAddress, byte[]> codeEntry : genesis.getCodes().entrySet()) {
249 RskAddress contractAddress = codeEntry.getKey();
250 repository.setupContract(contractAddress);
251 repository.saveCode(contractAddress, codeEntry.getValue());
252 Map<DataWord, byte[]> contractStorage = genesis.getStorages().get(contractAddress);
253 for (Map.Entry<DataWord, byte[]> storageEntry : contractStorage.entrySet()) {
254 repository.addStorageBytes(contractAddress, storageEntry.getKey(), storageEntry.getValue());
255 }
256 }
257
258 // given the accounts had the proper storage root set from the genesis construction we update the account state
259 for (Map.Entry<RskAddress, AccountState> accountEntry : genesis.getAccounts().entrySet()) {
260 repository.updateAccountState(accountEntry.getKey(), accountEntry.getValue());
261 }
262 }
263 }