This is an automated email from the ASF dual-hosted git repository. toulmean pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
The following commit(s) were added to refs/heads/main by this push: new 43ab630 Add genesis lib new 4f687c0 Merge pull request #372 from atoulme/add_genesis 43ab630 is described below commit 43ab630c0c52721e9f94cb30d6c7a84bfe282baf Author: Antoine Toulme <anto...@lunar-ocean.com> AuthorDate: Wed Mar 2 00:25:13 2022 -0800 Add genesis lib --- .../main/java/org/apache/tuweni/bytes/Bytes.java | 13 ++ .../main/java/org/apache/tuweni/eth/Address.java | 5 + .../java/org/apache/tuweni/eth/EthJsonModule.java | 13 ++ genesis/build.gradle | 31 +++++ .../apache/tuweni/genesis/AllocationGenerator.kt | 35 +++++ .../kotlin/org/apache/tuweni/genesis/Genesis.kt | 67 +++++++++ .../main/kotlin/org/apache/tuweni/genesis/Main.kt | 42 ++++++ .../kotlin/org/apache/tuweni/genesis/Quorum.kt | 155 +++++++++++++++++++++ .../org/apache/tuweni/genesis/GenesisTest.kt | 52 +++++++ .../kotlin/org/apache/tuweni/genesis/QuorumTest.kt | 58 ++++++++ settings.gradle | 1 + 11 files changed, 472 insertions(+) diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java index 596812f..2d5faf3 100644 --- a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java +++ b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes.java @@ -592,6 +592,19 @@ public interface Bytes extends Comparable<Bytes> { } /** + * Generate a bytes object filled with the same byte. + * + * @param b the byte to fill the Bytes with + * @param size the size of the object + * @return a value filled with a fixed byte + */ + static Bytes repeat(byte b, int size) { + byte[] buffer = new byte[size]; + Arrays.fill(buffer, b); + return Bytes.wrap(buffer); + } + + /** * * Provides the number of bytes this value represents. * diff --git a/eth/src/main/java/org/apache/tuweni/eth/Address.java b/eth/src/main/java/org/apache/tuweni/eth/Address.java index b7d426c..9dce379 100644 --- a/eth/src/main/java/org/apache/tuweni/eth/Address.java +++ b/eth/src/main/java/org/apache/tuweni/eth/Address.java @@ -25,6 +25,11 @@ import org.apache.tuweni.crypto.SECP256K1; public final class Address extends DelegatingBytes { /** + * Burn address. + */ + public static final Address ZERO = Address.fromBytes(Bytes.repeat((byte) 0, 20)); + + /** * Transform a public key into an Ethereum address. * * @param publicKey the public key diff --git a/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java b/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java index f3e8719..66ea82d 100644 --- a/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java +++ b/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java @@ -56,6 +56,18 @@ public class EthJsonModule extends SimpleModule { } } + static class AddressKeySerializer extends StdSerializer<Address> { + + protected AddressKeySerializer() { + super(Address.class); + } + + @Override + public void serialize(Address value, JsonGenerator g, SerializerProvider provider) throws IOException { + g.writeFieldName(value.toHexString()); + } + } + static class BytesSerializer extends StdSerializer<Bytes> { BytesSerializer() { @@ -204,6 +216,7 @@ public class EthJsonModule extends SimpleModule { public EthJsonModule() { addSerializer(Hash.class, new HashSerializer()); addSerializer(Address.class, new AddressSerializer()); + addKeySerializer(Address.class, new AddressKeySerializer()); addSerializer(Bytes.class, new BytesSerializer()); addSerializer(Gas.class, new GasSerializer()); addSerializer(UInt256.class, new UInt256Serializer()); diff --git a/genesis/build.gradle b/genesis/build.gradle new file mode 100644 index 0000000..055ca26 --- /dev/null +++ b/genesis/build.gradle @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You 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. + */ +description = 'Create genesis blocks and related artifacts' + +dependencies { + implementation project(':bytes') + implementation project(':crypto') + implementation project(':eth') + implementation project(':rlp') + implementation project(':units') + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'org.bouncycastle:bcprov-jdk15on' + + + testImplementation project(':junit') + testImplementation 'org.bouncycastle:bcprov-jdk15on' + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-params' + + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' +} diff --git a/genesis/src/main/kotlin/org/apache/tuweni/genesis/AllocationGenerator.kt b/genesis/src/main/kotlin/org/apache/tuweni/genesis/AllocationGenerator.kt new file mode 100644 index 0000000..0798b8b --- /dev/null +++ b/genesis/src/main/kotlin/org/apache/tuweni/genesis/AllocationGenerator.kt @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tuweni.genesis + +import org.apache.tuweni.crypto.SECP256K1 +import org.apache.tuweni.eth.Address +import org.apache.tuweni.units.bigints.UInt256 + +data class Allocation(val address: Address, val amount: UInt256, val keyPair: SECP256K1.KeyPair) + +class AllocationGenerator { + + fun createAllocations(numberAllocations: Int, amount: UInt256): List<Allocation> { + val allocs = mutableListOf<Allocation>() + for (i in 0..numberAllocations) { + val keyPair = SECP256K1.KeyPair.random() + allocs.add(Allocation(Address.fromPublicKey(keyPair.publicKey()), amount, keyPair)) + } + return allocs + } +} diff --git a/genesis/src/main/kotlin/org/apache/tuweni/genesis/Genesis.kt b/genesis/src/main/kotlin/org/apache/tuweni/genesis/Genesis.kt new file mode 100644 index 0000000..75eaa51 --- /dev/null +++ b/genesis/src/main/kotlin/org/apache/tuweni/genesis/Genesis.kt @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tuweni.genesis + +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import org.apache.tuweni.bytes.Bytes +import org.apache.tuweni.bytes.Bytes32 +import org.apache.tuweni.eth.Address +import org.apache.tuweni.units.bigints.UInt256 + +@JsonPropertyOrder(alphabetic = true) +open class GenesisConfig( + val chainId: Int, + val homesteadBlock: Int = 0, + val eip150Block: Int = 0, + val eip155Block: Int = 0, + val eip158Block: Int = 0, + val byzantiumBlock: Int = 0, + val constantinopleBlock: Int = 0, +) + +/** + * Genesis block representation + * + * The block contains the information about the chain, and the initial state of the chain, such as account balances. + */ +@JsonPropertyOrder("config", "nonce", "timestamp", "extraData", "gasLimit", "difficulty", "number", "gasUsed", "parentHash", "mixHash", "coinbase", "alloc") +class Genesis( + val nonce: Bytes, + val difficulty: UInt256, + val mixHash: Bytes32, + val coinbase: Address, + private val timestamp: Long, + val extraData: Bytes, + private val gasLimit: Long, + val parentHash: Bytes32, + val alloc: Map<Address, UInt256>, + val config: GenesisConfig, +) { + + fun getTimestamp(): String { + if (timestamp == 0L) { + return "0x0" + } + return Bytes.ofUnsignedLong(timestamp).toHexString() + } + + fun getGasLimit(): String { + return Bytes.ofUnsignedLong(gasLimit).toHexString() + } + + fun getNumber(): String = "0x0" +} diff --git a/genesis/src/main/kotlin/org/apache/tuweni/genesis/Main.kt b/genesis/src/main/kotlin/org/apache/tuweni/genesis/Main.kt new file mode 100644 index 0000000..2dcd823 --- /dev/null +++ b/genesis/src/main/kotlin/org/apache/tuweni/genesis/Main.kt @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tuweni.genesis + +import com.fasterxml.jackson.databind.json.JsonMapper +import org.apache.tuweni.bytes.Bytes32 +import org.apache.tuweni.eth.EthJsonModule +import org.apache.tuweni.units.bigints.UInt256 +import org.bouncycastle.jce.provider.BouncyCastleProvider +import java.nio.file.Files +import java.nio.file.Paths +import java.security.Security + +fun main(args: Array<String>) { + Security.addProvider(BouncyCastleProvider()) + val config = QuorumConfig.generate( + mixHash = Bytes32.random(), config = QuorumGenesisConfig(chainId = args[0].toInt()), + numberValidators = 10, + numberAllocations = 10, + amount = UInt256.valueOf(3000) + ) + + val mapper = JsonMapper() + mapper.registerModule(EthJsonModule()) + val contents = mapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(config.genesis) + Files.write(Paths.get("genesis.json"), contents) + Files.write(Paths.get("accounts.csv"), config.allocsToCsv().toByteArray()) +} diff --git a/genesis/src/main/kotlin/org/apache/tuweni/genesis/Quorum.kt b/genesis/src/main/kotlin/org/apache/tuweni/genesis/Quorum.kt new file mode 100644 index 0000000..70f3f88 --- /dev/null +++ b/genesis/src/main/kotlin/org/apache/tuweni/genesis/Quorum.kt @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tuweni.genesis + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import org.apache.tuweni.bytes.Bytes +import org.apache.tuweni.bytes.Bytes32 +import org.apache.tuweni.crypto.SECP256K1 +import org.apache.tuweni.eth.Address +import org.apache.tuweni.rlp.RLP +import org.apache.tuweni.units.bigints.UInt256 + +class QuorumConfig(val genesis: Genesis, val validators: List<SECP256K1.KeyPair>, val allocations: List<Allocation>) { + + companion object { + val header = "User,Public key,Address,Secret key\n" + + fun generate( + nonce: Bytes = Bytes.ofUnsignedLong(0), + difficulty: UInt256 = UInt256.ONE.shiftLeft(252), + mixHash: Bytes32, + coinbase: Address = Address.ZERO, + timestamp: Long = 0, + gasLimit: Long = 0, + parentHash: Bytes32 = Bytes32.ZERO, + vanity: Bytes32 = Bytes32.ZERO, + config: QuorumGenesisConfig, + numberValidators: Int, + numberAllocations: Int, + amount: UInt256, + ): QuorumConfig { + val allocations = AllocationGenerator().createAllocations(numberAllocations, amount) + + val validators = (0..numberValidators).map { + SECP256K1.KeyPair.random() + } + + return generate( + nonce = nonce, + difficulty = difficulty, + mixHash = mixHash, + coinbase = coinbase, + timestamp = timestamp, + gasLimit = gasLimit, + parentHash = parentHash, + config = config, + vanity = vanity, + allocations = allocations, + validators = validators + ) + } + + fun generate( + nonce: Bytes = Bytes.ofUnsignedLong(0), + difficulty: UInt256 = UInt256.ONE.shiftLeft(252), + coinbase: Address = Address.ZERO, + timestamp: Long = 0, + gasLimit: Long = 0, + parentHash: Bytes32 = Bytes32.ZERO, + config: QuorumGenesisConfig, + vanity: Bytes32 = Bytes32.ZERO, + mixHash: Bytes32, + allocations: List<Allocation>, + validators: List<SECP256K1.KeyPair>, + ): QuorumConfig { + val allocs = mutableMapOf<Address, UInt256>() + for (alloc in allocations) { + allocs[alloc.address] = alloc.amount + } + val genesis = Genesis( + nonce = nonce, + difficulty = difficulty, + mixHash = mixHash, + coinbase = coinbase, + timestamp = timestamp, + gasLimit = gasLimit, + parentHash = parentHash, + alloc = allocs, + extraData = QBFTGenesisExtraData(vanity, validators).toBytes(), + config = config + ) + + return QuorumConfig(genesis, validators, allocations) + } + } + + fun allocsToCsv(): String { + val lines = allocations.map { + "Unclaimed,${ + it.keyPair.publicKey().toHexString() + },${it.address.toHexString()},${it.keyPair.secretKey().bytes()}" + } + + return header + lines.joinToString("\n") + } +} + +data class QBFTGenesisExtraData(val vanity: Bytes32, val validators: List<SECP256K1.KeyPair>) { + + /** + * A RLP serialization of the extradata for QBFT: + * RLP([32 bytes Vanity, List<Validators>, No Vote, Round=Int(0), 0 Seals]). + */ + fun toBytes(): Bytes = RLP.encodeList { + it.writeValue(vanity) + it.writeList { valWriter -> + for (validator in validators) { + valWriter.writeValue(Address.fromPublicKey(validator.publicKey())) + } + } + it.writeList {} + it.writeString("") + it.writeList {} + } +} + +class IstanbulConfigOptions( + val epoch: Int = 3000, + val policy: Int = 0, + val testQBFTBlock: Int = 0, + val ceil2Nby3Block: Int = 0, +) + +@JsonPropertyOrder(alphabetic = true) +class QuorumGenesisConfig( + chainId: Int, + homesteadBlock: Int = 0, + eip150Block: Int = 0, + eip155Block: Int = 0, + eip158Block: Int = 0, + byzantiumBlock: Int = 0, + constantinopleBlock: Int = 0, + val istanbul: IstanbulConfigOptions = IstanbulConfigOptions(), + val txnSizeLimit: Int = 64, + val maxCodeSize: Int = 0, +) : GenesisConfig(chainId, homesteadBlock, eip150Block, eip155Block, eip158Block, byzantiumBlock, constantinopleBlock) { + + @JsonProperty("isQuorum") + fun isQuorum(): Boolean = true +} diff --git a/genesis/src/test/kotlin/org/apache/tuweni/genesis/GenesisTest.kt b/genesis/src/test/kotlin/org/apache/tuweni/genesis/GenesisTest.kt new file mode 100644 index 0000000..c23c961 --- /dev/null +++ b/genesis/src/test/kotlin/org/apache/tuweni/genesis/GenesisTest.kt @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tuweni.genesis + +import com.fasterxml.jackson.databind.json.JsonMapper +import org.apache.tuweni.bytes.Bytes +import org.apache.tuweni.bytes.Bytes32 +import org.apache.tuweni.eth.Address +import org.apache.tuweni.eth.EthJsonModule +import org.apache.tuweni.eth.genesis.GenesisFile +import org.apache.tuweni.junit.BouncyCastleExtension +import org.apache.tuweni.units.bigints.UInt256 +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(BouncyCastleExtension::class) +class GenesisTest { + + @Test + fun testMinimalJson() { + val genesis = Genesis( + nonce = Bytes.fromHexString("0xdeadbeef"), + difficulty = UInt256.ONE, + mixHash = Bytes32.leftPad(Bytes.fromHexString("0xf000")), + coinbase = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + timestamp = 0L, + extraData = Bytes.EMPTY, + gasLimit = 0L, + parentHash = Bytes32.leftPad(Bytes.fromHexString("0x00ff")), + alloc = mapOf(Pair(Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), UInt256.ONE)), + config = GenesisConfig(chainId = 1337) + ) + val mapper = JsonMapper() + mapper.registerModule(EthJsonModule()) + val contents = mapper.writeValueAsBytes(genesis) + GenesisFile.read(contents) + } +} diff --git a/genesis/src/test/kotlin/org/apache/tuweni/genesis/QuorumTest.kt b/genesis/src/test/kotlin/org/apache/tuweni/genesis/QuorumTest.kt new file mode 100644 index 0000000..05b8daa --- /dev/null +++ b/genesis/src/test/kotlin/org/apache/tuweni/genesis/QuorumTest.kt @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tuweni.genesis + +import com.fasterxml.jackson.databind.json.JsonMapper +import org.apache.tuweni.bytes.Bytes +import org.apache.tuweni.bytes.Bytes32 +import org.apache.tuweni.eth.Address +import org.apache.tuweni.eth.EthJsonModule +import org.apache.tuweni.eth.genesis.GenesisFile +import org.apache.tuweni.junit.BouncyCastleExtension +import org.apache.tuweni.units.bigints.UInt256 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(BouncyCastleExtension::class) +class QuorumTest { + + @Test + fun testGenerateQuorumGenesis() { + val quorum = QuorumConfig.generate( + nonce = Bytes.fromHexString("0xdeadbeef"), + difficulty = UInt256.ONE.shiftLeft(252), + mixHash = Bytes32.leftPad(Bytes.fromHexString("0xf000")), + coinbase = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + timestamp = 0L, + gasLimit = 0L, + parentHash = Bytes32.leftPad(Bytes.fromHexString("0x00ff")), + config = QuorumGenesisConfig(chainId = 1337,), + numberAllocations = 10, + amount = UInt256.valueOf(123), + numberValidators = 4, + vanity = Bytes32.leftPad(Bytes.fromHexString("0xdeadbeef")) + ) + val genesis = quorum.genesis + assertEquals(12, quorum.allocsToCsv().split("\n").size) + assertEquals("User,Public key,Address,Secret key", quorum.allocsToCsv().split("\n")[0]) + val mapper = JsonMapper() + mapper.registerModule(EthJsonModule()) + val contents = mapper.writeValueAsBytes(genesis) + GenesisFile.read(contents) + } +} diff --git a/settings.gradle b/settings.gradle index 3949e41..b798cc2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ include 'eth-repository' include 'ethstats' include 'evm' include 'eth-faucet' +include 'genesis' include 'gossip' include 'hobbits' include 'hobbits-relayer' --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@tuweni.apache.org For additional commands, e-mail: commits-h...@tuweni.apache.org