adapting for poc-7 final (eth:47) :
+ Repository refactoring
This commit is contained in:
parent
1907c0691f
commit
254c51704a
|
@ -130,8 +130,9 @@ public class BlockchainImpl implements Blockchain {
|
|||
recordBlock(block);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Try connect block: {}",
|
||||
Hex.toHexString(block.getEncoded()));
|
||||
logger.debug("Try connect block hash: {}, number: {}",
|
||||
Hex.toHexString(block.getHash()).substring(0, 6),
|
||||
block.getNumber());
|
||||
|
||||
if (blockStore.getBlockByHash(block.getHash()) != null){
|
||||
// retry of well known block
|
||||
|
@ -212,6 +213,9 @@ public class BlockchainImpl implements Blockchain {
|
|||
track.commit();
|
||||
repository.flush(); // saving to the disc
|
||||
|
||||
stateLogger.info("applied reward for block: [{}] \n state: [{}]",
|
||||
block.getNumber(),
|
||||
Hex.toHexString(repository.getRoot()));
|
||||
|
||||
// Remove all wallet transactions as they already approved by the net
|
||||
worldManager.getWallet().removeTransactions(block.getTransactionsList());
|
||||
|
@ -329,7 +333,7 @@ public class BlockchainImpl implements Blockchain {
|
|||
track.commit();
|
||||
|
||||
receipt.setPostTxState(repository.getRoot());
|
||||
stateLogger.info("executed block: [{}] tx: [{}] \n state: [{}]", block.getNumber(), i,
|
||||
stateLogger.info("block: [{}] executed tx: [{}] \n state: [{}]", block.getNumber(), i,
|
||||
Hex.toHexString(repository.getRoot()));
|
||||
|
||||
if(block.getNumber() >= CONFIG.traceStartBlock())
|
||||
|
@ -579,8 +583,7 @@ public class BlockchainImpl implements Blockchain {
|
|||
BigInteger gasPrice, Repository repository, byte[] senderAddress,
|
||||
byte[] contractAddress, byte[] coinbase, boolean initResults) {
|
||||
|
||||
if (result.getException() != null
|
||||
&& result.getException() instanceof Program.OutOfGasException) {
|
||||
if (result.getException() != null) {
|
||||
stateLogger.debug("contract run halted by OutOfGas: contract={}",
|
||||
Hex.toHexString(contractAddress));
|
||||
throw result.getException();
|
||||
|
|
|
@ -24,7 +24,7 @@ public class ContractDetails {
|
|||
|
||||
private byte[] code = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
|
||||
private boolean dirty = true;
|
||||
private boolean dirty = false;
|
||||
private boolean deleted = false;
|
||||
|
||||
private Trie storageTrie = new TrieImpl(null);
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.List;
|
|||
|
||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||
import static org.ethereum.crypto.SHA3Helper.*;
|
||||
import static org.ethereum.util.ByteUtil.wrap;
|
||||
|
||||
/**
|
||||
* www.etherj.com
|
||||
|
@ -88,40 +89,39 @@ public class RepositoryImpl implements Repository {
|
|||
public void updateBatch(HashMap<ByteArrayWrapper, AccountState> stateCache,
|
||||
HashMap<ByteArrayWrapper, ContractDetails> detailsCache) {
|
||||
|
||||
for (ByteArrayWrapper hash : detailsCache.keySet()) {
|
||||
|
||||
ContractDetails contractDetails = detailsCache.get(hash);
|
||||
|
||||
if (contractDetails.isDeleted())
|
||||
detailsDB.delete(hash.getData());
|
||||
else{
|
||||
if (contractDetails.isDirty())
|
||||
detailsDB.put(hash.getData(), contractDetails.getEncoded());
|
||||
}
|
||||
|
||||
if (contractDetails.isDirty() || contractDetails.isDeleted()){
|
||||
|
||||
AccountState accountState = stateCache.get(hash);
|
||||
accountState.setStateRoot(contractDetails.getStorageHash());
|
||||
accountState.setCodeHash(sha3(contractDetails.getCode()));
|
||||
}
|
||||
|
||||
contractDetails.setDeleted(false);
|
||||
contractDetails.setDirty(false);
|
||||
}
|
||||
|
||||
for (ByteArrayWrapper hash : detailsCache.keySet()) {
|
||||
for (ByteArrayWrapper hash : stateCache.keySet()) {
|
||||
|
||||
AccountState accountState = stateCache.get(hash);
|
||||
ContractDetails contractDetails = detailsCache.get(hash);
|
||||
|
||||
if (accountState.isDeleted())
|
||||
if (accountState.isDeleted()){
|
||||
worldState.delete(hash.getData());
|
||||
else{
|
||||
if (accountState.isDirty()){
|
||||
detailsDB.delete(hash.getData());
|
||||
|
||||
logger.debug("delete: [{}]",
|
||||
Hex.toHexString(hash.getData()));
|
||||
|
||||
} else{
|
||||
|
||||
if (contractDetails.isDirty()){
|
||||
detailsDB.put(hash.getData(), contractDetails.getEncoded());
|
||||
accountState.setStateRoot(contractDetails.getStorageHash());
|
||||
accountState.setCodeHash(sha3(contractDetails.getCode()));
|
||||
worldState.update(hash.getData(), accountState.getEncoded());
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("update: [{}],nonce: [{}] balance: [{}] \n [{}]",
|
||||
Hex.toHexString(hash.getData()),
|
||||
accountState.getNonce(),
|
||||
accountState.getBalance(),
|
||||
contractDetails.getStorage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!contractDetails.isDirty() && accountState.isDirty()){
|
||||
worldState.update(hash.getData(), accountState.getEncoded());
|
||||
|
||||
if (logger.isDebugEnabled()){
|
||||
|
||||
logger.debug("update: [{}],nonce: [{}] balance: [{}]",
|
||||
Hex.toHexString(hash.getData()),
|
||||
accountState.getNonce(),
|
||||
|
@ -131,8 +131,8 @@ public class RepositoryImpl implements Repository {
|
|||
}
|
||||
}
|
||||
|
||||
accountState.setDeleted(false);
|
||||
accountState.setDirty(false);
|
||||
detailsCache.remove(hash.getData());
|
||||
stateCache.remove(hash.getData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,6 +404,28 @@ public class RepositoryImpl implements Repository {
|
|||
return accountState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAccount(byte[] addr,
|
||||
HashMap<ByteArrayWrapper, AccountState> cacheAccounts,
|
||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails) {
|
||||
|
||||
AccountState account = getAccountState(addr);
|
||||
ContractDetails details = getContractDetails(addr);
|
||||
|
||||
if (account == null)
|
||||
account = new AccountState();
|
||||
else
|
||||
account = account.clone();
|
||||
|
||||
if (details == null)
|
||||
details = new ContractDetails();
|
||||
else
|
||||
details = details.clone();
|
||||
|
||||
cacheAccounts.put(wrap(addr), account);
|
||||
cacheDetails.put(wrap(addr), details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRoot() {
|
||||
return worldState.getRootHash();
|
||||
|
|
|
@ -56,30 +56,40 @@ public class RepositoryTrack implements Repository {
|
|||
AccountState accountState = cacheAccounts.get(wrap(addr));
|
||||
|
||||
if (accountState == null){
|
||||
accountState = repository.getAccountState(addr);
|
||||
|
||||
if (accountState == null){
|
||||
|
||||
accountState = createAccount(addr);
|
||||
} else {
|
||||
|
||||
accountState = accountState.clone();
|
||||
cacheAccounts.put(wrap(addr), accountState);
|
||||
|
||||
ContractDetails contractDetails = repository.getContractDetails(addr);
|
||||
cacheDetails.put(wrap(addr), contractDetails.clone());
|
||||
}
|
||||
repository.loadAccount(addr, cacheAccounts, cacheDetails);
|
||||
accountState = cacheAccounts.get(wrap(addr));
|
||||
}
|
||||
return accountState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractDetails getContractDetails(byte[] addr) {
|
||||
getAccountState(addr);
|
||||
|
||||
ContractDetails contractDetails = cacheDetails.get(wrap(addr));
|
||||
|
||||
if (contractDetails == null){
|
||||
repository.loadAccount(addr, cacheAccounts, cacheDetails);
|
||||
contractDetails = cacheDetails.get(wrap(addr));
|
||||
}
|
||||
|
||||
return contractDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAccount(byte[] addr, HashMap<ByteArrayWrapper, AccountState> cacheAccounts,
|
||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails){
|
||||
|
||||
AccountState accountState = this.cacheAccounts.get(wrap(addr));
|
||||
ContractDetails contractDetails = this.cacheDetails.get(wrap(addr));
|
||||
|
||||
if (accountState == null){
|
||||
repository.loadAccount(addr, cacheAccounts, cacheDetails);
|
||||
} else {
|
||||
cacheAccounts.put(wrap(addr), accountState.clone());
|
||||
cacheDetails.put(wrap(addr), contractDetails.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void delete(byte[] addr) {
|
||||
|
|
|
@ -190,4 +190,6 @@ public interface Repository {
|
|||
|
||||
public byte[] getRoot();
|
||||
|
||||
void loadAccount(byte[] addr, HashMap<ByteArrayWrapper, AccountState> cacheAccounts,
|
||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,9 @@ public class TestRunner {
|
|||
Repository repository = new RepositoryImpl();
|
||||
|
||||
try {
|
||||
System.out.println("\nRunning test case: " + testCase.getName());
|
||||
System.out.println("\n***");
|
||||
System.out.println(" Running test case: [" + testCase.getName() + "]") ;
|
||||
System.out.println("***\n");
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
System.out.println("--------- PRE ---------");
|
||||
|
|
|
@ -42,7 +42,7 @@ import static org.ethereum.net.message.StaticMessages.GET_TRANSACTIONS_MESSAGE;
|
|||
@Scope("prototype")
|
||||
public class EthHandler extends SimpleChannelInboundHandler<EthMessage> {
|
||||
|
||||
public final static byte VERSION = 46;
|
||||
public final static byte VERSION = 47;
|
||||
public final static byte NETWORK_ID = 0x0;
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger("net");
|
||||
|
|
|
@ -6,6 +6,8 @@ import org.ethereum.net.MessageQueue;
|
|||
import org.ethereum.net.eth.EthHandler;
|
||||
import org.ethereum.net.p2p.P2pHandler;
|
||||
import org.ethereum.net.shh.ShhHandler;
|
||||
import org.ethereum.net.wire.MessageDecoder;
|
||||
import org.ethereum.net.wire.MessageEncoder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -35,6 +37,14 @@ public class Channel {
|
|||
@Autowired
|
||||
ShhHandler shhHandler;
|
||||
|
||||
@Autowired
|
||||
MessageDecoder messageDecoder;
|
||||
|
||||
@Autowired
|
||||
MessageEncoder messageEncoder;
|
||||
|
||||
|
||||
|
||||
|
||||
public Channel() {
|
||||
}
|
||||
|
@ -57,6 +67,14 @@ public class Channel {
|
|||
return shhHandler;
|
||||
}
|
||||
|
||||
public MessageDecoder getMessageDecoder() {
|
||||
return messageDecoder;
|
||||
}
|
||||
|
||||
public MessageEncoder getMessageEncoder() {
|
||||
return messageEncoder;
|
||||
}
|
||||
|
||||
public void sendTransaction(Transaction tx){
|
||||
ethHandler.sendTransaction(tx);
|
||||
}
|
||||
|
|
|
@ -46,12 +46,6 @@ public class EthereumChannelInitializer extends ChannelInitializer<NioSocketChan
|
|||
@Autowired
|
||||
ChannelManager channelManager;
|
||||
|
||||
@Autowired
|
||||
MessageDecoder messageDecoder;
|
||||
|
||||
@Autowired
|
||||
MessageEncoder messageEncoder;
|
||||
|
||||
@Autowired
|
||||
WorldManager worldManager;
|
||||
|
||||
|
@ -70,8 +64,8 @@ public class EthereumChannelInitializer extends ChannelInitializer<NioSocketChan
|
|||
|
||||
ch.pipeline().addLast("readTimeoutHandler",
|
||||
new ReadTimeoutHandler(CONFIG.peerChannelReadTimeout(), TimeUnit.SECONDS));
|
||||
ch.pipeline().addLast("out encoder", messageEncoder);
|
||||
ch.pipeline().addLast("in encoder", messageDecoder);
|
||||
ch.pipeline().addLast("out encoder", channel.getMessageEncoder());
|
||||
ch.pipeline().addLast("in encoder", channel.getMessageDecoder());
|
||||
ch.pipeline().addLast(Capability.P2P, channel.getP2pHandler());
|
||||
ch.pipeline().addLast(Capability.ETH, channel.getEthHandler());
|
||||
ch.pipeline().addLast(Capability.SHH, channel.getShhHandler());
|
||||
|
|
|
@ -178,7 +178,7 @@ public class BlockTest {
|
|||
|
||||
|
||||
@Test
|
||||
public void testScenario5() throws URISyntaxException, IOException {
|
||||
public void testScenario1() throws URISyntaxException, IOException {
|
||||
|
||||
BlockchainImpl blockchain = (BlockchainImpl)worldManager.getBlockchain();
|
||||
|
||||
|
@ -199,7 +199,7 @@ public class BlockTest {
|
|||
|
||||
logger.info("asserting root state is: {}", Hex.toHexString( root ));
|
||||
|
||||
//expected root: dedd258f4cee2d1b45f137a2a74a2052e14a6d7fe1b1184be0a6adcec6a1d1d3
|
||||
//expected root: fb8be59e6420892916e3967c60adfdf48836af040db0072ca411d7aaf5663804
|
||||
assertEquals(Hex.toHexString(root),
|
||||
Hex.toHexString(worldManager.getRepository().getRoot()));
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ package test.ethereum.db;
|
|||
|
||||
import org.ethereum.config.SystemProperties;
|
||||
import org.ethereum.core.Genesis;
|
||||
import org.ethereum.crypto.HashUtil;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.db.RepositoryImpl;
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.junit.Assert;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
@ -493,67 +495,26 @@ public class RepositoryTest {
|
|||
Repository repository = new RepositoryImpl();
|
||||
|
||||
byte[] cow = Hex.decode("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826");
|
||||
byte[] horse = Hex.decode("13978AEE95F38490E9769C39B2773ED763D9CD5F");
|
||||
final BigInteger ELEVEN = BigInteger.TEN.add(BigInteger.ONE);
|
||||
|
||||
byte[] cowKey1 = "key-c-1".getBytes();
|
||||
byte[] cowValue1 = "val-c-1".getBytes();
|
||||
|
||||
byte[] horseKey1 = "key-h-1".getBytes();
|
||||
byte[] horseValue1 = "val-h-1".getBytes();
|
||||
|
||||
byte[] cowKey2 = "key-c-2".getBytes();
|
||||
byte[] cowValue2 = "val-c-2".getBytes();
|
||||
|
||||
byte[] horseKey2 = "key-h-2".getBytes();
|
||||
byte[] horseValue2 = "val-h-2".getBytes();
|
||||
|
||||
// changes level_1
|
||||
Repository track1 = repository.startTracking();
|
||||
track1.addStorageRow(cow, new DataWord(cowKey1), new DataWord(cowValue1));
|
||||
track1.addStorageRow(horse, new DataWord(horseKey1), new DataWord(horseValue1));
|
||||
|
||||
assertEquals(new DataWord(cowValue1), track1.getStorageValue(cow, new DataWord(cowKey1)));
|
||||
assertEquals(new DataWord(horseValue1), track1.getStorageValue(horse, new DataWord(horseKey1)));
|
||||
|
||||
// changes level_2
|
||||
Repository track2 = track1.startTracking();
|
||||
track2.addStorageRow(cow, new DataWord(cowKey2), new DataWord(cowValue2));
|
||||
track2.addStorageRow(horse, new DataWord(horseKey2), new DataWord(horseValue2));
|
||||
|
||||
track2.addStorageRow(cow, new DataWord(cowKey1), new DataWord(cowValue1));
|
||||
assertEquals(new DataWord(cowValue1), track2.getStorageValue(cow, new DataWord(cowKey1)));
|
||||
assertEquals(new DataWord(horseValue1), track2.getStorageValue(horse, new DataWord(horseKey1)));
|
||||
|
||||
assertEquals(new DataWord(cowValue2), track2.getStorageValue(cow, new DataWord(cowKey2)));
|
||||
assertEquals(new DataWord(horseValue2), track2.getStorageValue(horse, new DataWord(horseKey2)));
|
||||
|
||||
track2.rollback();
|
||||
// leaving level_2
|
||||
|
||||
assertEquals(new DataWord(cowValue1), track1.getStorageValue(cow, new DataWord(cowKey1)));
|
||||
assertEquals(new DataWord(horseValue1), track1.getStorageValue(horse, new DataWord(horseKey1)));
|
||||
|
||||
assertNull(track1.getStorageValue(cow, new DataWord(cowKey2)));
|
||||
assertNull(track1.getStorageValue(horse, new DataWord(horseKey2)));
|
||||
|
||||
|
||||
track1.commit();
|
||||
// leaving level_1
|
||||
|
||||
assertEquals(new DataWord(cowValue1), repository.getStorageValue(cow, new DataWord(cowKey1)));
|
||||
assertEquals(new DataWord(horseValue1), repository.getStorageValue(horse, new DataWord(horseKey1)));
|
||||
|
||||
assertNull(repository.getStorageValue(cow, new DataWord(cowKey2)));
|
||||
assertNull(repository.getStorageValue(horse, new DataWord(horseKey2)));
|
||||
|
||||
|
||||
Assert.assertEquals( Hex.toHexString(HashUtil.EMPTY_TRIE_HASH), Hex.toHexString(repository.getRoot()) );
|
||||
repository.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test18(){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
f90139f90134a0955f36d073ccb026b78ab3424c15cf966a7563aa270413859f78702b9e8e22cba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347949d6327e4f16d48a3e4b658503d9e3256293fce75a045c8c6675b11d66a33e351d2f104830727c909b58061cf992e85d622e33962e5a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b840000000000000000000000000000080000000000000100000000000000000400000000000000000000000000000000000000000000000000000000000000000008301ff80018609184e72a000830f3e6f80845466342180a0b8605c26e6d37aac4986bd952f36b6f595e96d76fec99eee832af740e454f42ac0c0
|
||||
f90139f90134a0dc24d5c43ad451400404f9255b7f63bdceeb11310a2b9ece302626cda9b41ff7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347949d6327e4f16d48a3e4b658503d9e3256293fce75a05c2fcc83b43e09eee6801174a2db881187fbbdaf51e201350ec5009f8231e389a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b840000000000000000000000000000080000000000000100000000000000000400000000000000000000000000000000000000000000000000000000000000000008301ffff028609184e72a000830f3a9f80845466342180a090649c06424d4ba8462bcb50b4dcd8507931366e8a5f8cd61019ae590872523bc0c0
|
||||
f9020af90136a007bc19da9a5ecc987f579f40d1c450bd1d9938983929dce31d0a0734f76798a5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347949d6327e4f16d48a3e4b658503d9e3256293fce75a013a5e615365c86438d98df5a2ca5bf1173ab4ea33be808fde7b94e47e9534549a00ddba1aad325f4f6d24e0b50ff6ceb3282650ccedc7afacbaa1256e18e014a55a072170d776433bd79dbbb1793f3cb1105ac369da0b142103639df7696e9718016b840000000000000000000000000000080000000000000100000000000000000400000000000000000000000000000000000000000000000000000000000000000008301ff80038609184e72a000830f36d08203e8845466342a80a086862224f20522063b12624e1c1158fc936bd549ca632e780d08471806bed945f8cef865808609184e72a0008203e894885f93eed577f2fc341ebb9a5c9b2ce4465d96c40c801ca01377aa9363574f78d328844287690b4ba7b96dbc9d2a12c67fc60c821093b3a3a06385f6507e8314e45c6b79536ffd164bc8f87d24c63a832c41ce444492101887f865018609184e72a0008203e894885f93eed577f2fc341ebb9a5c9b2ce4465d96c40c801ca062f64877aa71aa94bdddfa9c12be8aab35278f8856e1940aa2a55caf431152fea0101275c02e8b76c28f6cf90ed3a2dde0160eaba021a00cbfd02d1496b7d274fbc0
|
||||
f90272f90136a0317604546eb502cf91b3d142301de66d46385fa3e89d6994bf8ed04e32a7ae55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347949d6327e4f16d48a3e4b658503d9e3256293fce75a08bbff862199ccf5411c9505598eeba3d76b51e4d391ac1189903b0fcbdd3733ba0ae62cdf41908bd8593c9065b34653bdbebeeb5bd372e4f82b8afb9178dd0c843a0ee823bbd7016023e00804ae726cfe8c611e7ceeeb64644a96aaa77e4c868f3c4b840000000000000000000000000000080000000000000100000000000000000400000000000000000000000000000000000000000000000000000000000000000008301ff01048609184e72a000830f33038205dc845466346080a0a5f179dd5b8af29bd95c139af1a57815548669b6cd470485bea03088612c4c1cf90135f865028609184e72a0008203e894885f93eed577f2fc341ebb9a5c9b2ce4465d96c40c801ba02891f3a500f4639906fdebb99cfad59c8f3fda9ed9d4ac13c40f01bedf38cfeea03ea2c3ce1b5346fe803c2e00309cc324979da38fac6893f182fc2a70d6d354d0f865038609184e72a0008203e894885f93eed577f2fc341ebb9a5c9b2ce4465d96c40c801ba0e0b83b97cad39259c8d9c73125036b794025bf129b9718f16e2c95892c84f3b3a02cf5bfd58c73043eb6b8d23d0a8d762e5a33e68d964a0b078efb36c7fbb78ebff865048609184e72a0008203e894885f93eed577f2fc341ebb9a5c9b2ce4465d96c40c801ca0c2586f90246d16d1d19c5b2ecc73c64f7d2028367122ec4c4d38e37850bd22aca045021f7342e69f07e4d6f28ad0075105596e6f4b38e3c1d3905d7d16162fc9f8c0
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue