Merge pull request #5 from nicksavers/master

Update for new Genesis and cleanup
This commit is contained in:
romanman 2014-05-20 21:39:10 +03:00
commit 7e467c6afb
9 changed files with 59 additions and 248 deletions

View File

@ -5,10 +5,13 @@ import java.math.BigInteger;
public enum Denomination {
WEI(BigInteger.ONE),
SZABO(BigDecimal.valueOf(Math.pow(10, 12)).toBigInteger()),
FINNY(BigDecimal.valueOf(Math.pow(10, 15)).toBigInteger()),
ETHER(BigDecimal.valueOf(Math.pow(10, 18)).toBigInteger());
WEI(newBigInt(0)),
ADA(newBigInt(3)),
BABBAGE(newBigInt(6)),
SHANNON(newBigInt(9)),
SZABO(newBigInt(12)),
FINNY(newBigInt(15)),
ETHER(newBigInt(18));
private BigInteger amount;
@ -19,4 +22,8 @@ public enum Denomination {
public BigInteger getDenomination() {
return amount;
}
private static BigInteger newBigInt(double value) {
return BigDecimal.valueOf(Math.pow(10, value)).toBigInteger();
}
}

View File

@ -16,8 +16,8 @@ public class Genesis extends Block {
private static byte[] unclesHash = sha3EmptyList;
private static byte[] coinbase = zeroHash160;
private static byte[] stateRoot = // TODO: Get stateRoot from actual state
Hex.decode("2f4399b08efe68945c1cf90ffe85bbe3ce978959da753f9e649f034015b8817d");
private static byte[] txTrieRoot = zeroHash256;
Hex.decode("12582945fc5ad12c3e7b67c4fc37a68fc0d52d995bb7f7291ff41a2739a7ca16");
private static byte[] txTrieRoot = new byte[0];
private static byte[] difficulty = BigInteger.valueOf((long) Math.pow(2, 22)).toByteArray();
private static long number = 0;
private static long minGasPrice = 0;

View File

@ -1,7 +1,5 @@
package org.ethereum.core;
import java.math.BigInteger;
import org.ethereum.crypto.ECKey.ECDSASignature;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
@ -10,9 +8,8 @@ import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.util.Utils;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;
import edu.emory.mathcs.backport.java.util.Arrays;
import java.util.Arrays;
/**
* A transaction (formally, T ) is a single cryptographically
@ -24,11 +21,9 @@ import edu.emory.mathcs.backport.java.util.Arrays;
*/
public class Transaction {
private byte[] rlpEncoded;
private byte[] unsignedRLPEncoded;
private boolean parsed = false;
private static final int CALL_SIZE = 9;
private static final int CONTRACT_SIZE = 10;
/* creation contract tx
* [ nonce, endowment, 0, gasPrice, gasDeposit (for init), body, init, signature(v, r, s) ]
* or simple send tx
@ -68,6 +63,13 @@ public class Transaction {
/* the elliptic curve signature
* (including public key recovery bits) */
private ECDSASignature signature;
/* Tx in encoded form */
private byte[] rlpEncoded;
private byte[] unsignedRLPEncoded;
/* Indicates if this transaction has been parsed
* from the rlp-encoded data */
private boolean parsed = false;
public Transaction(byte[] rawData) {
this.rlpEncoded = rawData;
@ -95,29 +97,19 @@ public class Transaction {
this.hash = HashUtil.sha3(rlpEncoded);
/* Temporary order for an RLP encoded transaction in cpp client */
this.nonce = ((RLPItem) transaction.get(0)).getRLPData();
this.gasPrice = ((RLPItem) transaction.get(1)).getRLPData();
this.gasLimit = ((RLPItem) transaction.get(2)).getRLPData();
this.receiveAddress = ((RLPItem) transaction.get(3)).getRLPData();
this.value = ((RLPItem) transaction.get(4)).getRLPData();
this.data = ((RLPItem) transaction.get(5)).getRLPData();
/* Order of the Yellow Paper / eth-go & pyethereum clients
this.nonce = ((RLPItem) transaction.get(0)).getRLPData();
this.value = ((RLPItem) transaction.get(1)).getRLPData();
this.receiveAddress = ((RLPItem) transaction.get(2)).getRLPData();
this.gasPrice = ((RLPItem) transaction.get(3)).getRLPData();
this.gasLimit = ((RLPItem) transaction.get(4)).getRLPData();
this.data = ((RLPItem) transaction.get(5)).getRLPData();
*/
if (transaction.size() == 9){ // Simple transaction
if (transaction.size() == CALL_SIZE){ // Simple transaction
byte v = ((RLPItem) transaction.get(6)).getRLPData()[0];
byte[] r = ((RLPItem) transaction.get(7)).getRLPData();
byte[] s = ((RLPItem) transaction.get(8)).getRLPData();
this.signature = ECDSASignature.fromComponents(r, s, v);
} else if (transaction.size() == 10){ // Contract creation transaction
} else if (transaction.size() == CONTRACT_SIZE){ // Contract creation transaction
this.init = ((RLPItem) transaction.get(6)).getRLPData();
byte v = ((RLPItem) transaction.get(7)).getRLPData()[0];
byte[] r = ((RLPItem) transaction.get(8)).getRLPData();
@ -224,7 +216,7 @@ public class Transaction {
", signatureS=" + Utils.toHexString(signature.s.toByteArray()) +
']';
}
/**
* For signature games you have to keep also
* rlp of the transaction without any signature data
@ -256,7 +248,6 @@ public class Transaction {
if(rlpEncoded != null) return rlpEncoded;
/* Temporary order for an RLP encoded transaction in cpp client */
byte[] nonce = RLP.encodeElement(this.nonce);
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
@ -264,14 +255,9 @@ public class Transaction {
byte[] value = RLP.encodeElement(this.value);
byte[] data = RLP.encodeElement(this.data);
byte[] v = null;
byte[] r = null;
byte[] s = null;
v = RLP.encodeByte( signature.v );
r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r));
s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s));
byte[] v = RLP.encodeByte( signature.v );
byte[] r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r));
byte[] s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s));
if(Arrays.equals(this.receiveAddress, new byte[0])) {
byte[] init = RLP.encodeElement(this.init);
@ -281,20 +267,6 @@ public class Transaction {
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress,
data, v, r, s);
}
/* Order of the Yellow Paper / eth-go & pyethereum clients
byte[] nonce = RLP.encodeElement(this.nonce);
byte[] value = RLP.encodeElement(this.value);
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
byte[] data = RLP.encodeElement(this.data);
byte[] init = RLP.encodeElement(this.init);
*/
return rlpEncoded;
}
}

View File

@ -70,10 +70,9 @@ public class ConnectionConsoleWindow extends JFrame implements PeerListener{
public void run() {
// new ClientPeer(thisConsole).connect("54.201.28.117", 30303); // peer discovery
// new ClientPeer(thisConsole).connect("82.217.72.169", 30303); // Nick
// new ClientPeer(thisConsole).connect("54.204.10.41", 30303);
new ClientPeer(thisConsole).connect("54.211.14.10", 30303);
new ClientPeer(thisConsole).connect("82.217.72.169", 30303); // Nick
// new ClientPeer(thisConsole).connect("54.204.10.41", 30303); // CPP: ZeroGox Poc5
// new ClientPeer(thisConsole).connect("54.211.14.10", 30303); // CPP: ver16
// new ClientPeer(thisConsole).connect("192.168.1.102", 30303);
}
};

View File

@ -1,8 +1,14 @@
package org.ethereum.manager;
import com.maxmind.geoip.Location;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.ethereum.core.Block;
import org.ethereum.core.Genesis;
import org.ethereum.core.Transaction;
import org.ethereum.core.Wallet;
import org.ethereum.crypto.HashUtil;
@ -12,7 +18,7 @@ import org.ethereum.net.client.PeerData;
import org.ethereum.net.message.StaticMessages;
import org.spongycastle.util.encoders.Hex;
import java.util.*;
import com.maxmind.geoip.Location;
/**
* www.ethereumJ.com
@ -21,8 +27,6 @@ import java.util.*;
*/
public class MainData {
private Set<PeerData> peers = Collections.synchronizedSet(new HashSet<PeerData>());
private List<Block> blockChainDB = new ArrayList<Block>();
private Wallet wallet = new Wallet();
@ -57,7 +61,7 @@ public class MainData {
// if it is the first block to add
// check that the parent is the genesis
if (blockChainDB.isEmpty() &&
!Arrays.equals(StaticMessages.GENESSIS_HASH, firstBlockToAdd.getParentHash())){
!Arrays.equals(StaticMessages.GENESIS_HASH, firstBlockToAdd.getParentHash())){
return;
}
@ -80,18 +84,15 @@ public class MainData {
System.out.println("*** Block chain size: [" + blockChainDB.size() + "]");
}
public byte[] getLatestBlockHash(){
if (blockChainDB.isEmpty())
return StaticMessages.GENESSIS_HASH;
return (new Genesis()).getHash();
else
return blockChainDB.get(blockChainDB.size() - 1).getHash();
}
public List<Block> getAllBlocks(){
return blockChainDB;
}
@ -99,7 +100,6 @@ public class MainData {
return wallet;
}
public void setActivePeer(ClientPeer peer){
this.activePeer = peer;
}

View File

@ -1,42 +0,0 @@
package org.ethereum.net;
import java.util.HashMap;
import java.util.Map;
public enum ReasonCode {
REASON_DISCONNECT_REQUESTED(0x00),
REASON_TCP_ERROR(0x01),
REASON_BAD_PROTOCOL(0x02),
REASON_USELESS_PEER(0x03),
REASON_TOO_MANY_PEERS(0x04),
REASON_ALREADY_CONNECTED(0x05),
REASON_WRONG_GENESIS(0x06),
REASON_INCOMPATIBLE_PROTOCOL(0x07),
REASON_PEER_QUITING(0x08),
UNKNOWN(0xFF);
private int reason;
private static final Map<Integer, ReasonCode> intToTypeMap = new HashMap<Integer, ReasonCode>();
static {
for (ReasonCode type : ReasonCode.values()) {
intToTypeMap.put(type.reason, type);
}
}
private ReasonCode(int reason) {
this.reason = reason;
}
public static ReasonCode fromInt(int i) {
ReasonCode type = intToTypeMap.get(Integer.valueOf(i));
if (type == null)
return ReasonCode.UNKNOWN;
return type;
}
public byte asByte() {
return (byte) reason;
}
}

View File

@ -14,7 +14,7 @@ public enum ReasonCode {
WRONG_GENESIS(0x06),
INCOMPATIBLE_PROTOCOL(0x07),
PEER_QUITING(0x08),
UNKNOWN(0xFF);
UNKNOWN(0xFF);
private int reason;
@ -25,10 +25,10 @@ public enum ReasonCode {
}
}
private ReasonCode(int cmd) {
this.reason = cmd;
private ReasonCode(int reason) {
this.reason = reason;
}
public static ReasonCode fromInt(int i) {
ReasonCode type = intToTypeMap.get(Integer.valueOf(i));
if (type == null)

View File

@ -1,7 +1,6 @@
package org.ethereum.net.message;
import org.ethereum.crypto.HashUtil;
import org.ethereum.util.Utils;
import org.spongycastle.util.encoders.Hex;
/**
@ -27,43 +26,18 @@ public class StaticMessages {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02,
(byte)0xC1, (byte)0x16 };
public static final byte[] DISCONNECT_00 = {(byte)0x22, (byte)0x40, (byte)0x08, (byte)0x91,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x03,
(byte)0xC2, (byte)0x01, (byte)0x00};
public static final byte[] DISCONNECT_01 = {(byte) 0x22, (byte) 0x40, (byte) 0x08, (byte) 0x91,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03,
(byte) 0xC2, (byte) 0x01, (byte) 0x01};
public static final byte[] DISCONNECT_02 = {(byte) 0x22, (byte) 0x40, (byte) 0x08, (byte) 0x91,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03,
(byte) 0xC2, (byte) 0x01, (byte) 0x02};
public static final byte[] DISCONNECT_03 = {(byte) 0x22, (byte) 0x40, (byte) 0x08, (byte) 0x91,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03,
(byte) 0xC2, (byte) 0x01, (byte) 0x03};
public static final byte[] DISCONNECT_08 = {(byte) 0x22, (byte) 0x40, (byte) 0x08, (byte) 0x91,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03,
(byte) 0xC2, (byte) 0x01, (byte) 0x08};
public static final byte[] GET_CHAIN = Hex.decode("2240089100000027F82514A069A7356A245F9DC5B865475ADA5EE4E89B18F93C06503A9DB3B3630E88E9FB4E820100");
public static final byte[] GENESSIS_HASH = Hex.decode("f5232afe32aba6b366f8aa86a6939437c5e13d1fd71a0f51e77735d3456eb1a6");
public static final byte[] GENESIS_HASH = Hex.decode("c305511e7cb9b33767e50f5e94ecd7b1c51359a04f45183860ec6808d80b0d3f");
public static final byte[] MAGIC_PACKET = Hex.decode("22400891");
static {
byte[] peerIdBytes = HashUtil.randomPeerId();
HELLO_MESSAGE = new HelloMessage((byte)0x10, (byte)0x00, "EthereumJ [v0.5.1] pure java by RomanJ",
HELLO_MESSAGE = new HelloMessage((byte)0x10, (byte)0x00, "EthereumJ [v0.5.1]",
(byte)0b00000111, (short)30303, peerIdBytes);
/*
HELLO_MESSAGE = new HelloMessage((byte)0x0B, (byte)0x00, "EthereumJ [v0.0.1] pure java [by Roman Mandeleil]",
(byte)0b00000111, (short)30303, peerIdBytes);
*/
}
public static final HelloMessage HELLO_MESSAGE;

View File

@ -1,6 +1,7 @@
package org.ethereum.core;
import org.ethereum.net.message.BlocksMessage;
import org.ethereum.net.message.StaticMessages;
import org.ethereum.util.RLPList;
import org.spongycastle.util.encoders.Hex;
import org.ethereum.core.Block;
@ -12,111 +13,15 @@ import org.junit.Test;
import static org.junit.Assert.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
public class BlockTest {
// https://ethereum.etherpad.mozilla.org/12
private String CPP_PoC5_GENESIS_STATE_ROOT_HEX_HASH = "2f4399b08efe68945c1cf90ffe85bbe3ce978959da753f9e649f034015b8817d";
private String CPP_PoC5_GENESIS_HEX_RLP_ENCODED = "f8cbf8c7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a02f4399b08efe68945c1cf90ffe85bbe3ce978959da753f9e649f034015b8817da00000000000000000000000000000000000000000000000000000000000000000834000008080830f4240808080a004994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829c0c0";
private String CPP_PoC5_GENESIS_HEX_HASH = "69a7356a245f9dc5b865475ada5ee4e89b18f93c06503a9db3b3630e88e9fb4e";
@Test /* Creating genesis hash not ready yet */
public void test1() throws IOException {
/*
def serialize(self):
txlist = [x.serialize() for x in self.transactions]
header = [encode_int(self.number),
self.prevhash,
sha3(rlp.encode(self.uncles)),
self.coinbase.decode('hex'),
self.state.root,
sha3(rlp.encode(txlist)),
encode_int(self.difficulty),
encode_int(self.timestamp),
self.extradata,
encode_int(self.nonce)]
return rlp.encode([header, txlist, self.uncles])
*/
/*
( 0(256) - parentHash
SHA3(RLP(emptyList)) - hashes of transactions
0(160) - coinbase
0(256) - stateRoot
SHA3(RLP(emptyList)) - hashes of uncles
2**22 - difficulty
0 - timestamp
() -
42 - nonce )
() - uncles
() - transactions
block.appendList(9) << h256() << sha3EmptyList << h160() << stateRoot << sha3EmptyList << c_genesisDifficulty << (uint)0 << string() << sha3(bytes(1, 42));
block.appendRaw(RLPEmptyList);
block.appendRaw(RLPEmptyList);
*/
/* 1 */ byte[] prevHash =
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
prevHash = RLP.encodeElement(prevHash);
/* 2 */ byte[] uncleList = RLP.encodeElement(HashUtil.sha3(RLP.encodeList(new byte[]{})));
/* 3 */ byte[] coinbase =
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
coinbase = RLP.encodeElement(coinbase);
/* 4 */ byte[] rootState = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
rootState = RLP.encodeElement(rootState);
/* 5 */ byte[] transactionsRoot = RLP.encodeElement(HashUtil.sha3(RLP.encodeList(new byte[]{})));
/* 6 */ BigInteger difficulty = new BigInteger("2");
difficulty = difficulty.pow(22);
byte[] diffBytes = RLP.encodeElement(difficulty.toByteArray());
/* 7 */ byte[] longTS = {0x00, 0x00, 0x00, 0x00};
longTS = RLP.encodeElement(longTS);
/* 8 */ byte[] extradata = {};
extradata = RLP.encodeElement(extradata);
/* 9 */ byte[] nonce = {42};
nonce = RLP.encodeElement(HashUtil.sha3(nonce));
byte[] header = RLP.encodeList( prevHash,
uncleList,
coinbase,
rootState,
transactionsRoot,
diffBytes,
longTS,
extradata,
nonce);
// block.appendList(9) << h256() << sha3EmptyList << h160() << stateRoot << sha3EmptyList << c_genesisDifficulty << (uint)0 << string() << sha3(bytes(1, 42));
byte[] txList = RLP.encodeList(new byte[]{});
byte[] unclesList = RLP.encodeList(new byte[]{});
byte[] genesis = RLP.encodeList(header, txList, unclesList);
System.out.println(Hex.toHexString(genesis));
byte[] hash = HashUtil.sha3(genesis);
assertEquals(CPP_PoC5_GENESIS_HEX_HASH, Hex.toHexString(hash));
}
private String CPP_PoC5_GENESIS_STATE_ROOT_HEX_HASH = "12582945fc5ad12c3e7b67c4fc37a68fc0d52d995bb7f7291ff41a2739a7ca16";
private String CPP_PoC5_GENESIS_HEX_RLP_ENCODED = "f8abf8a7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a012582945fc5ad12c3e7b67c4fc37a68fc0d52d995bb7f7291ff41a2739a7ca1680834000008080830f4240808080a004994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829c0c0";
private String CPP_PoC5_GENESIS_HEX_HASH = Hex.toHexString(StaticMessages.GENESIS_HASH);
@Test /* got from go guy */
public void testGenesisFromRLP(){
@ -124,6 +29,7 @@ public class BlockTest {
byte[] genesisBytes = Hex.decode(CPP_PoC5_GENESIS_HEX_RLP_ENCODED);
Block genesis = new Block(genesisBytes);
assertEquals(CPP_PoC5_GENESIS_HEX_HASH, Hex.toHexString(genesis.getHash()));
assertEquals(CPP_PoC5_GENESIS_STATE_ROOT_HEX_HASH, Hex.toHexString(genesis.getStateRoot()));
}
@Test
@ -147,6 +53,8 @@ public class BlockTest {
)
*/
Block genesis = new Genesis();
System.out.println(CPP_PoC5_GENESIS_HEX_RLP_ENCODED);
System.out.println(Hex.toHexString(genesis.getEncoded()));
assertEquals(CPP_PoC5_GENESIS_HEX_RLP_ENCODED, Hex.toHexString(genesis.getEncoded()));
assertEquals(CPP_PoC5_GENESIS_HEX_HASH, Hex.toHexString(genesis.getHash()));
}
@ -182,11 +90,4 @@ public class BlockTest {
BlocksMessage blockData = new BlocksMessage(rlpList);
System.out.println(blockData.toString());
}
}
/*
[[ab6b9a5613970faa771b12d449b2e9bb925ab7a369f0a4b86b286e9d540099cf, 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, 3854aaf203ba5f8d49b1ec221329c7aebcf050d3, 990dc3b5acbee04124361d958fe51acb582593613fc290683940a0769549d3ed, 9bfe4817d274ea3eb8672e9fe848c3885b53bbbd1d7c26e6039f90fb96b942b0, 3ff000, 533f16b7, null, 00000000000000000000000000000000000000000000000077377adff6c227db, ]
[
[null, null, 0000000000000000000000000000000000000000, 09184e72a000, 2710, 606956330c0d630000003359366000530a0d630000003359602060005301356000533557604060005301600054630000000c58, 33606957, 1c, 7f6eb94576346488c6253197bde6a7e59ddc36f2773672c849402aa9c402c3c4, 6d254e662bf7450dd8d835160cbb053463fed0b53f2cdd7f3ea8731919c8e8cc, ]
[01, null, 0000000000000000000000000000000000000000, 09184e72a000, 2710, 36630000002e59606956330c0d63000000155933ff33560d63000000275960003356576000335700630000005358600035560d630000003a590033560d63000000485960003356573360003557600035335700, 7f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d65526567000000000000000000000000000000000000000000000000005733606957, 1b, 4af15a0ec494aeac5b243c8a2690833faa74c0f73db1f439d521c49c381513e9, 5802e64939be5a1f9d4d614038fbd5479538c48795614ef9c551477ecbdb49d2, ]
[02, null, ccdeac59d35627b7de09332e819d5159e7bb7250, 09184e72a000, 2710, 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d0aceee7e5ab874e22ccf8d1a649f59106d74e8, 1b, d05887574456c6de8f7a0d172342c2cbdd4cf7afe15d9dbb8b75b748ba6791c9, 1e87172a861f6c37b5a9e3a5d0d7393152a7fbe41530e5bb8ac8f35433e5931b, ]]*/
}