From 34c602991dcbe7e49900f3c8d322a1cef7c33471 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Sat, 29 Jun 2019 16:33:21 +0800 Subject: [PATCH 001/124] Modify Contract Plugin's Check Function! --- .../com/jd/blockchain/CheckImportsMojo.java | 1 + .../com/jd/blockchain/ContractCheckMojo.java | 5 +- .../com/jd/blockchain/ContractVerifyMojo.java | 227 ++++++++++++++++++ .../ledger/ContractVerifyMojoTest.java | 29 +++ source/contract/contract-samples/pom.xml | 10 +- .../blockchain/contract/ComplexContract.java | 7 + .../contract/ComplexContractImpl.java | 12 + .../service/GatewayInterceptService.java | 8 + .../GatewayInterceptServiceHandler.java | 42 ++++ 9 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java create mode 100644 source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java create mode 100644 source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContract.java create mode 100644 source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContractImpl.java create mode 100644 source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptService.java create mode 100644 source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java index 461917a4..01e3be99 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; */ @Mojo(name = "checkImports") public class CheckImportsMojo extends AbstractMojo { + Logger logger = LoggerFactory.getLogger(CheckImportsMojo.class); @Parameter(defaultValue = "${project}", required = true, readonly = true) diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java index a47e4eb6..7d823d95 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java @@ -141,7 +141,7 @@ public class ContractCheckMojo extends AbstractMojo { pluginExecution.setId("make-assembly"); pluginExecution.setPhase("verify"); List goals = new ArrayList<>(); - goals.add("checkImports"); + goals.add("JDChain.Verify"); pluginExecution.setGoals(goals); List pluginExecutions = new ArrayList<>(); pluginExecutions.add(pluginExecution); @@ -215,7 +215,8 @@ public class ContractCheckMojo extends AbstractMojo { //将字节数组fileInput中的内容输出到文件fileOut.xml中; ConsoleUtils.info(new String(buffer)); fos.write(buffer); - invokeCompile(fileOutput); + fos.flush(); fos.close(); + invokeCompile(fileOutput); } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java new file mode 100644 index 00000000..7dd78300 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java @@ -0,0 +1,227 @@ +package com.jd.blockchain; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.PackageDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.jd.blockchain.contract.ContractType; +import com.jd.blockchain.utils.IllegalDataException; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; + +/** + * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. + * This is a try of "from Initail to Abandoned". + * Since we are good at the class, why not? + * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. + * + * by zhaogw + * date 2019-06-05 16:17 + */ +@Mojo(name = "JDChain.Verify") +public class ContractVerifyMojo extends AbstractMojo { + + Logger logger = LoggerFactory.getLogger(ContractVerifyMojo.class); + + private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + /** + * jar's name; + */ + @Parameter + private String finalName; + + @Override + public void execute() throws MojoFailureException { + + List sources; + try { + + File jarFile = copyAndManage(); + + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); + Properties properties = new Properties(); + properties.load(inputStream); + String[] packageBlackList = properties.getProperty("blacklist").split(","); + Path baseDirPath = project.getBasedir().toPath(); + sources = Files.find(baseDirPath, Integer.MAX_VALUE, (file, attrs) -> (file.toString().endsWith(".java"))).collect(Collectors.toList()); + for (Path path : sources) { + CompilationUnit compilationUnit = JavaParser.parse(path); + + compilationUnit.accept(new MethodVisitor(), null); + + NodeList imports = compilationUnit.getImports(); + for (ImportDeclaration imp : imports) { + String importName = imp.getName().asString(); + for (String item : packageBlackList) { + if (importName.startsWith(item)) { + throw new MojoFailureException("在源码中不允许包含此引入包:" + importName); + } + } + } + + //now we parse the jar; + URL jarURL = jarFile.toURI().toURL(); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL},this.getClass().getClassLoader()); + Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); + String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); + try { + Class mainClass = classLoader.loadClass(contractMainClass); + ContractType.resolve(mainClass); + } catch (ClassNotFoundException e) { + throw new IllegalDataException(e.getMessage()); + } + } + } catch (IOException exception) { + logger.error(exception.getMessage()); + throw new MojoFailureException("IO ERROR"); + } catch (NullPointerException e) { + logger.error(e.getMessage()); + } + } + + private File copyAndManage() throws IOException { + // 首先将Jar包转换为指定的格式 + String srcJarPath = project.getBuild().getDirectory() + + File.separator + finalName + ".jar"; + + String dstJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-temp-" + System.currentTimeMillis() + ".jar"; + + File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); + + // 首先进行Copy处理 + copy(srcJar, dstJar); + + byte[] txtBytes = jdChainTxt(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + + String finalJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-jdchain.jar"; + + File finalJar = new File(finalJarPath); + + copy(dstJar, finalJar, new JarEntry(JDCHAIN_META), txtBytes, null); + + // 删除临时文件 + FileUtils.forceDelete(dstJar); + + return finalJar; + // 删除srcJar + + // 删除finalJar +// FileUtils.forceDelete(finalJar); +// // 删除srcJar +// srcJar.deleteOnExit(); +// +// // 修改名字 +// finalJar.renameTo(srcJar); + } + + private void copy(File srcJar, File dstJar) throws IOException { + copy(srcJar, dstJar, null, null, null); + } + + private void copy(File srcJar, File dstJar, JarEntry addEntry, byte[] addBytes, String filter) throws IOException { + JarFile jarFile = new JarFile(srcJar); + Enumeration jarEntries = jarFile.entries(); + JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dstJar))); + + while(jarEntries.hasMoreElements()){ + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (filter != null && filter.equals(entryName)) { + continue; + } + System.out.println(entryName); + jarOut.putNextEntry(jarEntry); + jarOut.write(readStream(jarFile.getInputStream(jarEntry))); + jarOut.closeEntry(); + } + if (addEntry != null) { + jarOut.putNextEntry(addEntry); + jarOut.write(addBytes); + jarOut.closeEntry(); + } + + jarOut.flush(); + jarOut.finish(); + jarOut.close(); + jarFile.close(); + } + + private String jdChainTxt(byte[] content) { + // hash=Hex(hash(content)) + String hashTxt = "hash:" + DigestUtils.sha256Hex(content); + System.out.println(hashTxt); + return hashTxt; + } + + private byte[] readStream(InputStream inputStream) { + try (ByteArrayOutputStream outSteam = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outSteam.write(buffer, 0, len); + } + inputStream.close(); + return outSteam.toByteArray(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private class MethodVisitor extends VoidVisitorAdapter { + @Override + public void visit(MethodDeclaration n, Void arg) { + /* here you can access the attributes of the method. + this method will be called for all methods in this + CompilationUnit, including inner class methods */ + logger.info("method:"+n.getName()); + super.visit(n, arg); + } + + @Override + public void visit(ClassOrInterfaceDeclaration n, Void arg) { + logger.info("class:"+n.getName()+" extends:"+n.getExtendedTypes()+" implements:"+n.getImplementedTypes()); + + super.visit(n, arg); + } + + @Override + public void visit(PackageDeclaration n, Void arg) { + logger.info("package:"+n.getName()); + super.visit(n, arg); + } + + } +} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java new file mode 100644 index 00000000..4cfc66b5 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java @@ -0,0 +1,29 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.CheckImportsMojo; +import com.jd.blockchain.ContractVerifyMojo; +import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * @Author zhaogw + * @Date 2019/3/1 21:27 + */ +public class ContractVerifyMojoTest extends AbstractMojoTestCase { + Logger logger = LoggerFactory.getLogger(ContractVerifyMojoTest.class); + + @Test + public void test1() throws Exception { + File pom = getTestFile( "src/test/resources/project-to-test/pom.xml" ); + assertNotNull( pom ); + assertTrue( pom.exists() ); + + ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "JDChain.Verify", pom ); + assertNotNull( myMojo ); + myMojo.execute(); + } +} diff --git a/source/contract/contract-samples/pom.xml b/source/contract/contract-samples/pom.xml index ef3d3407..0a7be66c 100644 --- a/source/contract/contract-samples/pom.xml +++ b/source/contract/contract-samples/pom.xml @@ -27,6 +27,12 @@ ${project.version} provided + + + com.alibaba + fastjson + + @@ -34,11 +40,11 @@ maven-assembly-plugin - transfer + complex false - com.jd.blockchain.contract.TransferContractImpl + com.jd.blockchain.contract.ComplexContractImpl diff --git a/source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContract.java b/source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContract.java new file mode 100644 index 00000000..b23511f3 --- /dev/null +++ b/source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContract.java @@ -0,0 +1,7 @@ +package com.jd.blockchain.contract; + +@Contract +public interface ComplexContract { + @ContractEvent(name = "read-key") + String read(String address, String key); +} diff --git a/source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContractImpl.java b/source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContractImpl.java new file mode 100644 index 00000000..15763954 --- /dev/null +++ b/source/contract/contract-samples/src/main/java/com/jd/blockchain/contract/ComplexContractImpl.java @@ -0,0 +1,12 @@ +package com.jd.blockchain.contract; + + +import com.alibaba.fastjson.JSON; + +public class ComplexContractImpl implements ComplexContract { + @Override + public String read(String address, String key) { + String json = JSON.toJSONString(address); + return System.currentTimeMillis() + "" + json; + } +} diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptService.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptService.java new file mode 100644 index 00000000..ca998b89 --- /dev/null +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptService.java @@ -0,0 +1,8 @@ +package com.jd.blockchain.gateway.service; + +import com.jd.blockchain.ledger.TransactionRequest; + +public interface GatewayInterceptService { + + void intercept(TransactionRequest txRequest); +} diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java new file mode 100644 index 00000000..b70bee5d --- /dev/null +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java @@ -0,0 +1,42 @@ +package com.jd.blockchain.gateway.service; + +import com.jd.blockchain.gateway.PeerService; +import com.jd.blockchain.ledger.ContractCodeDeployOperation; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.utils.IllegalDataException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class GatewayInterceptServiceHandler implements GatewayInterceptService { + + @Autowired + private PeerService peerService; + + @Override + public void intercept(TransactionRequest txRequest) { + // 当前仅处理合约发布的请求 + Operation[] operations = txRequest.getTransactionContent().getOperations(); + if (operations != null && operations.length > 0) { + for (Operation op : operations) { + if (ContractCodeDeployOperation.class.isAssignableFrom(op.getClass())) { + // 发布合约请求 + contractCheck((ContractCodeDeployOperation)op); + } + } + } + } + + private void contractCheck(final ContractCodeDeployOperation contractOP) { + // + byte[] chainCode = contractOP.getChainCode(); + if (chainCode == null || chainCode.length == 0) { + throw new IllegalDataException("Contract's content is empty !!!"); + } + + + + + } +} From 5fadf9bdb857f9c5c4fbdbcd3570d4e96b68d666 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Sat, 29 Jun 2019 17:32:55 +0800 Subject: [PATCH 002/124] =?UTF-8?q?SDK=E5=A2=9E=E5=8A=A0=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=8A=9F=E8=83=BD=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jd/blockchain/ContractVerifyMojo.java | 66 +-------- .../BlockchainOperationFactory.java | 13 ++ source/utils/utils-common/pom.xml | 6 + .../utils/jar/ContractJarUtils.java | 136 ++++++++++++++++++ .../src/main/resources/complex.jar | Bin 0 -> 476981 bytes .../test/my/utils/ContractJarUtilsTest.java | 64 +++++++++ .../src/test/resources/complex.jar | Bin 0 -> 476981 bytes 7 files changed, 222 insertions(+), 63 deletions(-) create mode 100644 source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java create mode 100644 source/utils/utils-common/src/main/resources/complex.jar create mode 100644 source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java create mode 100644 source/utils/utils-common/src/test/resources/complex.jar diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java index 7dd78300..adb898b2 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java @@ -36,6 +36,8 @@ import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.stream.Collectors; +import static com.jd.blockchain.utils.jar.ContractJarUtils.*; + /** * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. * This is a try of "from Initail to Abandoned". @@ -50,8 +52,6 @@ public class ContractVerifyMojo extends AbstractMojo { Logger logger = LoggerFactory.getLogger(ContractVerifyMojo.class); - private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; - @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; @@ -130,75 +130,15 @@ public class ContractVerifyMojo extends AbstractMojo { File finalJar = new File(finalJarPath); - copy(dstJar, finalJar, new JarEntry(JDCHAIN_META), txtBytes, null); + copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); // 删除临时文件 FileUtils.forceDelete(dstJar); return finalJar; - // 删除srcJar - - // 删除finalJar -// FileUtils.forceDelete(finalJar); -// // 删除srcJar -// srcJar.deleteOnExit(); -// -// // 修改名字 -// finalJar.renameTo(srcJar); - } - - private void copy(File srcJar, File dstJar) throws IOException { - copy(srcJar, dstJar, null, null, null); } - private void copy(File srcJar, File dstJar, JarEntry addEntry, byte[] addBytes, String filter) throws IOException { - JarFile jarFile = new JarFile(srcJar); - Enumeration jarEntries = jarFile.entries(); - JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dstJar))); - - while(jarEntries.hasMoreElements()){ - JarEntry jarEntry = jarEntries.nextElement(); - String entryName = jarEntry.getName(); - if (filter != null && filter.equals(entryName)) { - continue; - } - System.out.println(entryName); - jarOut.putNextEntry(jarEntry); - jarOut.write(readStream(jarFile.getInputStream(jarEntry))); - jarOut.closeEntry(); - } - if (addEntry != null) { - jarOut.putNextEntry(addEntry); - jarOut.write(addBytes); - jarOut.closeEntry(); - } - jarOut.flush(); - jarOut.finish(); - jarOut.close(); - jarFile.close(); - } - - private String jdChainTxt(byte[] content) { - // hash=Hex(hash(content)) - String hashTxt = "hash:" + DigestUtils.sha256Hex(content); - System.out.println(hashTxt); - return hashTxt; - } - - private byte[] readStream(InputStream inputStream) { - try (ByteArrayOutputStream outSteam = new ByteArrayOutputStream()) { - byte[] buffer = new byte[1024]; - int len; - while ((len = inputStream.read(buffer)) != -1) { - outSteam.write(buffer, 0, len); - } - inputStream.close(); - return outSteam.toByteArray(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } private class MethodVisitor extends VoidVisitorAdapter { @Override diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index ef9b138a..a8f34045 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -1,8 +1,15 @@ package com.jd.blockchain.transaction; +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; +import java.util.Enumeration; import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BytesValue; @@ -16,6 +23,9 @@ import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.jar.ContractJarUtils; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; /** * @author huanghaiquan @@ -250,6 +260,9 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private class ContractCodeDeployOperationBuilderFilter implements ContractCodeDeployOperationBuilder { @Override public ContractCodeDeployOperation deploy(BlockchainIdentity id, byte[] chainCode) { + // 校验chainCode + ContractJarUtils.verify(chainCode); + // 校验成功后发布 ContractCodeDeployOperation op = CONTRACT_CODE_DEPLOY_OP_BUILDER.deploy(id, chainCode); operationList.add(op); return op; diff --git a/source/utils/utils-common/pom.xml b/source/utils/utils-common/pom.xml index ff45bf19..d0aea4d7 100644 --- a/source/utils/utils-common/pom.xml +++ b/source/utils/utils-common/pom.xml @@ -24,6 +24,12 @@ commons-codec + + commons-io + commons-io + 2.4 + + net.i2p.crypto eddsa diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java new file mode 100644 index 00000000..08ccdbff --- /dev/null +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java @@ -0,0 +1,136 @@ +package com.jd.blockchain.utils.jar; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Random; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +public class ContractJarUtils { + + private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; + + private static final int JDCHAIN_HASH_LENGTH = 69; + + private static final Random FILE_RANDOM = new Random(); + + public static void verify(byte[] chainCode) { + // 首先生成合约文件 + File jarFile = newJarFile(); + try { + FileUtils.writeByteArrayToFile(jarFile, chainCode); + // 校验合约文件 + verify(jarFile); + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + // 删除文件 + try { + FileUtils.forceDelete(jarFile); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + private static void verify(File jarFile) throws Exception { + // 首先判断jarFile中是否含有META-INF/JDCHAIN.TXT,并将其读出 + URL jarUrl = new URL("jar:file:" + jarFile.getPath() + "!/" + JDCHAIN_META); + InputStream inputStream = jarUrl.openStream(); + if (inputStream == null) { + throw new IllegalStateException(JDCHAIN_META + " IS NULL !!!"); + } + byte[] bytes = IOUtils.toByteArray(inputStream); + if (bytes == null || bytes.length != JDCHAIN_HASH_LENGTH) { + throw new IllegalStateException(JDCHAIN_META + " IS Illegal !!!"); + } + // 获取对应的Hash内容 + String txt = new String(bytes, StandardCharsets.UTF_8); + + // 生成新的Jar包文件,该文件路径与JarFile基本一致 + File tempJar = newJarFile(); + + // 复制除JDCHAIN.TXT之外的部分 + copy(jarFile, tempJar, null, null, JDCHAIN_META); + + // 生成新Jar包对应的Hash内容 + String verifyTxt = jdChainTxt(FileUtils.readFileToByteArray(tempJar)); + + // 删除临时文件 + FileUtils.forceDelete(tempJar); + + // 校验Jar包内容 + if (!txt.equals(verifyTxt)) { + throw new IllegalStateException(String.format("Jar [%s] verify Illegal !!!", jarFile.getName())); + } + } + + public static void copy(File srcJar, File dstJar) throws IOException { + copy(srcJar, dstJar, null, null, null); + } + + public static void copy(File srcJar, File dstJar, JarEntry addEntry, byte[] addBytes, String filter) throws IOException { + JarFile jarFile = new JarFile(srcJar); + Enumeration jarEntries = jarFile.entries(); + JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dstJar))); + + while(jarEntries.hasMoreElements()){ + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (filter != null && filter.equals(entryName)) { + continue; + } + jarOut.putNextEntry(jarEntry); + jarOut.write(readStream(jarFile.getInputStream(jarEntry))); + jarOut.closeEntry(); + } + if (addEntry != null) { + jarOut.putNextEntry(addEntry); + jarOut.write(addBytes); + jarOut.closeEntry(); + } + + jarOut.flush(); + jarOut.finish(); + jarOut.close(); + jarFile.close(); + } + + public static String jdChainTxt(byte[] content) { + // hash=Hex(hash(content)) + return "hash:" + DigestUtils.sha256Hex(content); + } + + public static JarEntry jdChainMetaTxtJarEntry() { + return new JarEntry(JDCHAIN_META); + } + + private static byte[] readStream(InputStream inputStream) { + try (ByteArrayOutputStream outSteam = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outSteam.write(buffer, 0, len); + } + inputStream.close(); + return outSteam.toByteArray(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static File newJarFile() { + return new File("contract-" + + System.currentTimeMillis() + "-" + + System.nanoTime() + "-" + + FILE_RANDOM.nextInt(1024) + + ".jar"); + } +} diff --git a/source/utils/utils-common/src/main/resources/complex.jar b/source/utils/utils-common/src/main/resources/complex.jar new file mode 100644 index 0000000000000000000000000000000000000000..6f40ca026f609f0ca2c9fa8063815f410b684b6c GIT binary patch literal 476981 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≷IgksBbK;^0w$}Vq?TlR)H+F_PA+#m=H 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

fPF&49(+Iv8rtyi>B8dY!?ed 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

    NG*0lFY_wz^}npTMX>ZOp0vWfB)QwIjqY+ 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

    LW^9MVsoAH}%BP*@PwimKJUYJCJ;P^dc}X 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 Mp;QTWZfil1y&h?# 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�#EVF;OsTja(GCJ+4Z?T{aK|^YU~{F2iSzN> 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 TkY<5w2>h321s1K}L4W;!{Ge;_ literal 0 HcmV?d00001 diff --git a/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java b/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java new file mode 100644 index 00000000..9caa6da2 --- /dev/null +++ b/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java @@ -0,0 +1,64 @@ +package test.my.utils; + +import org.apache.commons.io.FileUtils; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +import static com.jd.blockchain.utils.jar.ContractJarUtils.*; +import static org.junit.Assert.fail; + +public class ContractJarUtilsTest { + + private String jarName = "complex"; + + @Test + public void test() { + + byte[] chainCode = null; + try { + ClassPathResource classPathResource = new ClassPathResource(jarName + ".jar"); + String classPath = classPathResource.getFile().getParentFile().getPath(); + + // 首先将Jar包转换为指定的格式 + String srcJarPath = classPath + + File.separator + jarName + ".jar"; + + String dstJarPath = classPath + + File.separator + jarName + "-temp-" + System.currentTimeMillis() + ".jar"; + + File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); + + // 首先进行Copy处理 + copy(srcJar, dstJar); + + byte[] txtBytes = jdChainTxt(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + + String finalJarPath = classPath + + File.separator + jarName + "-jdchain.jar"; + + File finalJar = new File(finalJarPath); + + copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); + + // 删除临时文件 + FileUtils.forceDelete(dstJar); + + // 读取finalJar中的内容 + chainCode = FileUtils.readFileToByteArray(finalJar); + + FileUtils.forceDelete(finalJar); + } catch (Exception e) { + e.printStackTrace(); + } + try { + verify(chainCode); + System.out.println("Verify Success !!!"); + } catch (Exception e) { + fail("Verify Fail !!"); + } + + } +} diff --git a/source/utils/utils-common/src/test/resources/complex.jar b/source/utils/utils-common/src/test/resources/complex.jar new file mode 100644 index 0000000000000000000000000000000000000000..6f40ca026f609f0ca2c9fa8063815f410b684b6c GIT binary patch literal 476981 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≷IgksBbK;^0w$}Vq?TlR)H+F_PA+#m=H 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

    fPF&49(+Iv8rtyi>B8dY!?ed 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

      NG*0lFY_wz^}npTMX>ZOp0vWfB)QwIjqY+ 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

      LW^9MVsoAH}%BP*@PwimKJUYJCJ;P^dc}X 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 Mp;QTWZfil1y&h?# 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�#EVF;OsTja(GCJ+4Z?T{aK|^YU~{F2iSzN> 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 TkY<5w2>h321s1K}L4W;!{Ge;_ literal 0 HcmV?d00001 From d1bbc6ec228bac2e52da295c7ec3242921256042 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 2 Jul 2019 11:05:56 +0800 Subject: [PATCH 003/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bftsmart/service/BftsmartNodeServer.java | 11 +- .../com/jd/blockchain/CheckImportsMojo.java | 256 ++++++------ .../com/jd/blockchain/ContractCheckMojo.java | 121 +++--- .../com/jd/blockchain/ContractVerifyMojo.java | 371 +++++++++++++++--- .../src/main/resources/config.properties | 9 +- .../ledger/CheckImportsMojoTest.java | 28 -- .../blockchain/ledger/ContractTestBase.java | 50 +++ .../ledger/ContractVerifyMojoTest.java | 3 +- .../blockchain/ledger/ContractVerifyTest.java | 47 +++ .../src/test/resources/complex.jar | Bin 0 -> 476981 bytes source/gateway/pom.xml | 17 +- .../service/GatewayQueryServiceHandler.java | 4 +- source/utils/utils-common/pom.xml | 17 + .../decompiler/loads/BytesTypeLoader.java | 2 +- .../decompiler/utils/DecompilerUtils.java | 4 +- .../utils/jar/ContractJarUtils.java | 2 +- 16 files changed, 640 insertions(+), 302 deletions(-) delete mode 100644 source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/CheckImportsMojoTest.java create mode 100644 source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java create mode 100644 source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java create mode 100644 source/contract/contract-maven-plugin/src/test/resources/complex.jar rename source/{gateway/src/main/java/com/jd/blockchain/gateway => utils/utils-common/src/main/java/com/jd/blockchain/utils}/decompiler/loads/BytesTypeLoader.java (99%) rename source/{gateway/src/main/java/com/jd/blockchain/gateway => utils/utils-common/src/main/java/com/jd/blockchain/utils}/decompiler/utils/DecompilerUtils.java (98%) diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java index 162ff130..869e0762 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java @@ -123,11 +123,11 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer return; } - protected void initConfig(int id, String systemConfig, String hostsConfig) { - - this.tomConfig = new TOMConfiguration(id, systemConfig, hostsConfig); - - } +// protected void initConfig(int id, String systemConfig, String hostsConfig) { +// +// this.tomConfig = new TOMConfiguration(id, systemConfig, hostsConfig); +// +// } protected void initConfig(int id, Properties systemsConfig, HostsConfig hostConfig) { this.tomConfig = new TOMConfiguration(id, systemsConfig, hostConfig); @@ -309,6 +309,7 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer try { LOGGER.debug("Start replica...[ID=" + getId() + "]"); + // 调整绑定Host this.replica = new ServiceReplica(tomConfig, this, this); this.topology = new BftsmartTopology(replica.getReplicaContext().getCurrentView()); status = Status.RUNNING; diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java index 01e3be99..8ed9b38e 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java @@ -1,128 +1,128 @@ -package com.jd.blockchain; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.PackageDeclaration; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import com.jd.blockchain.contract.ContractType; -import com.jd.blockchain.utils.IllegalDataException; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Properties; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.stream.Collectors; - -/** - * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. - * This is a try of "from Initail to Abandoned". - * Since we are good at the class, why not? - * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. - * - * by zhaogw - * date 2019-06-05 16:17 - */ -@Mojo(name = "checkImports") -public class CheckImportsMojo extends AbstractMojo { - - Logger logger = LoggerFactory.getLogger(CheckImportsMojo.class); - - @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; - - /** - * jar's name; - */ - @Parameter - private String finalName; - - @Override - public void execute() throws MojoFailureException { - List sources; - try { - InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); - Properties properties = new Properties(); - properties.load(inputStream); - String[] packageBlackList = properties.getProperty("blacklist").split(","); - Path baseDirPath = project.getBasedir().toPath(); - sources = Files.find(baseDirPath, Integer.MAX_VALUE, (file, attrs) -> (file.toString().endsWith(".java"))).collect(Collectors.toList()); - for (Path path : sources) { - CompilationUnit compilationUnit = JavaParser.parse(path); - - compilationUnit.accept(new MethodVisitor(), null); - - NodeList imports = compilationUnit.getImports(); - for (ImportDeclaration imp : imports) { - String importName = imp.getName().asString(); - for (String item : packageBlackList) { - if (importName.startsWith(item)) { - throw new MojoFailureException("在源码中不允许包含此引入包:" + importName); - } - } - } - - //now we parse the jar; - String jarPath = project.getBuild().getDirectory()+ File.separator+finalName+".jar"; - File jarFile = new File(jarPath); - URL jarURL = jarFile.toURI().toURL(); - ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL},this.getClass().getClassLoader()); - Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); - String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); - try { - Class mainClass = classLoader.loadClass(contractMainClass); - ContractType.resolve(mainClass); - } catch (ClassNotFoundException e) { - throw new IllegalDataException(e.getMessage()); - } - } - } catch (IOException exception) { - logger.error(exception.getMessage()); - throw new MojoFailureException("IO ERROR"); - } catch (NullPointerException e) { - logger.error(e.getMessage()); - } - } - - private class MethodVisitor extends VoidVisitorAdapter { - @Override - public void visit(MethodDeclaration n, Void arg) { - /* here you can access the attributes of the method. - this method will be called for all methods in this - CompilationUnit, including inner class methods */ - logger.info("method:"+n.getName()); - super.visit(n, arg); - } - - @Override - public void visit(ClassOrInterfaceDeclaration n, Void arg) { - logger.info("class:"+n.getName()+" extends:"+n.getExtendedTypes()+" implements:"+n.getImplementedTypes()); - - super.visit(n, arg); - } - - @Override - public void visit(PackageDeclaration n, Void arg) { - logger.info("package:"+n.getName()); - super.visit(n, arg); - } - - } -} +//package com.jd.blockchain; +// +//import com.github.javaparser.JavaParser; +//import com.github.javaparser.ast.CompilationUnit; +//import com.github.javaparser.ast.ImportDeclaration; +//import com.github.javaparser.ast.NodeList; +//import com.github.javaparser.ast.PackageDeclaration; +//import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +//import com.github.javaparser.ast.body.MethodDeclaration; +//import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +//import com.jd.blockchain.contract.ContractType; +//import com.jd.blockchain.utils.IllegalDataException; +//import org.apache.maven.plugin.AbstractMojo; +//import org.apache.maven.plugin.MojoFailureException; +//import org.apache.maven.plugins.annotations.Mojo; +//import org.apache.maven.plugins.annotations.Parameter; +//import org.apache.maven.project.MavenProject; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.File; +//import java.io.IOException; +//import java.io.InputStream; +//import java.net.URL; +//import java.net.URLClassLoader; +//import java.nio.file.Files; +//import java.nio.file.Path; +//import java.util.List; +//import java.util.Properties; +//import java.util.jar.Attributes; +//import java.util.jar.JarFile; +//import java.util.stream.Collectors; +// +///** +// * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. +// * This is a try of "from Initail to Abandoned". +// * Since we are good at the class, why not? +// * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. +// * +// * by zhaogw +// * date 2019-06-05 16:17 +// */ +//@Mojo(name = "checkImports") +//public class CheckImportsMojo extends AbstractMojo { +// +// Logger logger = LoggerFactory.getLogger(CheckImportsMojo.class); +// +// @Parameter(defaultValue = "${project}", required = true, readonly = true) +// private MavenProject project; +// +// /** +// * jar's name; +// */ +// @Parameter +// private String finalName; +// +// @Override +// public void execute() throws MojoFailureException { +// List sources; +// try { +// InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); +// Properties properties = new Properties(); +// properties.load(inputStream); +// String[] packageBlackList = properties.getProperty("blacklist").split(","); +// Path baseDirPath = project.getBasedir().toPath(); +// sources = Files.find(baseDirPath, Integer.MAX_VALUE, (file, attrs) -> (file.toString().endsWith(".java"))).collect(Collectors.toList()); +// for (Path path : sources) { +// CompilationUnit compilationUnit = JavaParser.parse(path); +// +// compilationUnit.accept(new MethodVisitor(), null); +// +// NodeList imports = compilationUnit.getImports(); +// for (ImportDeclaration imp : imports) { +// String importName = imp.getName().asString(); +// for (String item : packageBlackList) { +// if (importName.startsWith(item)) { +// throw new MojoFailureException("在源码中不允许包含此引入包:" + importName); +// } +// } +// } +// +// //now we parse the jar; +// String jarPath = project.getBuild().getDirectory()+ File.separator+finalName+".jar"; +// File jarFile = new File(jarPath); +// URL jarURL = jarFile.toURI().toURL(); +// ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL},this.getClass().getClassLoader()); +// Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); +// String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); +// try { +// Class mainClass = classLoader.loadClass(contractMainClass); +// ContractType.resolve(mainClass); +// } catch (ClassNotFoundException e) { +// throw new IllegalDataException(e.getMessage()); +// } +// } +// } catch (IOException exception) { +// logger.error(exception.getMessage()); +// throw new MojoFailureException("IO ERROR"); +// } catch (NullPointerException e) { +// logger.error(e.getMessage()); +// } +// } +// +// private class MethodVisitor extends VoidVisitorAdapter { +// @Override +// public void visit(MethodDeclaration n, Void arg) { +// /* here you can access the attributes of the method. +// this method will be called for all methods in this +// CompilationUnit, including inner class methods */ +// logger.info("method:"+n.getName()); +// super.visit(n, arg); +// } +// +// @Override +// public void visit(ClassOrInterfaceDeclaration n, Void arg) { +// logger.info("class:"+n.getName()+" extends:"+n.getExtendedTypes()+" implements:"+n.getImplementedTypes()); +// +// super.visit(n, arg); +// } +// +// @Override +// public void visit(PackageDeclaration n, Void arg) { +// logger.info("package:"+n.getName()); +// super.visit(n, arg); +// } +// +// } +//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java index 7d823d95..1403e079 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java @@ -1,6 +1,7 @@ package com.jd.blockchain; -import com.jd.blockchain.utils.ConsoleUtils; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import org.apache.commons.io.FileUtils; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; @@ -12,7 +13,6 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.invoker.*; import org.codehaus.plexus.util.xml.Xpp3Dom; -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,9 +22,25 @@ import java.util.Collections; import java.util.List; -@Mojo(name = "contractCheck") +@Mojo(name = "Contract.Check") public class ContractCheckMojo extends AbstractMojo { - Logger logger = LoggerFactory.getLogger(ContractCheckMojo.class); + Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); + + public static final String CONTRACT_VERIFY = "Contract.Verify"; + + private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; + + private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; + + private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; + + private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; + + private static final String GOALS_VERIFY = "verify"; + + private static final String GOALS_PACKAGE = "package"; + + private static final String OUT_POM_XML = "OutPom.xml"; @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; @@ -58,27 +74,21 @@ public class ContractCheckMojo extends AbstractMojo { */ @Override public void execute() { - this.compileFiles(); - + compileFiles(); } private void compileFiles(){ - // 获取当前项目pom.xml文件所在路径 -// URL targetClasses = this.getClass().getClassLoader().getResource(""); -// File file = new File(targetClasses.getPath()); -// String pomXmlPath = file.getParentFile().getParent() + File.separator + "pom.xml"; + try (FileInputStream fis = new FileInputStream(project.getFile())) { - FileInputStream fis = null; - try { -// fis = new FileInputStream(new File(pomXmlPath)); - fis = new FileInputStream(project.getFile()); MavenXpp3Reader reader = new MavenXpp3Reader(); Model model = reader.read(fis); //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; - Plugin plugin = model.getBuild().getPluginsAsMap().get("com.jd.blockchain:contract-maven-plugin"); + Plugin plugin = model.getBuild().getPluginsAsMap() + .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); if(plugin == null){ - plugin = model.getBuild().getPluginsAsMap().get("org.apache.maven.plugins:contract-maven-plugin"); + plugin = model.getBuild().getPluginsAsMap() + .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); } if(plugin == null) { @@ -86,26 +96,18 @@ public class ContractCheckMojo extends AbstractMojo { } model.getBuild().removePlugin(plugin); -// model.getBuild().setPlugins(null); - -// ConsoleUtils.info("----- 不携带Plugin -----"); -// print(model); List plugins = new ArrayList<>(); plugins.add(createAssembly()); - plugins.add(createCheckImports()); + plugins.add(createContractVerify()); model.getBuild().setPlugins(plugins); - ConsoleUtils.info("----- add Plugin -----"); handle(model); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (XmlPullParserException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + } catch (Exception e) { + LOG.error(e.getMessage()); + throw new IllegalStateException(e); } } @@ -116,43 +118,35 @@ public class ContractCheckMojo extends AbstractMojo { try { request.setPomFile(file); - request.setGoals( Collections.singletonList( "verify" ) ); -// request.setMavenOpts("-DmainClass="+mainClass); + request.setGoals(Collections.singletonList(GOALS_VERIFY)); invoker.setMavenHome(new File(mvnHome)); invoker.execute(request); } catch (MavenInvocationException e) { - e.printStackTrace(); + LOG.error(e.getMessage()); + throw new IllegalStateException(e); } } - private Plugin createCheckImports() { + private Plugin createContractVerify() { Plugin plugin = new Plugin(); - plugin.setGroupId("com.jd.blockchain"); - plugin.setArtifactId("contract-maven-plugin"); + plugin.setGroupId(JDCHAIN_PACKAGE); + plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); plugin.setVersion(ledgerVersion); Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); finalNameNode.setValue(finalName); Xpp3Dom configuration = new Xpp3Dom("configuration"); configuration.addChild(finalNameNode); - plugin.setConfiguration(configuration); - PluginExecution pluginExecution = new PluginExecution(); - pluginExecution.setId("make-assembly"); - pluginExecution.setPhase("verify"); - List goals = new ArrayList<>(); - goals.add("JDChain.Verify"); - pluginExecution.setGoals(goals); - List pluginExecutions = new ArrayList<>(); - pluginExecutions.add(pluginExecution); - plugin.setExecutions(pluginExecutions); + plugin.setConfiguration(configuration); + plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); return plugin; } private Plugin createAssembly() { Plugin plugin = new Plugin(); - plugin.setArtifactId("maven-assembly-plugin"); + plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); Xpp3Dom configuration = new Xpp3Dom("configuration"); @@ -180,21 +174,28 @@ public class ContractCheckMojo extends AbstractMojo { configuration.addChild(appendAssemblyId); configuration.addChild(archive); configuration.addChild(descriptorRefs); + plugin.setConfiguration(configuration); + plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); + return plugin; + } + + private List pluginExecution(String id, String phase, String goal) { PluginExecution pluginExecution = new PluginExecution(); - pluginExecution.setId("make-assembly"); - pluginExecution.setPhase("package"); + pluginExecution.setId(id); + pluginExecution.setPhase(phase); List goals = new ArrayList<>(); - goals.add("single"); + goals.add(goal); pluginExecution.setGoals(goals); List pluginExecutions = new ArrayList<>(); pluginExecutions.add(pluginExecution); - plugin.setExecutions(pluginExecutions); - return plugin; + + return pluginExecutions; } private void handle(Model model) throws IOException { + MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @@ -203,20 +204,10 @@ public class ContractCheckMojo extends AbstractMojo { byte[] buffer = outputStream.toByteArray(); - //输出文件 -// File fileOutput = new File("fileOut.xml"); -// File fileOutput = File.createTempFile("fileOut",".xml"); - File fileOutput = new File(project.getBasedir().getPath(),"fileOut.xml"); - fileOutput.createNewFile(); - - ConsoleUtils.info("fileOutput's path="+fileOutput.getPath()); - //创建文件输出流对象 - FileOutputStream fos = new FileOutputStream(fileOutput); - //将字节数组fileInput中的内容输出到文件fileOut.xml中; - ConsoleUtils.info(new String(buffer)); - fos.write(buffer); - fos.flush(); - fos.close(); - invokeCompile(fileOutput); + File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); + + FileUtils.writeByteArrayToFile(outPom, buffer); + + invokeCompile(outPom); } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java index adb898b2..28d5a1fb 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java @@ -20,6 +20,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.ResourceUtils; import java.io.*; import java.net.URL; @@ -27,15 +28,15 @@ import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Enumeration; -import java.util.List; -import java.util.Properties; +import java.util.*; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.stream.Collectors; +import static com.jd.blockchain.ContractCheckMojo.CONTRACT_VERIFY; +import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; import static com.jd.blockchain.utils.jar.ContractJarUtils.*; /** @@ -47,10 +48,10 @@ import static com.jd.blockchain.utils.jar.ContractJarUtils.*; * by zhaogw * date 2019-06-05 16:17 */ -@Mojo(name = "JDChain.Verify") +@Mojo(name = CONTRACT_VERIFY) public class ContractVerifyMojo extends AbstractMojo { - Logger logger = LoggerFactory.getLogger(ContractVerifyMojo.class); + Logger LOG = LoggerFactory.getLogger(ContractVerifyMojo.class); @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; @@ -61,53 +62,249 @@ public class ContractVerifyMojo extends AbstractMojo { @Parameter private String finalName; + private static final String JAVA_SUFFIX = ".java"; + + private static final String PATH_DIRECT = + "src" + File.separator + + "main" + File.separator + + "java" + File.separator; + + private static final String CONFIG = "config.properties"; + + private static final String BLACK_PACKAGE_LIST = "black.package.list"; + + private static final String BLACK_CLASS_LIST = "black.class.list"; + + private static final String BLACK_NAME_LIST = "black.name.list"; + @Override public void execute() throws MojoFailureException { - List sources; try { File jarFile = copyAndManage(); - InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); - Properties properties = new Properties(); - properties.load(inputStream); - String[] packageBlackList = properties.getProperty("blacklist").split(","); - Path baseDirPath = project.getBasedir().toPath(); - sources = Files.find(baseDirPath, Integer.MAX_VALUE, (file, attrs) -> (file.toString().endsWith(".java"))).collect(Collectors.toList()); - for (Path path : sources) { - CompilationUnit compilationUnit = JavaParser.parse(path); - - compilationUnit.accept(new MethodVisitor(), null); - - NodeList imports = compilationUnit.getImports(); - for (ImportDeclaration imp : imports) { - String importName = imp.getName().asString(); - for (String item : packageBlackList) { - if (importName.startsWith(item)) { - throw new MojoFailureException("在源码中不允许包含此引入包:" + importName); + Properties config = loadConfig(); + + List blackNameList = blackNameList(config); + + List blackPackageList = blackPackageList(config); + + Set blackClassSet = blackClassSet(config); + + LinkedList totalClassList = loadAllClass(jarFile); + // 该项目路径 + String projectDir = project.getBasedir().getPath(); + // 代码路径 + String codeBaseDir = projectDir + File.separator + PATH_DIRECT; + + if (!totalClassList.isEmpty()) { + + boolean isOK = true; + + for (String clazz : totalClassList) { + // 获取其包名 + String packageName = packageName(clazz); + + // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 + boolean isNameBlack = false; + for (ContractPackage blackName : blackNameList) { + isNameBlack = verifyPackage(packageName, blackName); + if (isNameBlack) { + break; } } + + // 假设是黑名单则打印日志 + if (isNameBlack) { + // 打印信息供检查 + LOG.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); + isOK = false; + continue; + } + + // 获取该Class对应的Java文件 + File javaFile = new File(codeBaseDir + clazz + JAVA_SUFFIX); + + boolean isNeedDelete = false; + if (!javaFile.exists()) { + // 表明不是项目中的内容,需要通过反编译获取该文件 + String source = null; + try { + source = decompileJarFile(jarFile.getPath(), clazz, true, StandardCharsets.UTF_8.name()); + if (source == null || source.length() == 0) { + throw new IllegalStateException(); + } + } catch (Exception e) { + LOG.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); + } + // 将source写入Java文件 + File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); + FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); + javaFile = sourceTempJavaFile; + isNeedDelete = true; + } + + // 解析文件中的内容 + CompilationUnit compilationUnit = JavaParser.parse(javaFile); + + MethodVisitor methodVisitor = new MethodVisitor(); + + compilationUnit.accept(methodVisitor, null); + + List imports = methodVisitor.importClasses; + + if (!imports.isEmpty()) { + for (String importClass : imports) { + if (importClass.endsWith("*")) { + // 导入的是包 + for (ContractPackage blackPackage : blackPackageList) { + String importPackageName = importClass.substring(0, importClass.length() - 2); + if (verifyPackage(importPackageName, blackPackage)) { + // 打印信息供检查 + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + isOK = false; + break; + } + } + } else { + // 导入的是具体的类,则判断类黑名单 + 包黑名单 + if (blackClassSet.contains(importClass)) { + // 包含导入类,该方式无法通过验证 + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); + isOK = false; + } else { + // 判断导入的该类与黑名单导入包的对应关系 + for (ContractPackage blackPackage : blackPackageList) { + if (verifyClass(importClass, blackPackage)) { + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + isOK = false; + break; + } + } + } + } + } + } + if (isNeedDelete) { + javaFile.delete(); + } + } + if (!isOK) { + throw new IllegalStateException("There are many Illegal information, please check !!!"); } - //now we parse the jar; + // 加载main-class,开始校验类型 URL jarURL = jarFile.toURI().toURL(); - ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL},this.getClass().getClassLoader()); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); - try { - Class mainClass = classLoader.loadClass(contractMainClass); - ContractType.resolve(mainClass); - } catch (ClassNotFoundException e) { - throw new IllegalDataException(e.getMessage()); - } + Class mainClass = classLoader.loadClass(contractMainClass); + ContractType.resolve(mainClass); + } else { + throw new IllegalStateException("There is none class !!!"); + } + } catch (Exception e) { + LOG.error(e.getMessage()); + throw new MojoFailureException(e.getMessage()); + } + } + + private List blackNameList(Properties config) { + return blackList(config, BLACK_NAME_LIST); + } + + private Set blackClassSet(Properties config) { + Set blackClassSet = new HashSet<>(); + String attrProp = config.getProperty(BLACK_CLASS_LIST); + if (attrProp != null && attrProp.length() > 0) { + String[] attrPropArray = attrProp.split(","); + for (String attr : attrPropArray) { + blackClassSet.add(attr.trim()); + } + } + return blackClassSet; + } + + private List blackPackageList(Properties config) { + return blackList(config, BLACK_PACKAGE_LIST); + } + + private List blackList(Properties config, String attrName) { + List list = new ArrayList<>(); + String attrProp = config.getProperty(attrName); + if (attrProp != null || attrProp.length() > 0) { + String[] attrPropArray = attrProp.split(","); + for (String attr : attrPropArray) { + list.add(new ContractPackage(attr)); + } + } + return list; + } + + private boolean verifyPackage(String packageName, ContractPackage contractPackage) { + boolean verify = false; + if (packageName.equals(contractPackage.packageName)) { + // 完全相同 + verify = true; + } else if (packageName.startsWith(contractPackage.packageName) && + contractPackage.isTotal) { + // 以某个包开头 + verify = true; + } + return verify; + } + + private boolean verifyClass(String className, ContractPackage contractPackage) { + boolean verify = false; + + if (contractPackage.isTotal) { + // 表示该包下面的其他所有包都会受限制,此处需要判断起始 + if (className.startsWith(contractPackage.packageName)) { + verify = true; + } + } else { + // 表示该包必须完整匹配ClassName所在包 + // 获取ClassName所在包 + String packageName = packageNameByDot(className); + if (packageName.equals(contractPackage.packageName)) { + verify = true; } - } catch (IOException exception) { - logger.error(exception.getMessage()); - throw new MojoFailureException("IO ERROR"); - } catch (NullPointerException e) { - logger.error(e.getMessage()); } + return verify; + } + + private String packageNameByDot(String className) { + String[] array = className.split("."); + if (Character.isLowerCase(array[array.length - 2].charAt(0))) { + // 如果是小写,表示非内部类 + // 获取完整包名 + return className.substring(0, className.lastIndexOf(".")); + } + // 表示为内部类,该包拼装组成 + StringBuilder buffer = new StringBuilder(); + for (String s : array) { + if (buffer.length() > 0) { + buffer.append("."); + } + if (Character.isUpperCase(s.charAt(0))) { + // 表明已经到具体类 + break; + } + buffer.append(s); + } + + if (buffer.length() == 0) { + throw new IllegalStateException(String.format("Import Class [%s] Illegal !!!", className)); + } + + return buffer.toString(); + } + + private String packageName(String clazz) { + int index = clazz.lastIndexOf("/"); + String packageName = clazz.substring(0, index); + return packageName.replaceAll("/", "."); } private File copyAndManage() throws IOException { @@ -138,30 +335,104 @@ public class ContractVerifyMojo extends AbstractMojo { return finalJar; } + private Properties loadConfig() throws Exception { + + Properties properties = new Properties(); + properties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG)); + + return properties; + } + + private LinkedList loadAllClass(File file) throws Exception { + JarFile jarFile = new JarFile(file); + LinkedList allClass = new LinkedList<>(); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (entryName.endsWith(".class")) { + // 内部类,不需要处理 + if (!entryName.contains("$")) { + allClass.addLast(entryName.substring(0, entryName.length() - 6)); + } + } + } + return allClass; + } + + private String tempPath(String codeBaseDir, String clazz) { + // 获取最后的名称 + String[] classArray = clazz.split("/"); + String tempPath = codeBaseDir + classArray[classArray.length - 1] + "_" + + System.currentTimeMillis() + "_" + System.nanoTime() + JAVA_SUFFIX; + return tempPath; + } + + private static class MethodVisitor extends VoidVisitorAdapter { + + private List importClasses = new ArrayList<>(); + +// @Override +// public void visit(MethodDeclaration n, Void arg) { +// /* here you can access the attributes of the method. +// this method will be called for all methods in this +// CompilationUnit, including inner class methods */ +// super.visit(n, arg); +// } +// +// @Override +// public void visit(ClassOrInterfaceDeclaration n, Void arg) { +// super.visit(n, arg); +// } +// +// @Override +// public void visit(PackageDeclaration n, Void arg) { +// super.visit(n, arg); +// } - private class MethodVisitor extends VoidVisitorAdapter { @Override - public void visit(MethodDeclaration n, Void arg) { - /* here you can access the attributes of the method. - this method will be called for all methods in this - CompilationUnit, including inner class methods */ - logger.info("method:"+n.getName()); + public void visit(ImportDeclaration n, Void arg) { + importClasses.add(parseClass(n.toString())); super.visit(n, arg); } - @Override - public void visit(ClassOrInterfaceDeclaration n, Void arg) { - logger.info("class:"+n.getName()+" extends:"+n.getExtendedTypes()+" implements:"+n.getImplementedTypes()); + private String parseClass(String importInfo) { + String className = importInfo.substring(7, importInfo.length() - 2); + if (importInfo.startsWith("import static ")) { + // 获取静态方法的类信息 + className = importInfo.substring(14, importInfo.lastIndexOf(".")); + } + if (!className.contains(".")) { + throw new IllegalStateException(String.format("Import Class [%s] is Illegal !!", className)); + } + return className; + } + } - super.visit(n, arg); + private static class ContractPackage { + + private String packageName; + + private boolean isTotal = false; + + public ContractPackage() { } - @Override - public void visit(PackageDeclaration n, Void arg) { - logger.info("package:"+n.getName()); - super.visit(n, arg); + public ContractPackage(String totalPackage) { + if (totalPackage.endsWith("*")) { + this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); + this.isTotal = true; + } + this.packageName = totalPackage; } + public String getPackageName() { + return packageName; + } + + public boolean isTotal() { + return isTotal; + } } } diff --git a/source/contract/contract-maven-plugin/src/main/resources/config.properties b/source/contract/contract-maven-plugin/src/main/resources/config.properties index fdfca23e..4a176409 100644 --- a/source/contract/contract-maven-plugin/src/main/resources/config.properties +++ b/source/contract/contract-maven-plugin/src/main/resources/config.properties @@ -1 +1,8 @@ -blacklist=java.io,java.net,java.util.Random \ No newline at end of file +#black.name.list:打包为合约Jar后,每个Class文件不允许使用的名称,默认不允许使用com.jd.blockchain.* +black.name.list=com.jd.blockchain.* + +#black.package.list:打包为合约中的每个Class都不允许使用的包列表,某个包下面的所有包通过.*表示 +black.package.list=java.io.*, java.net.*, org.apache.commons.io.* + +#black.class.list:打包为合约中的每个Class都不允许使用的类列表 +black.class.list=java.util.Random, com.jd.blockchain.ledger.BlockchainKeyGenerator \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/CheckImportsMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/CheckImportsMojoTest.java deleted file mode 100644 index 66b194c0..00000000 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/CheckImportsMojoTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.jd.blockchain.ledger; - -import com.jd.blockchain.CheckImportsMojo; -import org.apache.maven.plugin.testing.AbstractMojoTestCase; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -/** - * @Author zhaogw - * @Date 2019/3/1 21:27 - */ -public class CheckImportsMojoTest extends AbstractMojoTestCase { - Logger logger = LoggerFactory.getLogger(CheckImportsMojoTest.class); - - @Test - public void test1() throws Exception { - File pom = getTestFile( "src/test/resources/project-to-test/pom.xml" ); - assertNotNull( pom ); - assertTrue( pom.exists() ); - - CheckImportsMojo myMojo = (CheckImportsMojo) lookupMojo( "checkImports", pom ); - assertNotNull( myMojo ); - myMojo.execute(); - } -} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java new file mode 100644 index 00000000..09a5010f --- /dev/null +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java @@ -0,0 +1,50 @@ +package com.jd.blockchain.ledger; + +import org.apache.maven.model.Build; +import org.apache.maven.project.MavenProject; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; + +public class ContractTestBase { + + public static MavenProject mavenProjectInit() { + MavenProject mavenProject = new MavenProject(); + mavenProject.setBuild(buildInit()); + mavenProject.setFile(file()); + return mavenProject; + } + + public static File file() { + String resDir = resourceDir(); + File file = new File(resDir); + String path = file.getParentFile().getParentFile().getPath(); + return new File(path + File.separator + "src"); + } + + public static Build buildInit() { + Build build = new Build(); + build.setDirectory(resourceDir()); + return build; + } + + public static String resourceDir() { + try { + ClassPathResource classPathResource = new ClassPathResource("complex.jar"); + return classPathResource.getFile().getParentFile().getPath(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Test + public void testResourceDir() { + System.out.println(resourceDir()); + } + + @Test + public void testFile() { + System.out.println(file().getPath()); + } +} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java index 4cfc66b5..ac9813cd 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java @@ -1,6 +1,5 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.CheckImportsMojo; import com.jd.blockchain.ContractVerifyMojo; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import org.junit.Test; @@ -22,7 +21,7 @@ public class ContractVerifyMojoTest extends AbstractMojoTestCase { assertNotNull( pom ); assertTrue( pom.exists() ); - ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "JDChain.Verify", pom ); + ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "Contract.Verify", pom ); assertNotNull( myMojo ); myMojo.execute(); } diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java new file mode 100644 index 00000000..dc5d2541 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java @@ -0,0 +1,47 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.ContractVerifyMojo; +import org.apache.maven.project.MavenProject; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; + +import static com.jd.blockchain.ledger.ContractTestBase.mavenProjectInit; + +public class ContractVerifyTest { + + private MavenProject project; + + private String finalName; + + @Before + public void testInit() { + project = mavenProjectInit(); + finalName = "complex.jar"; + } + + @Test + public void test() throws Exception { + ContractVerifyMojo contractVerifyMojo = contractVerifyMojoConf(); + contractVerifyMojo.execute(); + } + + private ContractVerifyMojo contractVerifyMojoConf() throws Exception { + ContractVerifyMojo contractVerifyMojo = new ContractVerifyMojo(); + // 为不影响其内部结构,通过反射进行私有变量赋值 + Class clazz = contractVerifyMojo.getClass(); + Field projectField = clazz.getDeclaredField("project"); + Field finalNameField = clazz.getDeclaredField("finalName"); + + // 更新权限 + projectField.setAccessible(true); + finalNameField.setAccessible(true); + + // 设置具体值 + projectField.set(contractVerifyMojo, project); + finalNameField.set(contractVerifyMojo, finalName); + + return contractVerifyMojo; + } +} diff --git a/source/contract/contract-maven-plugin/src/test/resources/complex.jar b/source/contract/contract-maven-plugin/src/test/resources/complex.jar new file mode 100644 index 0000000000000000000000000000000000000000..3c595b4dbf9fb64484fb54c9ff02f184aed66e6b GIT binary patch literal 476981 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≷IgksBbK;^0w$}Vq?TlR)H+F_PA+#m=H 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

      fPF&49(+Iv8rtyi>B8dY!?ed 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

        NG*0lFY_wz^}npTMX>ZOp0vWfB)QwIjqY+ zt2( zGScBDJCGQogQldW{2L=E2BDaF^0bz|;s6C}E!HO()RS&xH%Y35im8?3eYU1=fj)Kt z;<*ZS0TtbgC4>ahVuevrd6E@Qa7V+o+F52`%P~Jrg+lqB-}rmg#p$_Xat3jSwo zeZMLiRW~#TO+eTNxIyZ&YmM{S(Mhb*z&Zwks71jQ@47x_>Cj#4s;;HG?fh6};N6={ zV1=1CHp{<06ozD2l&l;Nnf&`a+HSzH0jOu@*x!&geEOb6_2p|)SEufz%$_fM{)xZN zL4ZzpKPhJX{~3Q7{{y*AP~Ykwn*F~en5v#`NUF%+TSl&?u8iU=m?#{2_*@Mcpc%i0 z>&5voz^H->YyEHK^y<^3NwOw`w$uT_nyB;VDJ)r6su0FWg4E6A$Wms^(J zzeadJx_!P*aDB2l8^l?R65zUP+&9@BuiH-UI!>}ZkC(r7T0wGAz8?hy#sLw{X<_no zd-Z9t*a&t@L2-NOr$6vYaE=#4JMH%1S#h5B>9Jn+2iW$8MK@o`az4;!=?;A05><=2Wz8rNJJ`+K6M$l=Y0Rg&q`%v$S0Jx$P zyj~en-XU9kA1u-%(G$H~TdJbPpUUXkB*zbd&0Z z^;MmdD=r^E2)44@JcbxcU;W`cv=fj?<0cn`ElDhtm@JTB*d()z*d`~%jFutRnUR|% z&yYyKVvRn84#Zv_$(xb&e#AHh;M>{~zPZy`+wr2$ z#SlX14(1PDGdne85OEs!Pt92un)feNoSV~JN|4(N-ZZkM4-YtJSk%;y%(DN&asbgy zHV;t4Z`iKOMYk}CfK+F!JY~N{gwwwzQWCzfit|i@PZ#XBn=#UhTLBklSr88f7-gL= zY<1Bn5y7@N`8&~dZ^MSA`YyFJs|YYX=UHMfflGYQm0;zYlS!2KW7mdX!ki%bo;#H0 zWJ)T6H4Ypwp~7`0pSDONs|87Og0C+5P*RL_95G&^G|0^4$lv{&zDcs>)`$*sodEGu z^8qG*Zdrt8GMdy}G)Y@CgOwzSCeVf77KZ3wm8!2&tOzQ~H5l_DPp z8elro4R@P%*Bu^pmXFgZ^mkZmn7#rRJkakHA+2F#c-r0aup7s7VJ*Zvglfjgvb-FN zi<+up5`#}_(qunO0v|tI2sib?*jqygJ*oISrV$4C3p>R@*;~cmEkd0DfqYv7(OnWk z#Ke2^>Bvf|UM)8@emUW<>^}JW%$+i`!mYq6)cepM@syuQe!7uvus($Qzl=ii?}kKs zZH0+uiFaKId}4XS+z@?{?npa_=xn8!K#jQF#0D*dfIid*b#GP6;TgAmLx2tMgqWqz z;+xQ@;g9(eAp+J&EChUzUmKYv_15=SbVE|M-Y@)iLknfvFKsr-^>F57b<*aj42fG67< zH0RSrWKu<95bE*JEV2o-Pjgqc>z0fX;>nX6TnZD-DxbRB(>1ayrA6vhS=@`#>S%O= zmli>?S_qt?&Wmk%NXo&k@^nJEBFa2cimeW=es9InwAIM&seYB~8J^PEjCoP`Idulg z>paxQxK2vNV_Pgr71U|UR7;Xng%G0cPmT3wx=>vfRB@j6al$H0sv?jB70h)-cLF#X&AZM2J7+B630qFF~4$Y>tT))vtMZeYM{Zkp)1CcqI6=rxB-cYuMEh| zkU*8??Q3_}MBJP+d%nt!+gmrbAKIw}-HW~OQ=ppu~Q#cR_p_Ei{nm<02>#;M0S{+`; zu>hA;+PZF2>EnA3h>s3*$cq*X-XOf&OdYtAb8G8$rsduFct9>B%q^(fgI{J6HC#WvZxD~6`Kg_9U|2=|^q^ccih&CNY z2L?M=9fsF7Er=HK7m9OA9F}zNISID^OjGG3S?l);UFTIi-vz`uSNv{a$pK%MD#B)_ zA|7}*fJ~TBSSA@!{vSkPAtd{JHaRa4%?~C`v^(GkZ%3jXV27A0!+%Qp?i*TeaT@n+89LU?bX^5;24 zOG!WyAg@8ZbWv+pfo9-|T+t35If6G|o>;U3>sh#DZBcD?6R9KaH}$|N@snK#Mw1)C zBEN8hVly&v?NucP)##GJTgcM<+#45O`Xk@Sh5=q{(Hv`ohK<4IZYY%W5X*p`g$TcbpWHAp zZ*`gKysV6b!|H#rG`y|$kIzk7)FxGlKzjbFvSKu_!gQ(iULD&vGv#opLvm>-sZG)D z<8cA}yEehrtCQFL=sZ|u5M#wGFJ!Ce#nKdx-6s+;BE7C8RELh^ zRQtIqkci4%i9IERj5nlFn?wdbvI^1&ryPb@`MXL8c@Ifb+|5da-raY3S;}X)F4~Rw zFb;A9o)FW?gMlk{t_Poa?xu?yY3gVF75=Q4+_8P)^1WNY5ky-JTwM34Dm7wz7E|isSo@r~^ zgKj0g>gOY>O~Ozh8gqzC$Aj2XqJF_5zQFI}mZXW6LGXx7XVwbrtsRk)GDBo_U}Hd2 z7_kpSR?FC}!YCU<*kwOlS}(mAsXR?hoZ1!GHagKZHv$5y))~AIS9;-8OsfNXND2E5 z2QJ?q{#T2{aKhYy>rV-_8|nYLS2O)5d7ar0rPt8ue<_FBKguDRkBnG-hB0L@%lfOb zhL!j)_439h@Of`~pEvj)gOA(+}V;jFFSe zkU#h8G@ZMdFP)y-FYZ0rh+khXGp4_)ck}#tHb-;A;zP)>aWPYeNkKer%VIJtz9)O3 zOrSUWGec&Wz4UvPP)LrMH^i>dNOg@ay5`pFwZ3duAfIaSmiTw-bAs>^rLbS}PR*dADEOD3tjd$w0 zh%>U?Y$y}<$~Tb2KH*yNV=>#1jKD%B#Jn9qVlEdkVDO6$vc ztw-lOBUV^;#zsNxkWX=0g&>+BI96Bg z5k66kfv!Yc^j4NYRzHusY6?rb>OXgi-3cD(L08#bj*Zy_)Bfpiw+lTnM*q82c=wKv zy;cjTQ*UCtW#gV#KE5U9Xjb>4T4E>HH(zJ?`y!}ya^>!FN_x3%znUo@0U}6oY2PRH z?3=#KA}Q~&X4Ec5=VmppLF1_>B{~eps)Xmr&AJ;6xd)leVOm*;?SY|YILQ#vG;XO@ zZy$l?Vf?o>l3$20qU&8!c#S-~7}xij5b_HXRa$GTEfW{r9d$QitCP|+#hOd*0jVc| z6x421?^#z7UPg=~l5V9Fe65pG6sO~wF(+lm8K&y!Q`Othv{a7(53OT*fvZ3@_@l^; zDlydNFa&tYO?sr7x`M3qu>Q}0y@CUV7&rH$Yt-~H`^5+ z?|}>=1psXAq(#SF?P4m>b#noYX>Lij_o{(BE59IR*5o2R8cv?2Ut-(^JR;oubp)0DW5ZRRiN?v1YT94X;sNpajX z;S#t{Uf6a>WXZ4B=B$k(#2y(^SyT_TT8`f0|{*W3T$c(C! zFu@0B9`(SY7W`?r`m=DkNmoR~6ZYXV>Y4C}=xSZL)fDu6k)52>u90j;c-Fdn_97Pq z@c0G%(1)1$j$LCdti9!IpMCK(`cd%xGx7uqb`?3`F#l5z-UR#qBWCbBIOx0oTeV@! z7D*ZDd+RfGZA-@b3>At(1#Z>41&RX2Tr#f_ShE@UO5qL8o+HbqM9TF1k}CQAjxP$9 zq8HN6PCi>lHUO_0=NEZF;gXNBPj2Awi~>S2pNBjjIE z4Z9T#GNg7S!62DO2ac0o)WHvK+QY!;wxhGag*X#+A3FYK9E>z@7p|nz{_UAZsRc!A z;0qHMh%ELP4%K-Z2koYJ-IGyo3o7z@w$pZ|%v;SQkKo!F^O0XtbIy(V;{DA{dLA0z zjn_}^**Q10<&^g0|OW%pJ*f%eOk-4P~IdR zsSGIiOB@UIk?pnOk+%I^hxhhFRPT`oRlY#$D%?n7k9o^JLTaZ>!;fh_OSYXp{=u=z zLWbe@!!m~1wz7;^wZ-sBma5-VYhLIcIj%Sj(4BbAb4_^T3Bf-SoV=6Au&0c+T2DJF zy7DqGPm_-XY)VD+7}ZZeSSjv}zp7LW&o3-Xjt_iGpY;1!QKWv!$79cV8*<#}FlFiC zl?j#6w;ZDXz<&?SD>DnYFLnA3+yweujEtqoP6b+^1pA2Ifn2^6i2+n0q@>8n^fdf~ z(ScF*DN~KJ0abH-tbKvg%a?@vu(lKT$jSOurlmKj*K3Ku1AaA2VpURJ{=kc~ZppdkN--caG>V5e2(=mHKP6B5WJ6fJ8OLnL zK$v4RI}lT6Pv#&u3Um7@0tRxAZ~FC#DerPd^3&ZDgAyVg^RYhI)&GQ*p z1xdw`-LmsC1{>Npiocj6j5JM_}R&j^NjaUMs!9<1#E0 zF#(U7<#H=07a3R8Sy~}G!~@Hi7{cPIG|fcgL3y3v%}GCr+>xzy3DYfgMWW8V0W-qm zfkf6XE>iGC|8xZpPTey30BVLysALzb?Yu}lbC^b`7IIP)FDq6AT4QCX+5a*GMT0Np@$o#w`9KAF71*T3#kfhyj z0+Sel2tEFnUv8XQi6x1n5t({%+In#oTj>^t;*p2rCJzBdE-$onkhH9`q^uIPv@~H= z&^14Vay1h6?B$Xod%&41R#xl+hB$av2#ix%%AE(YuMd>xVHq|-vHBa$z&Z+Z)|GACT3Nx$b6w;uBrRbzq6?U zxd?pgKypWL`CN^Vw?qT9zjyu##ay`eF62M5Tpz^$=TK~AYw*wbu9dmIhq1$djqfHY zuQ^}}BXL_(Gh$3cb`AlAj;izN)FLGRicjDZ5L`telp^kwA5y&~?mde$oRBndlbgBPc**YezCK&d@%?^>>IJ4kXY;27;Ta~y zsCFR;>I{*E7D{};sHVJNgl1(za@u6l(-Ucnu((T>pfpciveO*F1_YlpYqiw-rTSxo z^3#grMU%VH)NZ>>MQ+z^)@F(RQ!h0cnpB!<@6m9zOlyC%;^;}xA-QQyVkn8Rc1TAI zli6V2=%Ip>h@FBAdbmdL_lR(jPR6iAKATEI4$bb)w@jPv6epT?&D8fDs)iK8@ob(s z^LStVv`sdZJlZm^Znf|+G{I_U&P=PSl$h{s8c9$Nt)AR$xnNM0dkVo)M}z}L(XUFy zi9A!Abo_$}5NMsY!a~({yApX@zTO|5IaDnm(dIRUK;?@SkD8E~1ox>4g*va0i^PIT7yZ0|RWdJ869lkW}o zGmwF@CWj=kYNt+H*~wpZDK@Rz{n$yu*k@Xw5)b3jE~OL|oBywfqt~{l&rW_SE|V*t+{v{djYtehZ@ag`?(BoMjs66;#>%j2-fgIa^zgmWSxR&O zN2(N)0z`YGry53pypqa%zv9C^c$Wy)6RAcLGJj-AA0opo_^jqmyD-sny=!5L*aua= z3jLh~W)~t2>R%A_hkwbEe*fW}6B;=_ya6{Z7(1l{hcari7D8=?pE`%Y=@G84D@=|I zimx-3HbaXNI3N?sA&gr*!)8vOz2W&hq2sKtvqp#V&Kg@q-JePDK+>jc3cC(7stm(L z`H7}7$g82l*!unbAdPE*%tTHwY~jTHo32XZ);jBiH<+DN%@%Lvy^S*vSAj`NK}1|6 zUH;~wQ9b(}amE$MH1wGiVP>CodJll;hl~tzFzB5zid1TdxMN7UdkW^n)RUub`Z|+* z{EXdTI2m<7Gi|teMer~(c1eGn#sd+>wL`d!OjSQ>2zr8{YB~(kIlzA3KG0UbJ>)5X zmu;65!JSmkn~MTj33rDuQ(Q?+6^bv)HTeQuSK^xCO*(3*+rrS>4gD3FH}zuwO+ ze_apfC=1aahlJVlkG`^%Qk-chu@G`NVIn-OEyjayVj@sbwK0NxI+p?V4Sx20^Q-jytJjE<8@2XrNiPdh8s zFA|B8C*rg@%q-I)#hFTT37=G7h#qE2HYUwdBE^}BOdGQSu$86Ea)5SxBPU53ixs4B zO!|Sywu8}cTnCN|m5uesmY<+8&VlBr2*8f=j-=>lF^IHB!eHlt7Fr?q>rHO=N~P&I zoIfZUlbOEcEO9Oq0=;J#(N#fiFW=~~d-?_&4Emhjjc>Vo#AG2%rc;d@nr;$aJmM9L zR*QL3+cN3i6l^CI?FuQ4m34}Ea<|qnBeONT;lVe%>L5Eyw>;b>d+%;40}n5L{&*HI z2tHC&OO!$b0Y7|1q)CL;@LmQYHVz|B%;P)7e8;hzy%XDbjArQsI<*rLiSW+cJ>iev zeRM1zkg6=yyCe{&{OSuZqI?xz{f#W0kxZC6eJsuSNiAVg9=`q-_ z;u6OH78?`cdRC~&LR?mtt8gn?VaLSpR$7>3s8LjY9l^Tbcj^q7Yx;sZd~x30Gjp~n z?;hDVCP{{RuHA}HGHO(I+G!?Xg{+L4Ofd(z<~yL>i4Wj1&KybW%(q5d$({u4y=clO zIQFq-LQfPGL9a*)`i9A-&1%qcZ5PI2*%^)vPeppnU*}V3IUn8uo=2jqWRE?xDUW@L zX4PUb_6)hGdPo|MOe9ftZ+{Y3G|4mvnl=&D^X zrPif`XOT7_vsGc)GON(L0oiKkbB4M0hW&)P<^fxc|DVbu6)fC~Dg7zS}fXq#!?bxugnm+Z6;4Apftj^<3_P+Cjug_z%DO4p}d*=asT$#Q#bc+RE zl_-zL(X2a+Cq{c@JGDgc8Tr;`%aO$a=-nVYl={3KwoB&O2^F5y_w|=ELGKANkV7)r zOn;V?1XaD%DFwnSHmj$-`663}z+T*m%XmNbHK7XIVuFI#^b2_DdW+O7_#1k}}@($r*z-D+KVZ zVXUC}pTIylF#_%kg1>bgWM+{o$-s|h?Z0I zBA~P@;fIU<@sl5&1}>mF_w!p+j6mKmnXW zm>ALF8KD=D^=m28Uf;66>~w1x80i`50T`I*GcnZBG1So2{J2^1V?e|jfEd7Hrv`f= z{&{-Cm%;xz%Ckp39wd2!Fa7W#gYD+o^ZCv* z0gZR-75LAXQ+%}IoiUxwkgAgxiZ>?f>B~QeBG#?i^sc9!t|h@8ovm2UqkW#vZuU9+ zSk8|JKI*u!1#pmaY)*l9v0xd%54EslxCmwq)S7`K1%TE|buc@~%W$4{U3!eaJGGN> zI7JQ^)LkrANoH)_Z(F0y-F}ZGxQz#t@Vqq7dWo{;%-vCEQ}O==O98)jhwHA`^W@a& zjrjfGE%4PFkiy(qX$YxZ#-$~-NvwUaxA(YHD)SqMV zk_N_yR<7%IE9u@%G*M&p+C+VG{sQAuvFD2Wp)&&5nYRarxxFw)3~SSm$I@A|CyU98 z@0Ne3v>w@_83++|6?R>V5z(AxZPL0RRis*scxN{*A%f91EfIW@W?sQMr{6R~+-fR( zF!zZ4TcC8G*V+r5saRD=5qQOY%q(5k z^EVg@Pfwe<{6p`> zP87w(VC}R}{e~3M_*GY96KU1cIf_rQ*$jD|l^nk#Cq|U(osPf{lHONWnOvp0lmU-1 zSR3N4H^enboX0F`WQ|HCwW3ik2_vfzxJ~&-T9(% z(#dw-fCUP%YtrprAPH0M)M*U%2#X=sn<)pTQggxPZ(%{0o5VnldqoxpJngnD=-mXdzCUA`(k>CSsGbLvxeJF47XrDi*}- zaG>(a-wQUw2jf5*)Ce~U*qcwh1YW7Ajyg>vlg=0H@D)jvQ*+g=;V^sU>NIiQQRxr9 z&K8nT73_r*5-SavK_>0q!jk)1RMnW!XhEhbp41!mk0#ZjE)~3#RfT4`6w-*H$qP zdp)4M!XbV$;M8HG{NdP`DCmd8DF6wZpyr1RqZ3l%WO(l!Hd$;6Zh_F5B$}Wom`XxJ z_vdV8;131A+?gpEN)yMSDNFJ|VUR!t)Ponm1EO;cXve*|LIgzT3!Z}Xb)K$m92K%cMH zFsGPGqt77<9xO;Hk8U3+q(wExjQpvbF8d12J6JI*?ngw?3p^ zl_O0xJhfuOk}gKuI>L@k3ZhyrTvOBv1rer$voRmJSHphYt7szQo>g6#-tk7OYlj9d3+yc{l zBxt5YYw)_MI6gm_+*fG0h+bzxSCCBV2~y=_97+U^FQuO=UV@6v`^r|S3Z2LnW@;)_ zr0u_-7FLonc#rbJKgQ5j(IylD7qTs~R~FaO5P7ytWMfDM-V^1ne2#m7Wcmd`1Lojm zHMQq=8kZ0&ie==h2-vFEkfd0NWTR=hZuPigk)a&SheW5A%XK0GI36C*cc)b&cAyCFvI*K!?x|7Adf-iy$-+fpdT@bcof!Z%ig8oyaxknI?w_+7#X?{M z>rJernQ&!c5bW{J&7;Z|(`YI_)ns;?wISCr4uv^Uw&E4ZgDC0uGim(O(KU(tOAGoT za>|mbC+Jn&pT~vE3;t_Mk3Ob%6H|6AH&3b>>t_>gp?X4wYg21*ug4(SEfT z?shI$fIm%=?7$jW!94{#kp^F964!3HnZR?wu5}gM0-4cnn45q0iTxn0ny_1$5R-EIoR%fStdbF+FzxLZ>1IyTPAhF`u4TSsR2o;|1>D_KBf|9U$Bq z;?FN5&jUwz+Erj%#+*5*LBiiS=ooh(-bdNcA|T$A_EJpdYE-GGL{hBvI>_!-&Y>u0=h!?09xivI3ez91`U+@VkpqV%h1Ej(L=yV4M_zXUR+r*8A7NI zfn}~S)z}fTN(U$x4RK`pkJvFT(srs+W4K<8_)zx|sQYEAYtd@aYaFt=hL7%8RhK)o zH1QnWy@HKD+}%l&cC9e|j+jwTW`(}S%SEuJgt@XJVL3sxoiJ5q)k77?R1^SD<}n9P z6`U442G2~0PhE)3k$j771k-Uv>3Bkx9x0b+HPs|<^l-XD9?k@{_;YrDhd`lmK-#Aj zAgi*A>`9EUYaNyLvqYD*F3iNd>~!Jist{Cu!m)S z5<@3l-__T3n~jEbQy09GAd^swA+iR?TVOfuhdlk*OBO+<+ABaX+S3}u-^rbrosuJv zE;Jd$pv<>Oj{z1O`bbg$@B=@dA5u$w#1Rb*jtoh|F7@FrgaF}0k1MJ_g?cjhWb@zKE3LD*~z zr5a7*^A4E3?vGr~wd5j1aGlkT?VIG4ik0D@%2x&zu@$nHI~X1bT@#owG3Rk~&^cO* z=?`5QcFR%nYI8ciKafeJcLHQnm7ZnENSj3|ozm06d&XDIr44xFz4oH*(Tr>3WMmhY z5!Ld$^XEu|VuD93&67<#TlafiU}mV&LMm|47qHB==R)NjgH<2g>rUTV8mLY_S!z|` zVKYF&JgUN`bS+GSwm`_;8}xu* zvLTphm7zAQB9kReZOk;WHlDXJ>9-zERU1`gJrp%cUvfqLmm#@5!8K=)$qlmRz`Sz^ z5tw#f4&ynvxP(RZ|w$YFut!E0x z|MrGv{ZHOFw$27t#{X{Ho22{?!6!No8(oJ{gCDAdBytmS69GO{k%D{O6N!)_1A?G= zfU+q>M*Ae`gcWP^o9rXpBa=p79g|Ft^Dy3#+qE<}en29f>y7ud_q4mtbl3YcpYIol z9_9{4>z^1z_81PF2he;q`c^fmUBgYOU8GG$J1keHO_o@G8CXe&(I2|C!;2Ir%Hd^p z^h{BMzvl8?P;@?j%!XO8OTjs~mS**v^Tg98jOReJg$vWB#SeSy445{GkHatNK@%%m z@fs5qSRq4ZscJ)K8}8hNJXUAB-nX`D)*Vx$Z9tQ|f~FmYt&;+NDzXY<@cJ#I@JiDK zOQueF$If3l;}-C9!e{Ae*=pWyi($C`W~;c@?pO#gjAM1}>2lbx0vpukpsxNIj}2-t z2iQjJ^lGv!ZHFo)Jmj7?P-T79p?vRK6R_lX&Zz3E^@1hiM2LTFZAu5KxMoDpFoS_0h^m&wckfTw+~QhWGV6^mp(mNEZ&Rbne~5L`XZk znn(rc`j*okd{SD2MMPFbG9@xW7I@Tnh{Jz=+k02YoiEGj-CJ4cW9fE82y|BgvI?>^ zuHJV(@pNrzxdQ6y9seb%gA^D!t6Fwn2R(E6z~}?dS%BJQB@~+oemII23%kcE$qGMW zpIjom^4BHm0EYqD;2h4{|)?_71c;2%$eRwBQRgMC5lIxJIq0?ALh;~9K{=ljFDXc9aZh2t;f zOrRLpnU60jjvW_EC?OW$nRItDd)ySJO24pPGwV9nz;uTgrj73S7W)Mj--A!lb37wf z+@xKYy=U>nL0B`Ju^jxkO6!rMFQ*ZFy2+L!W@+w`!v$`e4DbtSmR?*UA}O{1mkIiC zh+9qvQmfxZ>Hy$zoT;SB3zc`MCS{Un$joVg9eQEw2AyY6#mnTN^v*{{yo7_tBB1qN#{2jQo|Z?c!n@ zBH2ok5z(@lELndj;v|4nw0-~gT1cVvN zpo~yaDUqi79dmcFc7!Mhd{56)BAfG&wNTQ-%Ov~tdWG;uF?FR5?Php3OwiZ;DE4UN4$ zXem`Ap0{8tSStlH?Z~RXVFE!x%^?8T+6Y{1+vva5g9K>i zw_1;XS_E&YC#~oLzx0&XAB{9OeCeY>U^ zt#(i2$&_POaReDnXUnKy5fb+{0sCm}o~$0JqQCN0HSo9b@4V#9=Hys@x+f-S+fjC= zwthXY4tW~~haL#NCgOMDZ&tfsU@TrHyUf#OttPvUS}V6f07&XU99nGjAO_gm04>l` zh9bG{6NxaHBN?DJy^Qn`bTF%)mxccuGwr>%XdYSb1t3{AlP7$US7cs4200Cn6u;9el_g0z5{*Rbohxv0jm`ZX6540ffF*)t0_y*+3z){wq*>=Yp+@=6 zfR4jUvKG`faV*e|&6#JbJD53gqD0u}Jd*m{tWCWRE>0cw2!%3gB8>=U7QLW){kXe_ zsO8HA{cNFt#?Unm%;TVLGPe9= zVETb*DJfL(b8c7qRz!ddn$IJo4&qgeL7t=Ld~Ici*5vwtc+QLT?-r`V$$o|h2cmlX z83sqqp`E)*cQ9-;K8+}qUP&Ahn!1tPTtiRJ^J&M`XK@Sh51#}%D&LE~$SuHySq%_L zZULF96#LOUoywrLXB1r(jNn_c4NF8F(39E&BZ{si#2}oocZ* z{qP?y|9>;D{xMDeeT*h4YT60B{yVAZttgPJpa=Bgojp=$~5BDt1 zT`MOvHW(a~=ulj1ZZI(@7~J?+kosP&w_4pMFte9!^}y=N$sOJyzsnT|PQn2ve0upB zpyOzzf*z@THt|9sn6GBRK%4^t2HdEY&|M1WVU2M8&@7rw)zo{W6QM}CMr6w9Lq!z4 z_%1hW^c2B#(PPDWUUI(D{(O(SUu8uaI!AlVeK`6w|Fo(6-JBE`;XW0*$3q+j7LZMET~8-qTM z#)XE3ii%!uN+z6;1Hue#m93#7Q!v_F#yXsp9WeI@ijfH=lZ;wcqwFj~9Nu)crX)dA zIk%Gp3No)MN3(E~il1d7VH->xLQIsl&JMsuY786kh`F97nyllA{%3+S>l%t9TwzR< zSUP@fY9`gw06DS>Ps~fe!9O9-A$JKwjnq^4pbPyt@;1>0WyL9qy7~arn36_TFT_%jGVw=#ma26b{DV}j4mw{+x>%E) z3G(42PLG{Y;;2Oa&%5ucVOGK7md3fUW`0LwFS74Mtt}Mt%RtY{ljbIQ-37mze7t$M z(ZzLrRnU(3Gwz+Zpu>{l_t%?&Xpw!I1t^frWCqIdL6m`R?z6gAyI#DniivJC+;k4^ z7t2x>KaSP8b@@ateN1O}X_eNm(e-v|hh4p~KIU*(6FEu)_1y;{59HEFEf^fAL)A(e z+etvRW324`f$2PhDn#W<&Dw#;ERucgJ&Zd?#-7%bwvH&p(BToJLrhDuQM`olfg_Cq ze7mu~;f%hiPSWDFa>>Dz=lsD|9B~Ai^dasBC)sHvOpUd@H^RX>2GO2r3?QBQMQNJ< zm{WW310e-JSXlDU=J$W^jsJwF^>4lLuP*5Ni7inYasHaVwy1G&1|Yc8fJQe2nwR+9 z*%uED9?KsM&xkncKUgcK>UL+Lx((*j2tww3$t<0|Y>ivLK-z3*hZz4$AOfNZ#vH~R zSNf_|{GncR#087YI=)#vtJ&o(%k!o>TaBFW`)y=;GK<6UIP-@4c+%(Z6dezUURozp z=qEcuFK_1JaU!ek;&G#&AO6Sa`A8amcCR{+E+kqY**&{FgZicTFH# z_^t7WGQ4k;DWDbY`3Z^krv$v_9oxE>GFo@>)JoDdR{LAax);-*o`D7c?hsq^htBCw zZ-Q=|!x7iV13bt(ZJt9<%HC&~kKSIm0PusLUpj+)=chT1?&9V?g;+W&FuL*>IxM#{ z0v}L5R=Z8VmUqcOeJpl$1Jo)yEdRdmKz~q&a(2QOqVuucM&K_W?EEeL2Df|60re@v z_9=@0PGPO7(CcBn-Fds?1N~BcXM^r6`?PHp1)p*x40daLj!$|Cua$swI%FFJHz+|q z*Z4!kZ7o&{o~0lJFsPI+F3>n`Bv=e=AMrFKaG6jRoQSSKnc7W+BrED!#Y%+{HJIhAxfWJ)ita}hY#MQ`lG8+= zJYhz*fmo)i?aYHrU0llHPk}@zBskr(h-Xc}2@lX$;sWyX0&4(v6RirX^swoN?F>d)^K{0&5 zBQEQ%v-XVe25p%}ucnBxYszB1&uqWtbiQgn^=pL^+#)|sIu4(stH&Wfsi7Jl9uhvn z1&p`{hoyx|mPrig%@{4L86BwyE9QT@^M{d328=KH{R8Vp{58=to4c1x_EX8INoa=T-fn3NH-;B9smUUpYgkNnF z@$@uQIrXZH%TQ+fviNaV@rqbBOe3Y;Kx^SPnvBTBB?b=Qg$jzjc^YeFSPf(lB^9 ziAQu8mvM3!az5$&x>4g9fNgL%gqE05K!?=ho{Qk`;>l!n|1=`H@u)Pj$_Kg&ZMw=u!_qQpazs!4w2UEE+qL|!HXI7 z@~jM0GR*u^YnAb+aEzN)D^#kz;{@2}=Zc_h%)^N3`TYkT>GNk(o5m6W%!~z{G{OP- zZ9yigP(BWU{?kXc<0|HiKVf1|TvmdT5A=*9Gg5281Cz1=4_HWH=vhmNsM@KlX8Y1% zYLTmqL#g#yR(JHc3`NQ|2K(u5r1E|yY4w8Zi#_YoYhkDK1C|UTGpZ+BBo)Ax!(0O} zHFLPN3Y=1H>YUPRRnuJxPql%Wa`Ohs8M$kxD=#nfoh=<``;B!9$+?T!(WK=W%_nq* z$4LSP$*7RN&Wa!K71-J~F~sfxuzA^hVp~F*IH7~*9qpA$7U1G%@PEKeyNo@=wLL+G zU>jV=CAh2#ns>j%baPN{j-9oe%e<8OZjKqM&<|YXwUt@lVdNmG!Poz3Z_&*HXH@Z~bkFJNXVczh+TXG*m3WHj70VDc0d>U$ z#>_Y}M`(o{i_o|)TQ0D`7gZQ{sTYRxRbOKRoSRv5>#YCS4)qrMPKA@^}AgUFhN-ZQyO*WHl7kBZ1 zXEpTs{vB>EG$!_3o_Wun`2ewbFT2!-*@G|Z#G(;h@xoc3eI%nkOKxh0@r#gdnsly&m^m$|17f;mp z&oh_H2-W6+v2Wo%bM>|e)AWrfQitVRU2ra4YhaMOAYE}DhwAS7+j?ddni8ck>I#lv z6l}@*fJHQ_PXEW|69E_6kqfQTeGoC9c|>t9odCiHkJE^&{D&s^o6e+(+9Cseb1oKF zsU2Ao&!8Rqq@3LBF&E__0O#*hK7JOcQo^0`!w&+kU%z$=d)ujN`BLUd%BH&XYV45f%0TDsg`kaCSs7oWFz4k`n*d|}lNWRku zJMS-0Uj2T(DY=VI8nSe4G+lwBRCN0F748!ZT)xznahSTRyd^>bFNCN;kkbYn5tAq} z`psO^21o9dgaiZ2uF$lxEWnn34Z)R?DmlXE9n~)K-MnGUj~(iUbt-wn_Z>?e(QIe6 zoFMwb%c+;+4mc`rpkK?U^#*<=m#5N1|M^?to%qD0TvAsj;@8?ftXoeZ#K87kzZTzXg>cUVVN%zHrzkiK()dIK ze{X8PFcvFMVDrJg6@50TZS9lk#Jll+W%uw@AVgBl9h0SgIi4@L!ZToEyR1H>E1*h( zCm1W#?UULJ|7A#CF2}I4^n%m468wQy`y@cU)O{_GXuh)+gF*1^ek*~myr+Kow1+PB z9+Bn|L$%8FK39CGbfiIITNEEG%`Anci+aOZ=G4;a)S{JaLw6=Z$F6r+-y$_k8hulQ zv_0c=&)a_aZjYka$4weABn4=h2HZ@q)51jT!hqb44ox9)%LAPU$4HEuEBSn&Z}CWh zJtDu&A9Fy>8&z_L!5uKe7=bqe^@82-a&kx6{YQ5n*k|Zrrw7wIR8XPITw(9_D-~=8AWgm4$tT^kEutL2+l#R#r7i_7eWEnj&muB$8n( zc%YTg27Kd8H$F4hy6y{r`i^hD{D6BdviC2xt>2Z{K?cmPUyykJ{q^_nY)Jb5OtwigV?Sti*|rHYBT5LU{n|q(wz4R|J`}f^ZPQND_BG zJ z4zv(7?pwj@XD-CT=0T>x^G$;>L_!H@4T^!M+{On_5CGpffYb#I1}ovf5JuE# zq>h@Sr#vWgs44jVhFv)%lQq!~p%3c}_UA5%lreXc7L*@ogG(kAoyzY>fG@4PUx<#INe9F!v(jg@SzRt=uo!e4Ze(K69vvx}${!ly zXh4@;2N~c{#^;m;ZLjJ)!bT|#Z-j$VnsD}eOH5+Z#=k4+jy5|J#}pcv=ueJKkEQ#_Fq z2HBW52f=i~?NwxUY(+c7#8x~hlyitQIhJdD>d$XB7Tvo&rJz3wHNM-E^(ZDLnQR^d zgPCxgPpzKV&=Zf>ki!I1qQsmAIX5DODgnIHLR-gJ2E|wzuP)}!VYNzvi7Zb0IBhaT zF+zD*!XmaZB88?G4VhqBs6S4w5LPygL9!8dog+_WDdJ{~zOEV})eC|eYCBJBE3?dc ze2hNZ$davEhiKQ%q)j!2{fU<~ZX|ZJXaZA-8BvZgP^{U^K?PL*o)?QqMS^0CZ?bv# z^vyQiLCeEw!zq3ai!SqIQEWFgS>e)Op|+6K)b)iZfhI9jnbv4@P^x<_kQE;k&3d_5 z2P6yGSUE>iC?f@Xg(<`TJvp1ol1fD5s0 z`#W<7m4IL-pZI4P^T4YzXwO9=!a3^IIZ;hNr6yHbMeOUL?0X9y)uWwmYWW%-tl8~Q zkV@B}W+*{nQm-|?pJt|Z8=*o0g0XH7qV~Dr9b{a3T|%v(T|r^%d^@X~D|I7u-dm+j zRq)MQ>2k=Lg+DgdJaq;kLpZajmBt;CK(w1Gg0%T@WjbgE@7P}`%}~YDLikn1DVKpf zJdRXco`-rM2tqCxcAPU!>zS2rK_3y@)g!8BC47qIQMYK?I9gYDC^}GUvaCHIznm_a zv?WluX}JjFer2~7tGnob%|GnfbN9`WdBXYr0yXAvsP>Dc2|J+m!P%u|e=mT7cO`$h zg&u)7mCM8Icm+GHMkIv%4cO@UOWfF>jF_MK3CT;6Jm!{-v{XHcd};()p`qf6K-?V6 zHK;$*J++IQpy@mxPL2gplm9Q^$HbU(yizA%eKoYY=qM~mTZjfP$SYbIFh@@0mT)fB z>>bym-7c8fLuuQ>9hLtBIL=(Y>)tzzK1Y)o+ll;!G7@Z{<#C>G$huvwa7DK0Pn@I7 zqQhp)FWC460_J`C=338|{D^7d(drDU8OI}XncdcXk2LE2*_WL@saV;xzv7oiF;01f zpOX=?;$RfY6ld^tEA<>?z9&pkv+CjZEjdB*Kr`k%Z6s`piW*B9j#NcnDL=8| zV@SKM4GrFvA+>a69chaC$-S>`C5BJFeblLIlorEheysXICx(`bF9Dk1;BnP^pYZNh zag=2w;jOVl7Vnt;W#G~7tk>20qZ)7f`7-?1P4NHAS3%TS-$nnwk>ryU)n$?7(Rosu znXEC4RP6xN7f~sJY~c947%b>P5GCn(w_#h@jW`!3_n&0F?%Cj@grp<8-wGm6V4GMF ztGFjWlV7i2r&~5}_;|emYW=x{k?pGfAdZuwzQy$!#EA?I{w0L|9O;dR*chzm-~L;m z_7;s;s+5&$E;&;*(cXx~7!H@;+d_oW?$oW4_emc&bJV3L%V7yP+QWuqrvZ|!1BF0APrPOD^2a~_k~FwUUKhjj`sn!haWcBvJcjlS@=Ha)38g4jbG8iR6++;fpizo zK+*kOX#DEm%PA-2VM#Jkt?eObT^#@`${v5(up1V^EbT)Mv58{j#I_)!P(ey>^nLTf zX#nVl#%Lh=cpXy?bi>5%k7Pz!j;3Im#V<#pyk*Y; z4$0fpw$lin-HH!d;lCbzgYO=}kGT%v7RK9?E@r7Ht}6KcDM7}eNpm;HLEZEJgno9`KSi96$P*vu)(qZmm248#Jg!XLE4Kv3yA z&Ml1Tt<$>^Th5rgxvm3>%gQzyo3fMwXNRw4d9KblzGBvWm^A#{R#USV47s@+Po}-S zU9WLIA9nD3fOo-dMCPLJz@eo|QzP{tO^_GZEBeFp5t$71BhrXW)cbBZ7#xS%G9hKD z?J*+49YLvE;))Optf_TZS&VD7&f9&BchiOZKP8H3E-lUb@5aN?-EA@hHGET&5SU zBJ*+$=?^A}ohGC%%y}5{PCCyr4appWE#uj3Tf=-dm`GusTbXQ(I8g%Ir8XYhQIuF& za#Y~qoj^Lkq&LvVX9{81ib`k{EI_j&ZL*drLM(I8Z;K~0{dX%ZJ+w?RnwNO#8M&G3 z+b^**muJo(BwNUk2F}ch4z|+vF_BQk!xmpF=ho;hVN9sKeRKqzF;?s)XO2!aoTO{~ z2e??G^6U!%`k$pmC_<%DgIRcg5}%9OY9;#oHr6CFNujqUIzR=BADQ-LvioDKELY3r z3kWCs>vuV}#<4D7sPc!Vyf5PNE%2(kunZTM=4tw!3}_9Fcoyo?*7=S-HpwY-PSVmg z_|uoP8A&{%Ab4!u37uxd#;xbpvHOp!@-@U~kd@FKABWGmN} zM6S))*;r26JBk1c23zXJg)tT*@*Er4+A9X~zc61GX!kUY^p7Yr2?~Fs5WAq`8kk}@ z?i(H8$c7UI_{(SBW;O&*QDZTXS*P(sc2+AnIDWK$$Cwo%m+ z+w-9GEZBk0l)ZEXpWM~^hqMR^dsOXd6I}1IVwC6ZHMc1ZH}*_Y&ahmKWp|oKMG&{; zylRutd1g6I+mTV5Hdo1@X0#h+1@rC37`!9|SG1DuduR`NVs4M*uoTM4)d^v>jG5S9 z0>MnY$PI-D_w3eS^7g}kOwE^fnreha8k*i$TdwN-e$K>TN?<4D<8A)^uw1Z5 zA6+Tf(eK8E#C1tbP0>K|35mPY?P;q*>0vjxU-T$R9nr@x zUfEjVMZ^pPPTDQcokK?Lng(s4=yFGC9&;(~Bgo=NW;NF|{H@wCEe98olqepWk>28l z-*{81wk>RpPM!P|YCY-h;|zVaR;j-b^;Fy!wc@1>rRDm-#dd2u$H;ac!c!t#Sgd8LGN|RR(N&g1DQ8TAnXN}gVx#G@2}H7#k#V_QbxH^_4es4xgf7Xl z8*5u$$xHV4ayceQP;;MPCiKUKAISi3%#Q46)XQ>{wA3;hxQGYK@C;9YM|bzClphQ+ z7gT=YWrA>&!xL*H#-79lS-@J|-ytdil6#neI1FyjK3XFV054?(<*8IQHXr0&=TMan$954kZ|Fj|A(@746Za< zw}rbqR>xMywr!*1tT-#SZQHhO+qP}n=p-F|*=OHf-#O>jIkj)qv#M6T|JGcy#+>gn z#xq8V?z*y%6XK+w!*50$_+=$6H0s){A5L_6X#p#Sy_nbp0ssI7&Bc&a)MP_LB3e^^ zn$^(hRHs8#)|F$-In)Q2tT{x?fbmS|@k}5yFBI}`$?2@(M-cXrP1%4z{H|!dB@tec zskU2+zf?xQ?Y`2JhG+bJGH}J{^pX2oT*z^AS>TMw<#+tn^b9OQ70`}XYD@6|kH9*td_KTD!NwEKm;pQ*K1Wr& zm49Zd2gW5H*cFgGFb}Pob6*%gc5wa~BVMkS(n;gR(1aSkaNFe>f zkezgNRlk6h7sa#S5!L`x-XK+yN0MKlrBM;Y2~@aS0U7ama_4 znCz)SZJ?)8-xgYDakX9+=I&#li8lU#`3D`H12e~~^fmr#jqsnZp8s8{@b3(;N`Bou z{}Idi|Em@L4MIa0_0cB&JMHI!sH5!@N ziQ|b>OCvR1B3-E<4O1&e3&y~noJ(tzsAra{r;~|ykeVxCDF5Vm>U4!=6c)dKy1(Ci z->A>FpS~Gze!~Ce1C_TLHh)=zYkX6@5XM*QAI60It#%^n5YRhce^CQsyjG%W+E2+n z8dmQrH|fveG-E^iWaeU(1!r!{i?8039sg246XXc69u{dy36`>0&=|8He+Kwxx{bR6 z!K^t3gNpYguPXPz(7jdeHHo~_Tuz$yu`G^|%`p>#`OaNhvYK69=JkjwJR-ug`6}Ny zBAY>5&=0gj5UuO?z_+?%jahGvnXpp!ZYv&@RQlJ}>YFx$zOzYzK84DxKYY=fdmp@3{Fz$34K#ixVA9pR>)t z+Z{bDq~fvglj@d%iq|T5{3aCC11VcC#5-iT*!2*T>SOk&>OQjcTRrR($JV{ak1ikY zz3FfjkI|nR5?~>1$1eTjDjtJB*>*3v?-{4xR>P`EdssapjZJRaP`7LL(jk|OoXHP< zf+CXAR+~OFqqKHpEmkd7<*`)hnrO{jQ$e?Z;H7bI;*yu$wRFlj9=Tu%>doLowx-L5 zeGApObb2ZCG=GDPkAj*CP_n`y^s$}i;r(5LLO|1Olxgl?K5~A?g0AeM)w|PB8;-aD z?H!K|l#3ibuCg>&yZpJ(uHtS=S~#?Qa!2cgD2|04Z)Q>%T$AP@t#mY_93cX~N6he; zt^qZ(qdoGg;He|_4`y=_Zl|l8HpON0qHCIGm7Io8lfgq?)Dyk$@Kcey>w>fv`OYoN zcqXgh0&2xAs1Rf+!=GOVgB^wNHWHEFo2d?H^>gkuDDk>uXpQcK*?arg-UvL* ze42p{5BZQCWF11aU<);R=>%@0+gTIxnDDUfa5&zZ=Pz(W55ANyBeq;;L$xfsn3jbs zdaASJ8(arP(tG7B#}B?9lJKC8w4@|>z`bUutA=yUEyLM~>#)a8RoY5X5oI>0TA4XC z2XTAp%!FAJn8aBc_SW(DG^SFsyJ1Xu78)GZO|yVck@6#^MAF{E5sX++iQ4dEfBIzX#Q=wXG@|B!z zP1lAmrz-UCT(}L@iK^FOEYo zgTQ(N3we%)GzK1e-u4ykDh)Y8qnY>7B9VjP@TZkRMedS?5zD|LjizBxD=^1W&V>jE zUF+g>qnxIV?Cc_7L6c>V1auZKZ?Yv?|7>MpP%@BLz%0}?fA@_ctsATf(R{=Z8QPXc zLKqIsz>_n3>oSsoMJICOFTMM#1!`D;A;R13?v~UYsePG-K{NE=qkZ+{E^)|g+cC1Z zLv?gu6D!1sqR$dbcGs$!6DlaML4&mK$}WJ6JXnA?mehMn2RJ zNnDLQ5So@UmW{+9gFG)0#2!n+2C`k$!6P~*CLrwQpWztfcrc6I6nnkS8Xm=_^OHNt?e!9l{&(JqKw?;?SM;4e1N0P6&^>Gtg@ zL{dg6G`>QwiL^{DAJlUIhd~vYr6Qx6KJ@;kMtQ+ZuM41yN@lK?TSCO8>$Fs_Lhbik z90e&A$Ia%T;Mf~BwBy0d-sfp)t0K8+ofiKSMdq7FKlR3-i>L{vac zx|jd0i*zGJ0-XJucU36MJ15ei#j0j#RwxKvr2NqOwR50XvNk%lcZH(cK}45$a{RE4 z5)Yr`b)u#hDtYDG5Qk{jIupxj>UGq`>-)${FX5SuUuRln@kWLD$3qHO{tAJ$FCyk> z#|ZH#z1Be|>k|=Elrm{1{f^MV?+5kD2CO`DY5d16?d#)G2FR;vi6r?Ato^3zT}S1o zc4y(0{vMaHxF6JzGax4RiF`AA&~PRE_`bc$;J<6;hW~6Z*Dw}A z`OOaq;OXf1Z0`LDEc2A9Thp~H!O~{cTg23EWE~6LUU3@wEo!k`-r58&Mjs3d|r^F$Lt>tQ~8$l%kn`wL=6dpUU$arB4=0K`|2S)b?~`Y^My z)-X|r_N{u(e?QOm0NrLLZeR?1q*3ofr!Ojtu!0d`GQ&L*m?NH_Jex(N2GnW*EuB_7 z?Nz_T#&9z;6&2;AMLKnWnphN#X#_ zY6qU>>nw{I33VI_<}@oGSV~b>DYPqn6$>;mNtjs18+dZGyJ%`03{}=D$J^)%Jy$S} ziH$>4H}YL*U5Cpq`sQe#Ik87Ci*7c1Ah+LgUe4R;FHZ^LsIv-k7?q}mAXc%GsJFhI z5M^ZI4W@i2n%jUS(bnay@V`=%fC!Qx=ut{nN`(;c*fBBXk@Q0^fKqSUd(x5 z$tMmn;^UF0xO+k$kgI{iH??BhMr12Sm3(yIW%5jq<{%%DI3bV1<+Ils`=)qulih-N zsjM*%rF|`fV~H-OA(dIb0+VIZ^*{BtbIOgium`h6GrjwbzCszD0?B4pjvT=zus*1r z#legEz8`81H1LD|5WvSu43~;^7fg>t$3i^2u#ZJaI)9^${vA!n5Sdycp}VYp%|`hN zRh}<4Di*B%E=KqvP`I@n45uU}`NOC;+IsHnSK@^Zq7X^plu1YV$u8lX0)idEd9Gccp3kkXIk^CD`voM z4X)F-K{mNxkyLu$7Pcrf62q+)I8ER2TF^n?(nKE{6y?mP$A=-lxAR{V#r|k_k*z}U zfrIuno`1}&f$#$P<^FZp0($Y1lG!K^?W&&oVuvt>%kp}rUSz4C|J&bzdk z`gKrWi(krk%rh}BlX%u-C&{_gZO6zb1gOE(rTg)}G_Mi9> z%vn1S_Z0y|^ZHBHmqbJ9g8k0xgBH2gp=QRJaiAY%#7~bsYfUi4j4%}p>gE?Wu`{Z~ zg3lj3c=&!I`G{F#FJrvcaPgX%6%Jg%V13%Z=KXRvf}JF-%Q*IlBikeKVC!@z#UmXQh6*O2-c{8P))Ijm`A@Sq^ zq`gB0SEHGjm0cTtgp(dHzB(lL4kp#1kuh~Gd9SfE3Nk^f5#Xp5J;h5JSY zRA%MiLFi(Nm8EIx(YT;k$eLp?Iw31eBobWaTsf(8^{&cJ5LVKKUjM=-yrc~+CQ2n~ z@+hv%i6Onnq)+%Avt%EyW#PQCPQdHx3IT|lCYPNNE513Y(3?bTMn%d{5fj7+qOl#q zZ-JW2T;j3O#IRcPE z2-w}p&z z(evOMa3n_)gN{9P;FbijO{q#2>!Z)r@<>RY_74T7G$g8iUP$MMlO?lr^{B;-x}ZoE zt;7`XWu!v9xCG5(1|c7OQNEZYsocq<_GELI#-nmSBwJ)ySd$XiGpEFWDg9lj86EVN zRN_d*!N4HUxdR70L9Y5z2dW5z0Ixa-qciH?OoqS6)^>3-=ctXB@W|H%5HjHfT)6}s zcp(pgB{2w!E{;jH>ZTPcLAhb7w@lhRqkv4yim9JMjhddZs>c^TxgDIzs}kk*{c`&4 zqE*4Jo>{fp<`v69K(D?=yZ7Xdt{wp2Du9mz7AC$b{v6w-bE>6ttEH0{HvHgBK_$0T zdVkLe7ZmH44L-F%Ogbq?XHq9LiBcg&*ZzeT&A5Yhs=L#_#lOR+<=ftCV|1f z+K00cJfZt%NL4crTv>7ac6ppE3ZgYbdRE}mlQ>MR(JgoZjM8F#92YxQa<@nw(A9;S zrLvuBn-bpx)yE<2YPo3gTNdN786c|iK8)__jAO+3kiq3Keh1kD$w{KrYzQL!!pYbF zVoLi-Q5?6y9?1!+iWzI#u)KVj{{mf}D?K+Y#H}Jm(}7`|J28^9vdU$*p(4pyC+BL9 zWUO4L09%pRdY65M0Q;cZv1;h)WQX*MlwFC;*M&b4B9ry)WXJ6TeAi-!__OQLf`1O= ztMb|2lXQVh-vK`K5S;{y-EzCY()kVFB0UpngV7;2PVYJ1;8^(|+mgLOUHhq$r@k^> z1ux(J>hS&Dr|}Hi5oz7){*3qr)*bjnLGDQmy{n_g=s{h!i_Cz}iwd*5orlSbOmov} z68IScvjcN$&rVGnOgCtGlh^{&h4$3Pwuktv!VR?@TD%+Lf-dj|C;x^Vn3t2cBB#t8 zp$eWagXs@7#=ejM#V*c)7@r7@Q;`wyvk+}Qk#j;WNxx_=Kq&DsrLaM-sqTBU;#BYM z7#dElW*AW-m2E;D?l8GCu&WLRCV`e(DXZ6Z3U<%yMY9uGwd84!;tY>l+4HArDP5k_I|lY*|B8Xcu+cc=0|45V$N0Mo zD~YFTfyUcSDA_4le|Z89S*!<&OQc>ma&vm;`3dlWv*UZ}zvDv^DgcPVOjXDmJ9rxO_)Lt-=056F;)Hxo2U3Nv>}FvI2of&R9tWORX;+9R6r!x z&oIjyHucE?;{oLx=QM0d%`B?kY)GR++pPD z$Wqe~M^ryzE>rA;pE84JkMA2^cFx%`$8}5d0GPW%t4LfaIw13;4jje-t?Z3)IvZwO?Jkvzn-^M zxnX(KK@@WCV=RDk>WY*ll#&56y#GRv+RRU({f%i9-|@KbkQDVFWkjnf`*98R!k#td z{X=Mltk>Y99c?c#?ACF;d!sx&f|TLFibu)q$Tp+ZSDuM}J+w0JoC&u3|nD=R;`^k%OQwGxdFt z65)c3v-Gq4yU3iwSVyBoOGT1_dyqy0EeYWdzeaN_VQ6^);_`ot+a(uHXp$YQZOa(X zJUIOCGO&FYXm-kqZmFxZ(_ZE?FpE7TGgzHMuywq%4)0S z3ryk4S@tE!hS1pxdba8zFv)1c9D7zipuuHTX|vyjaBSH|1r#X`>4$u^aK--Y(<&Mi z9#njDjscfbh;kO>4(vDR2$nw7wgYPqS>mCG;@TD7kTSy#lDzP9O(3VqvuQ=ak7<}D zwWt?wRTPh*yrqxUL`7F8jkQrUjY{n3JrMLS$dsaiVUVE|V6>eBk_&Je$&G97eCyFn zX~L(~Amx|=OP=1BwcPd9b2|dypar+V)j{JXg_78UO|dWed)~l&mudpk?2ojAi75x) z5Qq;117UduiFu?U@fjN(U@Kt>1O_Ce!g4q#5dee{LS#mwsPzx2xy;D7vD%#m{%XR) zO%ekT3voYs%k&ZCi;#CC;_s*_Ywc*=!Ma<21-JVqQ1#m+;*dQRJ@BIxx*7!Xb4U~N ziX?padYsG?cNmWZ^TO2Ma-@XQ#q4DHDc+LI7e@taVj@-!1c#F1tE74W4DWKQ(DBc@ zg5mpb*z?70h#Dt?CE6IDh(B5&Z(rVYiRa)2HqdkfLg za1wTy>p@y4l=`-@h#nA~CrA&JcQW64#!W*IXTwX-;`3vE@JJ%#l8&K%RS{?y1%y{Y zFsKQ**l37pp(Lj*uCQ7yjpn&+n3*P6Y@0 zox_lKg{9f4cN;U9S$Pa*mit_!Dbm7ZOhmiCSe1-N>ykUtZ3`r&9UXxh3 z{XzN^ay#8H45;}*aubC7&^v}1YCB-&2wUf<$=oXPjc}Z*|LXpjd4OBBL}&7+WQvs# zbu2jho!<5JlNm9$?*MWitfM5YB>H4&9J26Z&?9npxu9(AO0stm#Jv}8~JN? zcz)Olzx)?`VF>qt7|co`p_xw#hCfu1DYBH=8@pB!`=nFzs+Gt){s%%{Y)R;CJ>Qs% zvAPB!1@@zf^QSCsGcM0B=J*LU(AAhg7WEMa&?Xd!bRKo$t*EPbFzzkmW=kWF7bk#9*mN}cm|)4);GUOJX8Q^ik>thFac#KrBB} zwn1whWuuh8*ko}9v@R=9ppk)>GfMA2ebJ{#mdG9jBNZ6J?F&)+-%gER%@oYcUL%Rr zb00dJLfZ#`0>8x6i6_G4P&-cr=ao4gwFd)Y_w)ghy{^BFup1;fhPV4U7}gFLY#%I=I+c2Hze@ ztWk57cD+Vuc8?$UJs@-)_Xg;P1>(wKY-P{nVe{=#h9RN0w>g&_JUp8`##0d=&-cq> z-=+wfZg7g+7$U>bXm{&;o#8r+3BoCmk7*2Ux&oOxJA=unZqWKt(5dA>87dqK zSKq8_W-3l~2P$43|H7KHzfx8QP+Lw$d&#o={2<>?x`7Xst)UGx-$Z`|^L+~zCOZq{qo$NgF*6^xhWER=~hr+wK1 ziE_%g-88C+hhiAc+G8$a`5#Pk>3t_#JW}~wmVo8%$}^RVK!Noh%RRf5&@#87cqZ=m zg!I|V_)Ug}-plpPZnFa2_;(?X1<^pD)L*P9Bw* z6{3sGLu2|W2wXPci0a6x9rnaqKQB6r<6Uy~W^W;o%$wlIkaA4;dn_zc+MQuWzS)zv zEX_v$3Rsf3Q^1hR6AE12L>(M;16dz$Ba~9Cl+%Om59C?X?jx7IZ9SIRUH1-2dJ5Y{ z5791GZdhe{%4Ll^MSp2D@#XDWXVu;tk@de})Xr5_vh_uj&bgu=DzLWSbAE{Y^b^uJ zSxtF{OD)|HGEluDVlzC56d=l8_j~!)sZo;@`U<+P^%k+yujL_Z<;$+_Q#$KWaIvqGuOm5Lq-qeQ0y`Ovh4TdeShG#YD)2%s&Iv34>pV#tORXcg~DiX_W+izUW0YJ&9BeC&k! zGp@ek1$pXT+^*sgO{@u&?Wqd2kZwh9Q&8MqN0pfsvFC;IEL$Kb zbL@`FIn8;gVqBM~_1m$i+X>9h@$&pRm%pv4mM+E>1t-R#w$yVG3$BfFgNfkB_+(Gw zR+jS0Rz_MY`PZ#!qv5Q{ab@PFGSC*?Y2V`?{*toAK`wH$1JWt@hkCON`Dx z5gI#N9j9tuF<+96_awlt?fEe-lTqKlfigzEccy9VsabKi#ZK@Sho??rRvn=$}s?nH`pZW8{XOqx6ZTWF@r?!7B93U8lk!4T8#PsaqInS zyEOt#T{M@~yrPY=Th(bslM+^H^~CN>>O02Nsk!PFN@@=loH_$yjHn|oY(<&ICPqS$ z6~xmN(1d(`RJI2+kl3z+hA2oynVV$LI;|<>1fr!Wkjv%J6gCv{i>DV{J71-8<6 zS>c;^Xm{LVvf(4D+Iu7ShbW#-;; zFokhHR)f8v@&wq~c61Jpt--`UoW5=Mc87{@CoXo6S8vDTfAj}mzuthTeau9Eeiak7}rV@M{2oH#4|NY>t`mJf9Z)ZGpbC4$UHRMpQ=-7>MaK9 z3%%6%X5qPEQ9{KHtQB;wv=D0ir zG8|}HoXzHYik{4AtBv%W<>||)Fb8w4?WigS%`_1ka}RHoj+2AVVF)afFsV>H;!Yr| z{O82gg+c1{nGiL*W-@6!bc8sXXSsx@jXsRjBU`27lF6{v3W~?eKkMU7c@V>ghQ)~D zVYNSnmB`pZo@YM8HPverCT&N6iwhns#*N)Yaf(r`RC=bkDImAvoR;I;NznsiXve)# z?_|Y!gEv;wXSCQDX5qMrf?fyg;S~u5;?%KQ=`^WezKIYQ9EhR6#V+r@3|pP5L);7J zAqt*liY=BC*LWR%36>f#KB3}pq2ac_`b?IYLqYMNgfqbv>YAGRBTqH=9hv*(It>%9 z!LY_!(dgU39X~N7DLU2N0raR*Gl^YS8XD?uO_KDGJrpNqq%#Z!2TzFiR#3%(`^v{A zUR;mHaZYno7FvFZ3Z%mBR?D&stpWs9-~9+;r)tRMNkZEJZuA6B*s3dbG)SjAi@VI6 zxY6r@5uMJG=PkrU9P?pf{S*}}0+>=x2IpS4niIJc3BuR<>|7ARPq15TXqjUJaZrS+ zC_n0mH_dT49pEfWO4!5l+=upJU$2dejx1*veR0?{W250%t4Wrpu5T*9i^f&a?M~pv z7ZeH?IWfj@u*Xmr6=XVC+~$bQ?C*Q}tdulM`8hcULC1+rDOh@7+qm2mfWyj*UkYI3sv+c z`Ve^+D1~Vv#`W?4Xt*H%QE$QJ%a_Ye2mTo(E#8~mAl5I%wuuhx_hZjhc~#0Uv{Evs z-Uti{jCUDPFuw&&---L%1o?(gTr6_zDz#aV2KxCOhA%keH;aZDF0a9^L`JPr3e{qs zI?WsR*4V`Oi+Re87zq|7bpA-6f8DZMjx8KfeQmp~*uFnnRv*++n2@=(wFkZTG}a3& zdo~*0Fi!;+cj=l^`)@~p8IP<+nPQ`c8Aw*mf@sxE!W!|>yH6mze_^essbVXJv4XJs zfuX^j#uLr#mgaZ+KkY`E`h+&udXRp6C6WB5L4jW+1Z z(_VY0Vdzw{c#9t^7MjQ#jZs7Uv&@J&ztuKdRJI>kzVN;k|w+Cnlc$ z1)4Hes(u#-LPp%_3+F5SublbyjqV?*1@SnD}}x>LeraZO(_1?K+7MlbvXh)KWZFO2F z*P=MT)o0x#B4gaYXx*gImEIEko;I!jcIF>WmBa1v{p^M#fg56(qs5q6TpC89@0c_J z$P{I7xVmo3w&ww}P~f{JLB5I&*^B~l>W9D1EMD0;>oX$hpoB#W+yRHvG*zifB8=%w zD5it2Tkxe`JgO&%2ftHo9UgSnc*fxdE>m~xQf^bf0a>I>q ziYEf<`i#oqI;o9kDASN&jp0Gmj@l%_?WU5Iqi)L_LZ~9W^Dw$fptqcU zE{;=_k&iyTVKJ0HAp7BGY+_Nzq74s;P%8wSEx_KOk}t@i<2tj3KP8j zkP6eNcOR)q2gbP-(#u`f)dOtPB!*2&xhh(hU8ZugC~t>*48c#qnCV%n;~xkLl`U9; z&*6_iHAic{?{kl&z%sB`6@x6#OyC-+3I&@Fc)pVJ9wen>Lthh-zRm0(aJ+Nszz;RaA2p^p*ouDQi@PIfl4Dx zN&bs=)AyZs*v?@Lz36%d46pVlVkm3yb##FH`)oef^XWJ6r0 z-s{uR_cXoU5Qo<-1*li-(T`zz@V1+#@tkm``r~RAza+GS96*+_E$2jqC>zZOiUMnO zpCZB|LeW=nc|*0HQK+5=JMx@^^A~ZSao?(F-LCBlc#H}X!|GPiEbD>lQs0|2`xsqK z=k&u32Ez|N)EaIw_t(yBEFD|A(ra65j@<2SoF+d7XVX6Mia9Nq;+4ofQi?a7ZQIDH zxpfX+rCjL=AdWi2dn3?&Ul_M_D4_3oAxMolXVh)wMQna*Xm!HnEkVdz_N`50cnOy> zReO^S_xxE`P3@+u)%J`;^9Y@NbsY917E$#{sMfX14P4iIedLtn;R7E5CWm)| zRPQAri6Ab}ztj8a>V!k$+b01$cHen|HKuc&8zR#l8|$>KjBn5R{Qu6vthlDITpu;r zW4Ls!(;RR<-7__0W*cf5X}eaFsS|ZJZ^aTz8~P-_oKsmi^A!QT{J^0bBq^>u5(ht6 zMl~%INv2j5Mwz_%O|{+{6GZN!8-m+7OnCDnS1rS`vonm?rIJl5vslzSSD(8y8+Ki` zx#lR>P#z2q1^8X%i#%KfCX$GS0fIbUjDM6Sx zmN3oahn&#_bo)hZ>oh~<_VaFzRt(#)z$C@j#)dR1V-nN=jX(qiG-~B2NoC(dGfXUH znx!%`=29#R`Egt-7Wt9rS=KqxW=`LxMM&*Yt}(RzV zi(BV5xAE+9!oM$GR^G1>HSRM;JEe+g9@T0YO^*ZHCh|`E!K%#j1|}UgIVCs4C>NvL z8nbBELu$^1%h!DHWNGsTwSz2Ci*anDg71>dj$Kv*Eu1AaA2~Vya3)={Bqb(F+tPNr z0pNSohSyrAPf^-Nyv)z}%;!3~55Zuwhm49IvBlPD^#eEF)F&IG8O*nz*VJkCTz)n? zwGS#GHMSe~-6M~vxKeqPg`Zgo1)dE(mIceN1^;Zs)hf$==|EmzoRn=)$8kS6JJK74 zwu5Ca$XvZ9U;C=*@4GP)Ef=WMFTs8F7>Gbo7=8PgT8;+s!}o&LSx>C&UF#psWYr8WZ+I zyg;(>YRD)~_8@}DJz~K_k*y*I@Bs2xUilh>EqC&VFZvUM{ZuRbboKT!-}ejM`YAK@DujxSyV=-br2~WKx&pF+kO!wZj+SsMkCc zuo_obh(PF4N+&E!`r=n;ggA=75RXbN`&mYo1L59irs)3SdGn8_Ebw^nMmsI4U(Bfj~Q4${pvYM%* z0TSC%Lp?>Ynh<6pmYIvpO6yZMNk}_8vIi9-&n6kp_Lh8w8$mSfMjCztS%*L@e(Njr zFE&_>Xr|t*m1Jzyobijli0fmM1V<}NcP(MAf5J2Pj1q{vfwP3})wh2(QK9+Rskidn zF+K0UUhlOYKw)0q6_coRO%(<I&Yz%3jo}q=PsfEM8`a*%xwa|U^2tl9oO|xw+&p*R-ZJeAb*&XcL695W& z8gt|)`5e#RcoR|x^V0iq@%fy$ZKIDbf9ght_K53J#~|rhX}A+i9H|W&CncRL9Brkx zZBYLSV78X6n$gf_0jAZ?vDPeGEk+|JiP$GtMCuLR^<-AJ6(walkp`$<9!yIjcvxR3 z6~g?E`2>A|*p3U}#|Xd*(jg}l4sjPm`ulAU>>mrjla+j#_x1CRzCQmm%k{H+nDbVfjX2QzzXD>{2aJJYX+ZiaSr|FHn}#KML~Kt~IQ|7$rim8Sne)_xSV zUnbQ-qxELb5$dJ5&R^Yq1xRdc*K^sPytrc@{Mw4dwTtH74$r1vD5I#NC|hIo@4~!* z3@U9p4oB|<5eukpA(BwEfS^rY=YCjlEm+w1jAle_9-zG*ZJER+$aZH$Bk*6n^F*|g z#1jdd6kUh=X+g^q%(by~D8CW;V>=4;yX18z_1DG%3;V3YT<Pwx`UO1(`LCl(v_-6!-+YVeIm} zeV8HjJa-DsW>iB9$_74DR;Fs~`jijAp_jg)Kk#H@FK92z?koppMXH3AJwlYDyU%%* z{VLISLkSqLxY{_!1S>_DzxBZT!{rxz#Vf=zNk{TE5aUtXo-aQ#Zy^T~n=8VT>pNP) zN*pKuRCL1i^?GNJ8|$I+32u~`qtpz}7xMx=Id`U$W4g41(YA(5v@Z8&XeP^?++Ef@ z;)0{jTXv^JpJ_YicGoKP(HxDZlGFHXz_ifPgX zilxY3tg2}7r?|Qk{G7WiT@|Qv4ACPZ*muabKN5(%0`gFX&-6>#NE!KU0@VvWlvX*Z za%OXsq)ni22@{;I8wCrP8-QM{B((V24!UO7m zdq@8h;3H!vCHwgihCk=w^Gz4m${x=NLmGH6KoQ@1{80rU-AYgf=1Hv_$60-em2crZ zuz65XJig&ee(A#UI2_=a+UuFRU#>4+hsUvfWGYfd^tbpIg~4IK(cq{N$HeN0{&Lm@ z5M!?^I5XfLZ; zP?ONM8W+QU9BN%JiV4Bt_jkNi#yP{57u2+*cYIzMHNAQ5NYWZ?2Igi(wiJZeN+URl zy7#Sp#t!)y_Wd>aCU`1#k~*JgC0tS*NodJwSwT3ggIi5ao;0;|5I>l;a*Pc!!(`1! zUV|&;r;M_S77Ud#fbuFPo&TWRF*x%);G_tN zIYH|5Qxu70>zIA}4fG%Q#EfjZ?C=H9t6%@<|BO%nPVZjY)c*e>-Wye4?KPEgKQkux zjhufwv6-?R`GJ{%Sfv$6;e)RxslZCCGRsh!#`99!)VE4lwXAMlZC%0$fUZ%Jhp3Pg z`ly(qph}xILn*7{is207UqmoPG5v*L-+vB|Vw%c%=5Tfv!mE@>Wk}6<<=MXX?4BC6 z{<#0c^=)bIn3s9KUi)I$UzQW5#k%*VJDGyVz zrC_ZHMR_e6WTb?o+$Kuh&Wxjk8CPKj2q)$-W4jQX=&}%v<{>-kBAv-oW#G{T{oCZ( zkJC#ble=)&6d%h*+A^yAFd%sxoyB7`Sk%yqC~NknB8Uz~No}-3pBTBQCsQkQ0kBY0 z`CY#!|`4XuZ)8;ay2;sFRvnv9nRTe2!gwVnfE{55w3YUf;W>g-Gw^!Had8E9TOKfLC)Xv^G>RXl;=;Jwo=7pcpozODQyxR>{cWC6O@B`k$ znHVA9(&fl-Wmbk}^MA0^kE~k|r7OqPDgD}cw!S)@~p8g?OPVn4s)6&pJF&ph%y)VT1}5 z0G)JzGwyUA=AfRJ5at7Iaa{qZ&``1@xUtWZXKOwy`O0q|KQ`8#KbtJjVVy4cv9#>P z4lKf@N}MozhTdhjP`b^xGq8tyJy0hF0%^!Rf?5 zbIO`M2Mw^@(uAVl`VFwl&fd17iF;ttLAm_7OlFTvCgJsOC(-ULN7`25F22PLWdcy$ zYI*$bH%BsNAh`8>WC*qYLBpnWG#1!QJhks9Om(H;q+q2@ zgU&GMKx)ql({bb)Rg4tR(c~W2E{C}jqQ*bZQzwNt{tk||~+qTUW+jdrL+qP}z7u)8F zZ6_!Hb6@t^RrlOobzi!wtKYijte#`^_d)}nxH5)kq!RvYF@I8 z@n$`)Je;GdJZKJvTtu-lx@j&u&FiOrs1$`^;syi{?97x6Gyq#Z74oN`YUz;hR(y$|mA z_H%1OICED`f;>zdbw_lQ{`A`c&LR;KfAeU}G|!9H^nukh2>g|AyI?(wAVN)_XcZb{ zYFK2+4n4;C>8!P@RErS8PCiCu)I+wlvMHpwM0BLi0~CQflG%Z_o+@6~OBt)6S*dct3S4j5?TOVK;C3Vk zN=rmcLjiudh9}abqXfK0-vkeT!f5Gt4nfBrjTTA+ zVeG~=RJr~_M27;Nn}V@A9)4-H=+JLZ=Sv=ailMFpurxK?3Xu>4Rg0N*8Q=QN zyRK@M(2+K@CD0eAWYahT!#}Y<$5ix%Qe(dmfviiAi2q&-a&T zBUUFv*sH_hi>@EQTWt||A3)qU=smYz5qclEoj3AQ@93@vJ5B2PwKw}|5Qe!BdxDCw zp(4n5Xw?iuVlZKBY5Tq~;d)VF-597~c!=Qok%Jh>@n84?c#OX@rL`%O3mcIcivS@gVVmU7|MR z^j_@u$-%_K2cCqbw%~Uwfyi1ioo*OfwlU3>Xxx(=D#y^&i&1^EVK&(l~aColyN&WU`!iGlA9ZIPRe!j{j7fq2%!XitEh4S?AA=o9lUFm z&bNGf?uM~K+5K<*lMy-Ua=&tu6FM*T>~nliuGwLe5qt4Pdb(_767PqB6WdjL*pgko zq$~xamC)9>OD%Wv)93#V>=`E|G>`oxaD@I3D9nF9_|gAQ4t{pFX8#38*!Miw_ELJ*< zyd{OVR6;OPrLt2y^!sNQl$DdM8%B_-a%kY$A)m{4%)OK4Gfg|5^Hr^sPBO^S_R3-$uc|G~JuzzGwy7rBYly)Pkplu@iBZf7 zrIINxC*;9R+4Dg($qMC|eC<^x+1QlCa8zA4_=iWhq?GD%htGx@ z%?{p@N7z`WL2bSLJH<#5t+z80yGtbZ;)#xA;+3jQ!Sv_BI|wuo%1 zEc&HUSrLO0<84*@P$kAXs*KUC67M^?5&&s;Z2)&$2~jL1>dGyhf13JP125mOF6#B3 z59(AK;Q1Ps9EfNu#Z(>Y`=3;O^?zXb5m7yx?-xPOQHj}N8BB&Z|~`prds|z{sf%E ziot5{TZ}4;Sb9};{gmRXF6EmUN86oh!)kdOLYYQKWXaHTu{6~jEgO{&`(;Yb6qxMh zr0eQF9`YVv*Q)J2Qs}o(*d%az$sdIj7sb|QI}yMXex0mLPRA>+VZB1LGxk#btdGi82okioGIQ88$a;t)7B++iFqEVvgj`nM zg#fgi0si#fU-&1MQ$RdPIhdvZw0`6o-W#aNmo@%VqAsIXj_LWw8W2vA{m=!1UY`9R z2Eu3Vb~Op?+!&ju-cY>I&4o9DcTT{cTo`~5yn?JMLd_5SP=P!J8R)NEDIi4pG_H29_w#bxzq(Y98#_r#T$BM{Yw_SQteWZ&yykq zd4)dR2QP0V9LlJ87U>|p!qz;ej2>Sc#Rj4iYr=|{1k!M8mH7p^hOY#g1FMT?iWe6dSWEo<9a{!1N=Sw&E_**`!64FH>6_DzprKJR!tD2RcN6iilWOtE(=1?9=A4Y?jKuW@qjk zBhx_Q$NwZ|zJ5OX?79Bj_Ue7!HeJbw&HNQ*K+dHQeh8GW#g{X0G1R2 zhZS^|5Ns*c0{uOk^=NICjBeZO?aMeyL_vmhibNwTmn3vOSyTGi=n$ZWMwlT$iA|Zp zO!mYE#>%0BnlhGCJZZUftKpGGv{7vp0-06UrK}PuhmU_7!YqT%31p?j2NYK1DyeHP z$oehmxb$e@s0dF1njf8-Lteg*MVd

        LW^9MVsoAH}%BP*@PwimKJUYJCJ;P^dc}X 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 Mp;QTWZfil1y&h?# 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�#EVF;OsTja(GCJ+4Z?T{aK|^YU~{F2iSzN> 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.version} - - org.bitbucket.mstrobel - procyon-core - - - org.bitbucket.mstrobel - procyon-expressions - - - org.bitbucket.mstrobel - procyon-reflection - - - org.bitbucket.mstrobel - procyon-compilertools - + org.springframework.boot diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java index 4421dde1..4ff94632 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java @@ -3,11 +3,8 @@ package com.jd.blockchain.gateway.service; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider; -import com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.gateway.PeerService; -import com.jd.blockchain.gateway.decompiler.utils.DecompilerUtils; import com.jd.blockchain.ledger.ContractInfo; import com.jd.blockchain.ledger.LedgerMetadata; import com.jd.blockchain.ledger.ParticipantNode; @@ -15,6 +12,7 @@ import com.jd.blockchain.sdk.ContractSettings; import com.jd.blockchain.sdk.LedgerInitSettings; import com.jd.blockchain.utils.QueryUtil; import com.jd.blockchain.utils.codec.HexUtils; +import com.jd.blockchain.utils.decompiler.utils.DecompilerUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Arrays; diff --git a/source/utils/utils-common/pom.xml b/source/utils/utils-common/pom.xml index d0aea4d7..c897ad4d 100644 --- a/source/utils/utils-common/pom.xml +++ b/source/utils/utils-common/pom.xml @@ -43,6 +43,23 @@ spring-beans + + org.bitbucket.mstrobel + procyon-core + + + org.bitbucket.mstrobel + procyon-expressions + + + org.bitbucket.mstrobel + procyon-reflection + + + org.bitbucket.mstrobel + procyon-compilertools + + diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/decompiler/loads/BytesTypeLoader.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/decompiler/loads/BytesTypeLoader.java similarity index 99% rename from source/gateway/src/main/java/com/jd/blockchain/gateway/decompiler/loads/BytesTypeLoader.java rename to source/utils/utils-common/src/main/java/com/jd/blockchain/utils/decompiler/loads/BytesTypeLoader.java index 8caf66f0..379aab08 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/decompiler/loads/BytesTypeLoader.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/decompiler/loads/BytesTypeLoader.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.gateway.decompiler.loads; +package com.jd.blockchain.utils.decompiler.loads; import com.strobel.assembler.ir.ConstantPool; import com.strobel.assembler.metadata.Buffer; diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/decompiler/utils/DecompilerUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/decompiler/utils/DecompilerUtils.java similarity index 98% rename from source/gateway/src/main/java/com/jd/blockchain/gateway/decompiler/utils/DecompilerUtils.java rename to source/utils/utils-common/src/main/java/com/jd/blockchain/utils/decompiler/utils/DecompilerUtils.java index 0a66da9f..6d7729b4 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/decompiler/utils/DecompilerUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/decompiler/utils/DecompilerUtils.java @@ -1,6 +1,6 @@ -package com.jd.blockchain.gateway.decompiler.utils; +package com.jd.blockchain.utils.decompiler.utils; -import com.jd.blockchain.gateway.decompiler.loads.BytesTypeLoader; +import com.jd.blockchain.utils.decompiler.loads.BytesTypeLoader; import com.strobel.assembler.metadata.JarTypeLoader; import com.strobel.decompiler.Decompiler; import com.strobel.decompiler.DecompilerSettings; diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java index 08ccdbff..a59512db 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java @@ -15,7 +15,7 @@ import java.util.jar.JarOutputStream; public class ContractJarUtils { - private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; + private static final String JDCHAIN_META = "META-INF/CONTRACT.MF"; private static final int JDCHAIN_HASH_LENGTH = 69; From f08f6cc9cfe2cb5c39bedf5e13a8fe324a1d86c9 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 2 Jul 2019 19:24:29 +0800 Subject: [PATCH 004/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E6=89=93=E5=8C=85=E9=AA=8C=E8=AF=81=E5=8A=9F=E8=83=BD=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/contract/contract-maven-plugin/pom.xml | 46 +++++++++---------- .../com/jd/blockchain/ContractCheckMojo.java | 20 ++++---- .../com/jd/blockchain/ContractVerifyMojo.java | 46 ++++++++----------- .../src/main/resources/config.properties | 6 +-- .../blockchain/ledger/ContractVerifyTest.java | 2 +- .../GatewayInterceptServiceHandler.java | 12 ++--- .../gateway/web/TxProcessingController.java | 7 +++ .../ContractCodeDeployOperationHandle.java | 8 +++- .../BlockchainOperationFactory.java | 2 - .../utils/jar/ContractJarUtils.java | 21 +++++---- .../test/my/utils/ContractJarUtilsTest.java | 4 +- 11 files changed, 87 insertions(+), 87 deletions(-) diff --git a/source/contract/contract-maven-plugin/pom.xml b/source/contract/contract-maven-plugin/pom.xml index 7ba6a4f4..63f0e64c 100644 --- a/source/contract/contract-maven-plugin/pom.xml +++ b/source/contract/contract-maven-plugin/pom.xml @@ -103,27 +103,27 @@ maven-plugin-plugin 3.5 - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - UTF-8 - false - true - false - false - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - + + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java index 1403e079..a57357e0 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java @@ -1,6 +1,5 @@ package com.jd.blockchain; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; import org.apache.commons.io.FileUtils; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; @@ -8,6 +7,7 @@ import org.apache.maven.model.PluginExecution; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.model.io.xpp3.MavenXpp3Writer; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; @@ -22,11 +22,12 @@ import java.util.Collections; import java.util.List; -@Mojo(name = "Contract.Check") +@Mojo(name = "contractCheck") public class ContractCheckMojo extends AbstractMojo { + Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); - public static final String CONTRACT_VERIFY = "Contract.Verify"; + public static final String CONTRACT_VERIFY = "contractVerify"; private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; @@ -36,11 +37,11 @@ public class ContractCheckMojo extends AbstractMojo { private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; - private static final String GOALS_VERIFY = "verify"; + private static final String GOALS_VERIFY = "package"; private static final String GOALS_PACKAGE = "package"; - private static final String OUT_POM_XML = "OutPom.xml"; + private static final String OUT_POM_XML = "ContractPom.xml"; @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; @@ -51,7 +52,6 @@ public class ContractCheckMojo extends AbstractMojo { @Parameter private String finalName; - /** * mainClass; */ @@ -73,11 +73,11 @@ public class ContractCheckMojo extends AbstractMojo { * first compile the class, then parse it; */ @Override - public void execute() { + public void execute() throws MojoFailureException { compileFiles(); } - private void compileFiles(){ + private void compileFiles() throws MojoFailureException { try (FileInputStream fis = new FileInputStream(project.getFile())) { MavenXpp3Reader reader = new MavenXpp3Reader(); @@ -107,7 +107,7 @@ public class ContractCheckMojo extends AbstractMojo { } catch (Exception e) { LOG.error(e.getMessage()); - throw new IllegalStateException(e); + throw new MojoFailureException(e.getMessage()); } } @@ -185,7 +185,7 @@ public class ContractCheckMojo extends AbstractMojo { PluginExecution pluginExecution = new PluginExecution(); pluginExecution.setId(id); pluginExecution.setPhase(phase); - List goals = new ArrayList<>(); + List goals = new ArrayList<>(); goals.add(goal); pluginExecution.setGoals(goals); List pluginExecutions = new ArrayList<>(); diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java index 28d5a1fb..950eb9bc 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java @@ -3,37 +3,25 @@ package com.jd.blockchain; import com.github.javaparser.JavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.PackageDeclaration; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.jd.blockchain.contract.ContractType; -import com.jd.blockchain.utils.IllegalDataException; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.util.ResourceUtils; import java.io.*; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.*; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.stream.Collectors; import static com.jd.blockchain.ContractCheckMojo.CONTRACT_VERIFY; import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; @@ -78,12 +66,15 @@ public class ContractVerifyMojo extends AbstractMojo { private static final String BLACK_NAME_LIST = "black.name.list"; @Override - public void execute() throws MojoFailureException { + public void execute() throws MojoExecutionException { try { File jarFile = copyAndManage(); + // 首先校验MainClass + verifyMainClass(jarFile); + Properties config = loadConfig(); List blackNameList = blackNameList(config); @@ -193,23 +184,25 @@ public class ContractVerifyMojo extends AbstractMojo { if (!isOK) { throw new IllegalStateException("There are many Illegal information, please check !!!"); } - - // 加载main-class,开始校验类型 - URL jarURL = jarFile.toURI().toURL(); - ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); - Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); - String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); - Class mainClass = classLoader.loadClass(contractMainClass); - ContractType.resolve(mainClass); } else { throw new IllegalStateException("There is none class !!!"); } } catch (Exception e) { LOG.error(e.getMessage()); - throw new MojoFailureException(e.getMessage()); + throw new MojoExecutionException(e.getMessage()); } } + private void verifyMainClass(File jarFile) throws Exception { + // 加载main-class,开始校验类型 + URL jarURL = jarFile.toURI().toURL(); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); + Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); + String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); + Class mainClass = classLoader.loadClass(contractMainClass); + ContractType.resolve(mainClass); + } + private List blackNameList(Properties config) { return blackList(config, BLACK_NAME_LIST); } @@ -320,14 +313,14 @@ public class ContractVerifyMojo extends AbstractMojo { // 首先进行Copy处理 copy(srcJar, dstJar); - byte[] txtBytes = jdChainTxt(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); String finalJarPath = project.getBuild().getDirectory() + File.separator + finalName + "-jdchain.jar"; File finalJar = new File(finalJarPath); - copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); + copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); // 删除临时文件 FileUtils.forceDelete(dstJar); @@ -423,8 +416,9 @@ public class ContractVerifyMojo extends AbstractMojo { if (totalPackage.endsWith("*")) { this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); this.isTotal = true; + } else { + this.packageName = totalPackage; } - this.packageName = totalPackage; } public String getPackageName() { diff --git a/source/contract/contract-maven-plugin/src/main/resources/config.properties b/source/contract/contract-maven-plugin/src/main/resources/config.properties index 4a176409..b54de2fb 100644 --- a/source/contract/contract-maven-plugin/src/main/resources/config.properties +++ b/source/contract/contract-maven-plugin/src/main/resources/config.properties @@ -1,8 +1,8 @@ -#black.name.list:打包为合约Jar后,每个Class文件不允许使用的名称,默认不允许使用com.jd.blockchain.* -black.name.list=com.jd.blockchain.* +#black.name.list:打包为合约Jar后,每个Class文件不允许使用或包含的名称,默认不允许使用com.jd.blockchain.*及一些内部已经引用的包 +black.name.list=com.jd.blockchain.*, com.alibaba.fastjson.*, org.apache.commons.io.*, org.apache.commons.codec.*, io.netty.* #black.package.list:打包为合约中的每个Class都不允许使用的包列表,某个包下面的所有包通过.*表示 -black.package.list=java.io.*, java.net.*, org.apache.commons.io.* +black.package.list=java.io.*, java.nio.*, java.net.*, org.apache.commons.io.* #black.class.list:打包为合约中的每个Class都不允许使用的类列表 black.class.list=java.util.Random, com.jd.blockchain.ledger.BlockchainKeyGenerator \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java index dc5d2541..d57ab5d0 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java @@ -18,7 +18,7 @@ public class ContractVerifyTest { @Before public void testInit() { project = mavenProjectInit(); - finalName = "complex.jar"; + finalName = "complex"; } @Test diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java index b70bee5d..2f3d215e 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java @@ -5,6 +5,7 @@ import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.utils.IllegalDataException; +import com.jd.blockchain.utils.jar.ContractJarUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -29,14 +30,7 @@ public class GatewayInterceptServiceHandler implements GatewayInterceptService { } private void contractCheck(final ContractCodeDeployOperation contractOP) { - // - byte[] chainCode = contractOP.getChainCode(); - if (chainCode == null || chainCode.length == 0) { - throw new IllegalDataException("Contract's content is empty !!!"); - } - - - - + // 校验chainCode + ContractJarUtils.verify(contractOP.getChainCode()); } } diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/TxProcessingController.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/TxProcessingController.java index 3ee571d9..5405cc98 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/TxProcessingController.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/TxProcessingController.java @@ -1,5 +1,6 @@ package com.jd.blockchain.gateway.web; +import com.jd.blockchain.gateway.service.GatewayInterceptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -30,9 +31,15 @@ public class TxProcessingController implements TransactionService { @Autowired private PeerService peerService; + @Autowired + private GatewayInterceptService interceptService; + @RequestMapping(path = "rpc/tx", method = RequestMethod.POST, consumes = BinaryMessageConverter.CONTENT_TYPE_VALUE, produces = BinaryMessageConverter.CONTENT_TYPE_VALUE) @Override public @ResponseBody TransactionResponse process(@RequestBody TransactionRequest txRequest) { + // 拦截请求进行校验 + interceptService.intercept(txRequest); + // 检查交易请求的信息是否完整; HashDigest ledgerHash = txRequest.getTransactionContent().getLedgerHash(); if (ledgerHash == null) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java index e18c9304..fd4dc617 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java @@ -1,5 +1,6 @@ package com.jd.blockchain.ledger.core.impl.handles; +import com.jd.blockchain.utils.jar.ContractJarUtils; import org.springframework.stereotype.Service; import com.jd.blockchain.ledger.BytesValue; @@ -22,8 +23,12 @@ public class ContractCodeDeployOperationHandle implements OperationHandle { // TODO: 请求者应该提供合约账户的公钥签名,已确定注册的地址的唯一性; + byte[] chainCode = contractOP.getChainCode(); + // 校验合约代码,不通过会抛出异常 + ContractJarUtils.verify(chainCode); + dataset.getContractAccountSet().deploy(contractOP.getContractID().getAddress(), - contractOP.getContractID().getPubKey(), contractOP.getAddressSignature(), contractOP.getChainCode()); + contractOP.getContractID().getPubKey(), contractOP.getAddressSignature(), chainCode); return null; } @@ -37,5 +42,4 @@ public class ContractCodeDeployOperationHandle implements OperationHandle { public boolean support(Class operationType) { return ContractCodeDeployOperation.class.isAssignableFrom(operationType); } - } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index a8f34045..93f0e3c7 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -260,8 +260,6 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private class ContractCodeDeployOperationBuilderFilter implements ContractCodeDeployOperationBuilder { @Override public ContractCodeDeployOperation deploy(BlockchainIdentity id, byte[] chainCode) { - // 校验chainCode - ContractJarUtils.verify(chainCode); // 校验成功后发布 ContractCodeDeployOperation op = CONTRACT_CODE_DEPLOY_OP_BUILDER.deploy(id, chainCode); operationList.add(op); diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java index a59512db..5ec66dde 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java @@ -15,13 +15,16 @@ import java.util.jar.JarOutputStream; public class ContractJarUtils { - private static final String JDCHAIN_META = "META-INF/CONTRACT.MF"; + private static final String CONTRACT_MF = "META-INF/CONTRACT.MF"; private static final int JDCHAIN_HASH_LENGTH = 69; private static final Random FILE_RANDOM = new Random(); public static void verify(byte[] chainCode) { + if (chainCode == null || chainCode.length == 0) { + throw new IllegalStateException("ChainCode is empty !!!"); + } // 首先生成合约文件 File jarFile = newJarFile(); try { @@ -42,14 +45,14 @@ public class ContractJarUtils { private static void verify(File jarFile) throws Exception { // 首先判断jarFile中是否含有META-INF/JDCHAIN.TXT,并将其读出 - URL jarUrl = new URL("jar:file:" + jarFile.getPath() + "!/" + JDCHAIN_META); + URL jarUrl = new URL("jar:file:" + jarFile.getPath() + "!/" + CONTRACT_MF); InputStream inputStream = jarUrl.openStream(); if (inputStream == null) { - throw new IllegalStateException(JDCHAIN_META + " IS NULL !!!"); + throw new IllegalStateException(CONTRACT_MF + " IS NULL !!!"); } byte[] bytes = IOUtils.toByteArray(inputStream); if (bytes == null || bytes.length != JDCHAIN_HASH_LENGTH) { - throw new IllegalStateException(JDCHAIN_META + " IS Illegal !!!"); + throw new IllegalStateException(CONTRACT_MF + " IS Illegal !!!"); } // 获取对应的Hash内容 String txt = new String(bytes, StandardCharsets.UTF_8); @@ -58,10 +61,10 @@ public class ContractJarUtils { File tempJar = newJarFile(); // 复制除JDCHAIN.TXT之外的部分 - copy(jarFile, tempJar, null, null, JDCHAIN_META); + copy(jarFile, tempJar, null, null, CONTRACT_MF); // 生成新Jar包对应的Hash内容 - String verifyTxt = jdChainTxt(FileUtils.readFileToByteArray(tempJar)); + String verifyTxt = contractMF(FileUtils.readFileToByteArray(tempJar)); // 删除临时文件 FileUtils.forceDelete(tempJar); @@ -103,13 +106,13 @@ public class ContractJarUtils { jarFile.close(); } - public static String jdChainTxt(byte[] content) { + public static String contractMF(byte[] content) { // hash=Hex(hash(content)) return "hash:" + DigestUtils.sha256Hex(content); } - public static JarEntry jdChainMetaTxtJarEntry() { - return new JarEntry(JDCHAIN_META); + public static JarEntry contractMFJarEntry() { + return new JarEntry(CONTRACT_MF); } private static byte[] readStream(InputStream inputStream) { diff --git a/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java b/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java index 9caa6da2..02376434 100644 --- a/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java +++ b/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java @@ -34,14 +34,14 @@ public class ContractJarUtilsTest { // 首先进行Copy处理 copy(srcJar, dstJar); - byte[] txtBytes = jdChainTxt(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); String finalJarPath = classPath + File.separator + jarName + "-jdchain.jar"; File finalJar = new File(finalJarPath); - copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); + copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); // 删除临时文件 FileUtils.forceDelete(dstJar); From 13478a21299b7035fbe91443d3277c7505b86e50 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 2 Jul 2019 19:31:14 +0800 Subject: [PATCH 005/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E6=89=93=E5=8C=85=E6=A8=A1=E5=9D=97=E5=AF=B9=E5=BA=94=E7=9A=84?= =?UTF-8?q?package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jd/blockchain/CheckImportsMojo.java | 128 ------ .../contract/maven/ContractCheckMojo.java | 216 +++++++++ .../contract/maven/ContractDeployExeUtil.java | 176 +++++++ .../contract/maven/ContractDeployMojo.java | 119 +++++ .../contract/maven/ContractVerifyMojo.java | 433 ++++++++++++++++++ .../ledger/ContractDeployMojoTest.java | 2 +- .../ledger/ContractVerifyMojoTest.java | 4 +- ...rifyTest.java => ContractVerifyTest_.java} | 4 +- 8 files changed, 949 insertions(+), 133 deletions(-) delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java rename source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/{ContractVerifyTest.java => ContractVerifyTest_.java} (93%) diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java deleted file mode 100644 index 8ed9b38e..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java +++ /dev/null @@ -1,128 +0,0 @@ -//package com.jd.blockchain; -// -//import com.github.javaparser.JavaParser; -//import com.github.javaparser.ast.CompilationUnit; -//import com.github.javaparser.ast.ImportDeclaration; -//import com.github.javaparser.ast.NodeList; -//import com.github.javaparser.ast.PackageDeclaration; -//import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -//import com.github.javaparser.ast.body.MethodDeclaration; -//import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -//import com.jd.blockchain.contract.ContractType; -//import com.jd.blockchain.utils.IllegalDataException; -//import org.apache.maven.plugin.AbstractMojo; -//import org.apache.maven.plugin.MojoFailureException; -//import org.apache.maven.plugins.annotations.Mojo; -//import org.apache.maven.plugins.annotations.Parameter; -//import org.apache.maven.project.MavenProject; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.File; -//import java.io.IOException; -//import java.io.InputStream; -//import java.net.URL; -//import java.net.URLClassLoader; -//import java.nio.file.Files; -//import java.nio.file.Path; -//import java.util.List; -//import java.util.Properties; -//import java.util.jar.Attributes; -//import java.util.jar.JarFile; -//import java.util.stream.Collectors; -// -///** -// * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. -// * This is a try of "from Initail to Abandoned". -// * Since we are good at the class, why not? -// * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. -// * -// * by zhaogw -// * date 2019-06-05 16:17 -// */ -//@Mojo(name = "checkImports") -//public class CheckImportsMojo extends AbstractMojo { -// -// Logger logger = LoggerFactory.getLogger(CheckImportsMojo.class); -// -// @Parameter(defaultValue = "${project}", required = true, readonly = true) -// private MavenProject project; -// -// /** -// * jar's name; -// */ -// @Parameter -// private String finalName; -// -// @Override -// public void execute() throws MojoFailureException { -// List sources; -// try { -// InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); -// Properties properties = new Properties(); -// properties.load(inputStream); -// String[] packageBlackList = properties.getProperty("blacklist").split(","); -// Path baseDirPath = project.getBasedir().toPath(); -// sources = Files.find(baseDirPath, Integer.MAX_VALUE, (file, attrs) -> (file.toString().endsWith(".java"))).collect(Collectors.toList()); -// for (Path path : sources) { -// CompilationUnit compilationUnit = JavaParser.parse(path); -// -// compilationUnit.accept(new MethodVisitor(), null); -// -// NodeList imports = compilationUnit.getImports(); -// for (ImportDeclaration imp : imports) { -// String importName = imp.getName().asString(); -// for (String item : packageBlackList) { -// if (importName.startsWith(item)) { -// throw new MojoFailureException("在源码中不允许包含此引入包:" + importName); -// } -// } -// } -// -// //now we parse the jar; -// String jarPath = project.getBuild().getDirectory()+ File.separator+finalName+".jar"; -// File jarFile = new File(jarPath); -// URL jarURL = jarFile.toURI().toURL(); -// ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL},this.getClass().getClassLoader()); -// Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); -// String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); -// try { -// Class mainClass = classLoader.loadClass(contractMainClass); -// ContractType.resolve(mainClass); -// } catch (ClassNotFoundException e) { -// throw new IllegalDataException(e.getMessage()); -// } -// } -// } catch (IOException exception) { -// logger.error(exception.getMessage()); -// throw new MojoFailureException("IO ERROR"); -// } catch (NullPointerException e) { -// logger.error(e.getMessage()); -// } -// } -// -// private class MethodVisitor extends VoidVisitorAdapter { -// @Override -// public void visit(MethodDeclaration n, Void arg) { -// /* here you can access the attributes of the method. -// this method will be called for all methods in this -// CompilationUnit, including inner class methods */ -// logger.info("method:"+n.getName()); -// super.visit(n, arg); -// } -// -// @Override -// public void visit(ClassOrInterfaceDeclaration n, Void arg) { -// logger.info("class:"+n.getName()+" extends:"+n.getExtendedTypes()+" implements:"+n.getImplementedTypes()); -// -// super.visit(n, arg); -// } -// -// @Override -// public void visit(PackageDeclaration n, Void arg) { -// logger.info("package:"+n.getName()); -// super.visit(n, arg); -// } -// -// } -//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java new file mode 100644 index 00000000..2cd5dba6 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java @@ -0,0 +1,216 @@ +package com.jd.blockchain.contract.maven; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.shared.invoker.*; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +@Mojo(name = "contractCheck") +public class ContractCheckMojo extends AbstractMojo { + + Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); + + public static final String CONTRACT_VERIFY = "contractVerify"; + + private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; + + private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; + + private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; + + private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; + + private static final String GOALS_VERIFY = "package"; + + private static final String GOALS_PACKAGE = "package"; + + private static final String OUT_POM_XML = "ContractPom.xml"; + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + /** + * jar's name; + */ + @Parameter + private String finalName; + + /** + * mainClass; + */ + @Parameter + private String mainClass; + /** + * ledgerVersion; + */ + @Parameter + private String ledgerVersion; + + /** + * mvnHome; + */ + @Parameter + private String mvnHome; + + /** + * first compile the class, then parse it; + */ + @Override + public void execute() throws MojoFailureException { + compileFiles(); + } + + private void compileFiles() throws MojoFailureException { + try (FileInputStream fis = new FileInputStream(project.getFile())) { + + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model model = reader.read(fis); + + //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; + Plugin plugin = model.getBuild().getPluginsAsMap() + .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); + if(plugin == null){ + plugin = model.getBuild().getPluginsAsMap() + .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); + } + + if(plugin == null) { + return; + } + + model.getBuild().removePlugin(plugin); + + List plugins = new ArrayList<>(); + plugins.add(createAssembly()); + plugins.add(createContractVerify()); + + model.getBuild().setPlugins(plugins); + + handle(model); + + } catch (Exception e) { + LOG.error(e.getMessage()); + throw new MojoFailureException(e.getMessage()); + } + } + + private void invokeCompile(File file) { + InvocationRequest request = new DefaultInvocationRequest(); + + Invoker invoker = new DefaultInvoker(); + try { + request.setPomFile(file); + + request.setGoals(Collections.singletonList(GOALS_VERIFY)); + invoker.setMavenHome(new File(mvnHome)); + invoker.execute(request); + } catch (MavenInvocationException e) { + LOG.error(e.getMessage()); + throw new IllegalStateException(e); + } + } + + private Plugin createContractVerify() { + Plugin plugin = new Plugin(); + plugin.setGroupId(JDCHAIN_PACKAGE); + plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); + plugin.setVersion(ledgerVersion); + + Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); + finalNameNode.setValue(finalName); + Xpp3Dom configuration = new Xpp3Dom("configuration"); + configuration.addChild(finalNameNode); + + plugin.setConfiguration(configuration); + plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); + + return plugin; + } + + private Plugin createAssembly() { + Plugin plugin = new Plugin(); + plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); + + Xpp3Dom configuration = new Xpp3Dom("configuration"); + + Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); + mainClassNode.setValue(mainClass); + + Xpp3Dom manifest = new Xpp3Dom("manifest"); + manifest.addChild(mainClassNode); + + Xpp3Dom archive = new Xpp3Dom("archive"); + archive.addChild(manifest); + + Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); + finalNameNode.setValue(finalName); + + Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); + appendAssemblyId.setValue("false"); + + Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); + descriptorRef.setValue("jar-with-dependencies"); + Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); + descriptorRefs.addChild(descriptorRef); + + configuration.addChild(finalNameNode); + configuration.addChild(appendAssemblyId); + configuration.addChild(archive); + configuration.addChild(descriptorRefs); + + plugin.setConfiguration(configuration); + plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); + + return plugin; + } + + private List pluginExecution(String id, String phase, String goal) { + PluginExecution pluginExecution = new PluginExecution(); + pluginExecution.setId(id); + pluginExecution.setPhase(phase); + List goals = new ArrayList<>(); + goals.add(goal); + pluginExecution.setGoals(goals); + List pluginExecutions = new ArrayList<>(); + pluginExecutions.add(pluginExecution); + + return pluginExecutions; + } + + private void handle(Model model) throws IOException { + + MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + mavenXpp3Writer.write(outputStream, model); + + byte[] buffer = outputStream.toByteArray(); + + File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); + + FileUtils.writeByteArrayToFile(outPom, buffer); + + invokeCompile(outPom); + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java new file mode 100644 index 00000000..10aada08 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java @@ -0,0 +1,176 @@ +package com.jd.blockchain.contract.maven; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.net.NetworkAddress; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @Author zhaogw + * @Date 2018/11/2 10:18 + */ +public enum ContractDeployExeUtil { + instance; + private BlockchainService bcsrv; + private Bytes contractAddress; + + public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ + PubKey pub = null; + PrivKey prv = null; + try { + prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); + pub = KeyGenCommand.readPubKey(pubPath); + + } catch (Exception e) { + e.printStackTrace(); + } + + return new BlockchainKeypair(pub, prv); + } + + public PubKey getPubKey(String pubPath){ + PubKey pub = null; + try { + if(pubPath == null){ + BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); + pub = contractKeyPair.getPubKey(); + }else { + pub = KeyGenCommand.readPubKey(pubPath); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return pub; + } + public byte[] getChainCode(String path){ + byte[] chainCode = null; + File file = null; + InputStream input = null; + try { + file = new File(path); + input = new FileInputStream(file); + chainCode = new byte[input.available()]; + input.read(chainCode); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if(input!=null){ + input.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return chainCode; + } + + private void register(){ + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); + DataContractRegistry.register(DataAccountKVSetOperation.class); + DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); + DataContractRegistry.register(Operation.class); + DataContractRegistry.register(ContractCodeDeployOperation.class); + DataContractRegistry.register(ContractEventSendOperation.class); + DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(UserRegisterOperation.class); + } + + public BlockchainService initBcsrv(String host, int port) { + if(bcsrv!=null){ + return bcsrv; + } + NetworkAddress addr = new NetworkAddress(host, port); + GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); + bcsrv = gwsrvFact.getBlockchainService(); + return bcsrv; + } + + public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ + register(); + + TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); + txTpl.contracts().deploy(contractIdentity, chainCode); + PreparedTransaction ptx = txTpl.prepare(); + ptx.sign(ownerKey); + // 提交并等待共识返回; + TransactionResponse txResp = ptx.commit(); + + // 验证结果; + contractAddress = contractIdentity.getAddress(); + this.setContractAddress(contractAddress); + System.out.println("contract's address="+contractAddress); + return txResp.isSuccess(); + } + public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ + register(); + + BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); + initBcsrv(host,port); + return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); + } + + // 根据用户指定的公钥生成合约地址 + public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, + String ownerPassword, String chainCodePath,String pubPath){ + PubKey pubKey = getPubKey(pubPath); + BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); + byte[] chainCode = getChainCode(chainCodePath); + + BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); + HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); + initBcsrv(host,port); + return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); + } + + +// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; + +// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, +// String ownerPassword,String event,String contractArgs){ +// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); +// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); +// +// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; +// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); +// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); +// +// // 签名; +// PreparedTransaction ptx = txTpl.prepare(); +// ptx.sign(ownerKey); +// +// // 提交并等待共识返回; +// TransactionResponse txResp = ptx.commit(); +// +// // 验证结果; +// return txResp.isSuccess(); +// } + + public Bytes getContractAddress() { + return contractAddress; + } + + public void setContractAddress(Bytes contractAddress) { + this.contractAddress = contractAddress; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java new file mode 100644 index 00000000..25da2357 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java @@ -0,0 +1,119 @@ +package com.jd.blockchain.contract.maven; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.StringUtils; +import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.io.FileUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * for contract remote deploy; + * @goal contractDeploy + * @phase process-sources + * @Author zhaogw + * @Date 2018/10/18 10:12 + */ + +@Mojo(name = "deploy") +public class ContractDeployMojo extends AbstractMojo { + Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); + + @Parameter + private File config; + + @Override + public void execute()throws MojoFailureException { + Properties prop = new Properties(); + InputStream input = null; + + try { + input = new FileInputStream(config); + prop.load(input); + + } catch (IOException ex) { + logger.error(ex.getMessage()); + throw new MojoFailureException("io error"); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + } + int port; + try { + port = Integer.parseInt(prop.getProperty("port")); + }catch (NumberFormatException e){ + logger.error(e.getMessage()); + throw new MojoFailureException("invalid port"); + } + String host = prop.getProperty("host"); + String ledger = prop.getProperty("ledger"); + String pubKey = prop.getProperty("pubKey"); + String prvKey = prop.getProperty("prvKey"); + String password = prop.getProperty("password"); + String contractPath = prop.getProperty("contractPath"); + + + if(StringUtils.isEmpty(host)){ + logger.info("host不能为空"); + return; + } + + if(StringUtils.isEmpty(ledger)){ + logger.info("ledger不能为空."); + return; + } + if(StringUtils.isEmpty(pubKey)){ + logger.info("pubKey不能为空."); + return; + } + if(StringUtils.isEmpty(prvKey)){ + logger.info("prvKey不能为空."); + return; + } + if(StringUtils.isEmpty(contractPath)){ + logger.info("contractPath不能为空."); + return; + } + + File contract = new File(contractPath); + if (!contract.isFile()){ + logger.info("文件"+contractPath+"不存在"); + return; + } + byte[] contractBytes = FileUtils.readBytes(contractPath); + + + PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); + PubKey pub = KeyGenCommand.decodePubKey(pubKey); + BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); + HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); + + StringBuffer sb = new StringBuffer(); + sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). + append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); + logger.info(sb.toString()); + ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); + } + +} + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java new file mode 100644 index 00000000..2cafd249 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java @@ -0,0 +1,433 @@ +package com.jd.blockchain.contract.maven; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.jd.blockchain.contract.ContractType; +import org.apache.commons.io.FileUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import static com.jd.blockchain.contract.maven.ContractCheckMojo.CONTRACT_VERIFY; +import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; +import static com.jd.blockchain.utils.jar.ContractJarUtils.*; + +/** + * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. + * This is a try of "from Initail to Abandoned". + * Since we are good at the class, why not? + * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. + * + * by zhaogw + * date 2019-06-05 16:17 + */ +@Mojo(name = CONTRACT_VERIFY) +public class ContractVerifyMojo extends AbstractMojo { + + Logger LOG = LoggerFactory.getLogger(ContractVerifyMojo.class); + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + /** + * jar's name; + */ + @Parameter + private String finalName; + + private static final String JAVA_SUFFIX = ".java"; + + private static final String PATH_DIRECT = + "src" + File.separator + + "main" + File.separator + + "java" + File.separator; + + private static final String CONFIG = "config.properties"; + + private static final String BLACK_PACKAGE_LIST = "black.package.list"; + + private static final String BLACK_CLASS_LIST = "black.class.list"; + + private static final String BLACK_NAME_LIST = "black.name.list"; + + @Override + public void execute() throws MojoExecutionException { + + try { + + File jarFile = copyAndManage(); + + // 首先校验MainClass + verifyMainClass(jarFile); + + Properties config = loadConfig(); + + List blackNameList = blackNameList(config); + + List blackPackageList = blackPackageList(config); + + Set blackClassSet = blackClassSet(config); + + LinkedList totalClassList = loadAllClass(jarFile); + // 该项目路径 + String projectDir = project.getBasedir().getPath(); + // 代码路径 + String codeBaseDir = projectDir + File.separator + PATH_DIRECT; + + if (!totalClassList.isEmpty()) { + + boolean isOK = true; + + for (String clazz : totalClassList) { + // 获取其包名 + String packageName = packageName(clazz); + + // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 + boolean isNameBlack = false; + for (ContractPackage blackName : blackNameList) { + isNameBlack = verifyPackage(packageName, blackName); + if (isNameBlack) { + break; + } + } + + // 假设是黑名单则打印日志 + if (isNameBlack) { + // 打印信息供检查 + LOG.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); + isOK = false; + continue; + } + + // 获取该Class对应的Java文件 + File javaFile = new File(codeBaseDir + clazz + JAVA_SUFFIX); + + boolean isNeedDelete = false; + if (!javaFile.exists()) { + // 表明不是项目中的内容,需要通过反编译获取该文件 + String source = null; + try { + source = decompileJarFile(jarFile.getPath(), clazz, true, StandardCharsets.UTF_8.name()); + if (source == null || source.length() == 0) { + throw new IllegalStateException(); + } + } catch (Exception e) { + LOG.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); + } + // 将source写入Java文件 + File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); + FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); + javaFile = sourceTempJavaFile; + isNeedDelete = true; + } + + // 解析文件中的内容 + CompilationUnit compilationUnit = JavaParser.parse(javaFile); + + MethodVisitor methodVisitor = new MethodVisitor(); + + compilationUnit.accept(methodVisitor, null); + + List imports = methodVisitor.importClasses; + + if (!imports.isEmpty()) { + for (String importClass : imports) { + if (importClass.endsWith("*")) { + // 导入的是包 + for (ContractPackage blackPackage : blackPackageList) { + String importPackageName = importClass.substring(0, importClass.length() - 2); + if (verifyPackage(importPackageName, blackPackage)) { + // 打印信息供检查 + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + isOK = false; + break; + } + } + } else { + // 导入的是具体的类,则判断类黑名单 + 包黑名单 + if (blackClassSet.contains(importClass)) { + // 包含导入类,该方式无法通过验证 + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); + isOK = false; + } else { + // 判断导入的该类与黑名单导入包的对应关系 + for (ContractPackage blackPackage : blackPackageList) { + if (verifyClass(importClass, blackPackage)) { + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + isOK = false; + break; + } + } + } + } + } + } + if (isNeedDelete) { + javaFile.delete(); + } + } + if (!isOK) { + throw new IllegalStateException("There are many Illegal information, please check !!!"); + } + } else { + throw new IllegalStateException("There is none class !!!"); + } + } catch (Exception e) { + LOG.error(e.getMessage()); + throw new MojoExecutionException(e.getMessage()); + } + } + + private void verifyMainClass(File jarFile) throws Exception { + // 加载main-class,开始校验类型 + URL jarURL = jarFile.toURI().toURL(); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); + Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); + String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); + Class mainClass = classLoader.loadClass(contractMainClass); + ContractType.resolve(mainClass); + } + + private List blackNameList(Properties config) { + return blackList(config, BLACK_NAME_LIST); + } + + private Set blackClassSet(Properties config) { + Set blackClassSet = new HashSet<>(); + String attrProp = config.getProperty(BLACK_CLASS_LIST); + if (attrProp != null && attrProp.length() > 0) { + String[] attrPropArray = attrProp.split(","); + for (String attr : attrPropArray) { + blackClassSet.add(attr.trim()); + } + } + return blackClassSet; + } + + private List blackPackageList(Properties config) { + return blackList(config, BLACK_PACKAGE_LIST); + } + + private List blackList(Properties config, String attrName) { + List list = new ArrayList<>(); + String attrProp = config.getProperty(attrName); + if (attrProp != null || attrProp.length() > 0) { + String[] attrPropArray = attrProp.split(","); + for (String attr : attrPropArray) { + list.add(new ContractPackage(attr)); + } + } + return list; + } + + private boolean verifyPackage(String packageName, ContractPackage contractPackage) { + boolean verify = false; + if (packageName.equals(contractPackage.packageName)) { + // 完全相同 + verify = true; + } else if (packageName.startsWith(contractPackage.packageName) && + contractPackage.isTotal) { + // 以某个包开头 + verify = true; + } + return verify; + } + + private boolean verifyClass(String className, ContractPackage contractPackage) { + boolean verify = false; + + if (contractPackage.isTotal) { + // 表示该包下面的其他所有包都会受限制,此处需要判断起始 + if (className.startsWith(contractPackage.packageName)) { + verify = true; + } + } else { + // 表示该包必须完整匹配ClassName所在包 + // 获取ClassName所在包 + String packageName = packageNameByDot(className); + if (packageName.equals(contractPackage.packageName)) { + verify = true; + } + } + return verify; + } + + private String packageNameByDot(String className) { + String[] array = className.split("."); + if (Character.isLowerCase(array[array.length - 2].charAt(0))) { + // 如果是小写,表示非内部类 + // 获取完整包名 + return className.substring(0, className.lastIndexOf(".")); + } + // 表示为内部类,该包拼装组成 + StringBuilder buffer = new StringBuilder(); + for (String s : array) { + if (buffer.length() > 0) { + buffer.append("."); + } + if (Character.isUpperCase(s.charAt(0))) { + // 表明已经到具体类 + break; + } + buffer.append(s); + } + + if (buffer.length() == 0) { + throw new IllegalStateException(String.format("Import Class [%s] Illegal !!!", className)); + } + + return buffer.toString(); + } + + private String packageName(String clazz) { + int index = clazz.lastIndexOf("/"); + String packageName = clazz.substring(0, index); + return packageName.replaceAll("/", "."); + } + + private File copyAndManage() throws IOException { + // 首先将Jar包转换为指定的格式 + String srcJarPath = project.getBuild().getDirectory() + + File.separator + finalName + ".jar"; + + String dstJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-temp-" + System.currentTimeMillis() + ".jar"; + + File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); + + // 首先进行Copy处理 + copy(srcJar, dstJar); + + byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + + String finalJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-jdchain.jar"; + + File finalJar = new File(finalJarPath); + + copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); + + // 删除临时文件 + FileUtils.forceDelete(dstJar); + + return finalJar; + } + + private Properties loadConfig() throws Exception { + + Properties properties = new Properties(); + + properties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG)); + + return properties; + } + + private LinkedList loadAllClass(File file) throws Exception { + JarFile jarFile = new JarFile(file); + LinkedList allClass = new LinkedList<>(); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (entryName.endsWith(".class")) { + // 内部类,不需要处理 + if (!entryName.contains("$")) { + allClass.addLast(entryName.substring(0, entryName.length() - 6)); + } + } + } + return allClass; + } + + private String tempPath(String codeBaseDir, String clazz) { + // 获取最后的名称 + String[] classArray = clazz.split("/"); + String tempPath = codeBaseDir + classArray[classArray.length - 1] + "_" + + System.currentTimeMillis() + "_" + System.nanoTime() + JAVA_SUFFIX; + return tempPath; + } + + private static class MethodVisitor extends VoidVisitorAdapter { + + private List importClasses = new ArrayList<>(); + +// @Override +// public void visit(MethodDeclaration n, Void arg) { +// /* here you can access the attributes of the method. +// this method will be called for all methods in this +// CompilationUnit, including inner class methods */ +// super.visit(n, arg); +// } +// +// @Override +// public void visit(ClassOrInterfaceDeclaration n, Void arg) { +// super.visit(n, arg); +// } +// +// @Override +// public void visit(PackageDeclaration n, Void arg) { +// super.visit(n, arg); +// } + + @Override + public void visit(ImportDeclaration n, Void arg) { + importClasses.add(parseClass(n.toString())); + super.visit(n, arg); + } + + private String parseClass(String importInfo) { + String className = importInfo.substring(7, importInfo.length() - 2); + if (importInfo.startsWith("import static ")) { + // 获取静态方法的类信息 + className = importInfo.substring(14, importInfo.lastIndexOf(".")); + } + if (!className.contains(".")) { + throw new IllegalStateException(String.format("Import Class [%s] is Illegal !!", className)); + } + return className; + } + } + + private static class ContractPackage { + + private String packageName; + + private boolean isTotal = false; + + public ContractPackage() { + } + + public ContractPackage(String totalPackage) { + if (totalPackage.endsWith("*")) { + this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); + this.isTotal = true; + } else { + this.packageName = totalPackage; + } + } + + public String getPackageName() { + return packageName; + } + + public boolean isTotal() { + return isTotal; + } + } +} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java index 0d205758..442f1ba0 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.ContractDeployMojo; +import com.jd.blockchain.contract.maven.ContractDeployMojo; import java.lang.reflect.Field; diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java index ac9813cd..570cc8e6 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.ContractVerifyMojo; +import com.jd.blockchain.contract.maven.ContractVerifyMojo; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import org.junit.Test; import org.slf4j.Logger; @@ -21,7 +21,7 @@ public class ContractVerifyMojoTest extends AbstractMojoTestCase { assertNotNull( pom ); assertTrue( pom.exists() ); - ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "Contract.Verify", pom ); + ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "contractVerify", pom ); assertNotNull( myMojo ); myMojo.execute(); } diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java similarity index 93% rename from source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java rename to source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java index d57ab5d0..f3e8fea9 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.ContractVerifyMojo; +import com.jd.blockchain.contract.maven.ContractVerifyMojo; import org.apache.maven.project.MavenProject; import org.junit.Before; import org.junit.Test; @@ -9,7 +9,7 @@ import java.lang.reflect.Field; import static com.jd.blockchain.ledger.ContractTestBase.mavenProjectInit; -public class ContractVerifyTest { +public class ContractVerifyTest_ { private MavenProject project; From c021cf48cacc9e4e639fdb0339767fa63ae5b306 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 2 Jul 2019 19:43:41 +0800 Subject: [PATCH 006/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E6=89=93=E5=8C=85=E5=BC=82=E5=B8=B8=E5=88=A0=E9=99=A4=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jd/blockchain/ContractCheckMojo.java | 213 --------- .../jd/blockchain/ContractDeployExeUtil.java | 193 -------- .../com/jd/blockchain/ContractDeployMojo.java | 119 ----- .../com/jd/blockchain/ContractVerifyMojo.java | 432 ------------------ .../contract/maven/ContractVerifyMojo.java | 3 + 5 files changed, 3 insertions(+), 957 deletions(-) delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java deleted file mode 100644 index a57357e0..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractCheckMojo.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.jd.blockchain; - -import org.apache.commons.io.FileUtils; -import org.apache.maven.model.Model; -import org.apache.maven.model.Plugin; -import org.apache.maven.model.PluginExecution; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.model.io.xpp3.MavenXpp3Writer; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.invoker.*; -import org.codehaus.plexus.util.xml.Xpp3Dom; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -@Mojo(name = "contractCheck") -public class ContractCheckMojo extends AbstractMojo { - - Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); - - public static final String CONTRACT_VERIFY = "contractVerify"; - - private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; - - private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; - - private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; - - private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; - - private static final String GOALS_VERIFY = "package"; - - private static final String GOALS_PACKAGE = "package"; - - private static final String OUT_POM_XML = "ContractPom.xml"; - - @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; - - /** - * jar's name; - */ - @Parameter - private String finalName; - - /** - * mainClass; - */ - @Parameter - private String mainClass; - /** - * ledgerVersion; - */ - @Parameter - private String ledgerVersion; - - /** - * mvnHome; - */ - @Parameter - private String mvnHome; - - /** - * first compile the class, then parse it; - */ - @Override - public void execute() throws MojoFailureException { - compileFiles(); - } - - private void compileFiles() throws MojoFailureException { - try (FileInputStream fis = new FileInputStream(project.getFile())) { - - MavenXpp3Reader reader = new MavenXpp3Reader(); - Model model = reader.read(fis); - - //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; - Plugin plugin = model.getBuild().getPluginsAsMap() - .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); - if(plugin == null){ - plugin = model.getBuild().getPluginsAsMap() - .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); - } - - if(plugin == null) { - return; - } - - model.getBuild().removePlugin(plugin); - - List plugins = new ArrayList<>(); - plugins.add(createAssembly()); - plugins.add(createContractVerify()); - - model.getBuild().setPlugins(plugins); - - handle(model); - - } catch (Exception e) { - LOG.error(e.getMessage()); - throw new MojoFailureException(e.getMessage()); - } - } - - private void invokeCompile(File file) { - InvocationRequest request = new DefaultInvocationRequest(); - - Invoker invoker = new DefaultInvoker(); - try { - request.setPomFile(file); - - request.setGoals(Collections.singletonList(GOALS_VERIFY)); - invoker.setMavenHome(new File(mvnHome)); - invoker.execute(request); - } catch (MavenInvocationException e) { - LOG.error(e.getMessage()); - throw new IllegalStateException(e); - } - } - - private Plugin createContractVerify() { - Plugin plugin = new Plugin(); - plugin.setGroupId(JDCHAIN_PACKAGE); - plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); - plugin.setVersion(ledgerVersion); - - Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); - finalNameNode.setValue(finalName); - Xpp3Dom configuration = new Xpp3Dom("configuration"); - configuration.addChild(finalNameNode); - - plugin.setConfiguration(configuration); - plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); - - return plugin; - } - - private Plugin createAssembly() { - Plugin plugin = new Plugin(); - plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); - - Xpp3Dom configuration = new Xpp3Dom("configuration"); - - Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); - mainClassNode.setValue(mainClass); - - Xpp3Dom manifest = new Xpp3Dom("manifest"); - manifest.addChild(mainClassNode); - - Xpp3Dom archive = new Xpp3Dom("archive"); - archive.addChild(manifest); - - Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); - finalNameNode.setValue(finalName); - - Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); - appendAssemblyId.setValue("false"); - - Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); - descriptorRef.setValue("jar-with-dependencies"); - Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); - descriptorRefs.addChild(descriptorRef); - - configuration.addChild(finalNameNode); - configuration.addChild(appendAssemblyId); - configuration.addChild(archive); - configuration.addChild(descriptorRefs); - - plugin.setConfiguration(configuration); - plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); - - return plugin; - } - - private List pluginExecution(String id, String phase, String goal) { - PluginExecution pluginExecution = new PluginExecution(); - pluginExecution.setId(id); - pluginExecution.setPhase(phase); - List goals = new ArrayList<>(); - goals.add(goal); - pluginExecution.setGoals(goals); - List pluginExecutions = new ArrayList<>(); - pluginExecutions.add(pluginExecution); - - return pluginExecutions; - } - - private void handle(Model model) throws IOException { - - MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - mavenXpp3Writer.write(outputStream, model); - - byte[] buffer = outputStream.toByteArray(); - - File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); - - FileUtils.writeByteArrayToFile(outPom, buffer); - - invokeCompile(outPom); - } -} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java deleted file mode 100644 index a206a854..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.jd.blockchain; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainIdentityData; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.EndpointRequest; -import com.jd.blockchain.ledger.NodeRequest; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.PreparedTransaction; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionContentBody; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.TransactionTemplate; -import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.tools.keygen.KeyGenCommand; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.net.NetworkAddress; - -/** - * @Author zhaogw - * @Date 2018/11/2 10:18 - */ -public enum ContractDeployExeUtil { - instance; - private BlockchainService bcsrv; - private Bytes contractAddress; - - public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ - PubKey pub = null; - PrivKey prv = null; - try { - prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); - pub = KeyGenCommand.readPubKey(pubPath); - - } catch (Exception e) { - e.printStackTrace(); - } - - return new BlockchainKeypair(pub, prv); - } - - public PubKey getPubKey(String pubPath){ - PubKey pub = null; - try { - if(pubPath == null){ - BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); - pub = contractKeyPair.getPubKey(); - }else { - pub = KeyGenCommand.readPubKey(pubPath); - } - - } catch (Exception e) { - e.printStackTrace(); - } - - return pub; - } - public byte[] getChainCode(String path){ - byte[] chainCode = null; - File file = null; - InputStream input = null; - try { - file = new File(path); - input = new FileInputStream(file); - chainCode = new byte[input.available()]; - input.read(chainCode); - - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if(input!=null){ - input.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return chainCode; - } - - private void register(){ - DataContractRegistry.register(TransactionContent.class); - DataContractRegistry.register(TransactionContentBody.class); - DataContractRegistry.register(TransactionRequest.class); - DataContractRegistry.register(NodeRequest.class); - DataContractRegistry.register(EndpointRequest.class); - DataContractRegistry.register(TransactionResponse.class); - DataContractRegistry.register(DataAccountKVSetOperation.class); - DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); - DataContractRegistry.register(Operation.class); - DataContractRegistry.register(ContractCodeDeployOperation.class); - DataContractRegistry.register(ContractEventSendOperation.class); - DataContractRegistry.register(DataAccountRegisterOperation.class); - DataContractRegistry.register(UserRegisterOperation.class); - } - - public BlockchainService initBcsrv(String host, int port) { - if(bcsrv!=null){ - return bcsrv; - } - NetworkAddress addr = new NetworkAddress(host, port); - GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); - bcsrv = gwsrvFact.getBlockchainService(); - return bcsrv; - } - - public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ - register(); - - TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); - txTpl.contracts().deploy(contractIdentity, chainCode); - PreparedTransaction ptx = txTpl.prepare(); - ptx.sign(ownerKey); - // 提交并等待共识返回; - TransactionResponse txResp = ptx.commit(); - - // 验证结果; - contractAddress = contractIdentity.getAddress(); - this.setContractAddress(contractAddress); - System.out.println("contract's address="+contractAddress); - return txResp.isSuccess(); - } - public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ - register(); - - BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); - initBcsrv(host,port); - return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); - } - - // 根据用户指定的公钥生成合约地址 - public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, - String ownerPassword, String chainCodePath,String pubPath){ - PubKey pubKey = getPubKey(pubPath); - BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); - byte[] chainCode = getChainCode(chainCodePath); - - BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); - HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); - initBcsrv(host,port); - return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); - } - - -// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; - -// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, -// String ownerPassword,String event,String contractArgs){ -// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); -// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -// -// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; -// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); -// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); -// -// // 签名; -// PreparedTransaction ptx = txTpl.prepare(); -// ptx.sign(ownerKey); -// -// // 提交并等待共识返回; -// TransactionResponse txResp = ptx.commit(); -// -// // 验证结果; -// return txResp.isSuccess(); -// } - - public Bytes getContractAddress() { - return contractAddress; - } - - public void setContractAddress(Bytes contractAddress) { - this.contractAddress = contractAddress; - } -} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java deleted file mode 100644 index 3eac13bb..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.jd.blockchain; - -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.tools.keygen.KeyGenCommand; -import com.jd.blockchain.utils.StringUtils; -import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.io.FileUtils; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -/** - * for contract remote deploy; - * @goal contractDeploy - * @phase process-sources - * @Author zhaogw - * @Date 2018/10/18 10:12 - */ - -@Mojo(name = "deploy") -public class ContractDeployMojo extends AbstractMojo { - Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); - - @Parameter - private File config; - - @Override - public void execute()throws MojoFailureException { - Properties prop = new Properties(); - InputStream input = null; - - try { - input = new FileInputStream(config); - prop.load(input); - - } catch (IOException ex) { - logger.error(ex.getMessage()); - throw new MojoFailureException("io error"); - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException e) { - logger.error(e.getMessage()); - } - } - } - int port; - try { - port = Integer.parseInt(prop.getProperty("port")); - }catch (NumberFormatException e){ - logger.error(e.getMessage()); - throw new MojoFailureException("invalid port"); - } - String host = prop.getProperty("host"); - String ledger = prop.getProperty("ledger"); - String pubKey = prop.getProperty("pubKey"); - String prvKey = prop.getProperty("prvKey"); - String password = prop.getProperty("password"); - String contractPath = prop.getProperty("contractPath"); - - - if(StringUtils.isEmpty(host)){ - logger.info("host不能为空"); - return; - } - - if(StringUtils.isEmpty(ledger)){ - logger.info("ledger不能为空."); - return; - } - if(StringUtils.isEmpty(pubKey)){ - logger.info("pubKey不能为空."); - return; - } - if(StringUtils.isEmpty(prvKey)){ - logger.info("prvKey不能为空."); - return; - } - if(StringUtils.isEmpty(contractPath)){ - logger.info("contractPath不能为空."); - return; - } - - File contract = new File(contractPath); - if (!contract.isFile()){ - logger.info("文件"+contractPath+"不存在"); - return; - } - byte[] contractBytes = FileUtils.readBytes(contractPath); - - - PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); - PubKey pub = KeyGenCommand.decodePubKey(pubKey); - BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); - HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); - - StringBuffer sb = new StringBuffer(); - sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). - append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); - logger.info(sb.toString()); - ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); - } - -} - - diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java deleted file mode 100644 index 950eb9bc..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java +++ /dev/null @@ -1,432 +0,0 @@ -package com.jd.blockchain; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import com.jd.blockchain.contract.ContractType; -import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import static com.jd.blockchain.ContractCheckMojo.CONTRACT_VERIFY; -import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; -import static com.jd.blockchain.utils.jar.ContractJarUtils.*; - -/** - * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. - * This is a try of "from Initail to Abandoned". - * Since we are good at the class, why not? - * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. - * - * by zhaogw - * date 2019-06-05 16:17 - */ -@Mojo(name = CONTRACT_VERIFY) -public class ContractVerifyMojo extends AbstractMojo { - - Logger LOG = LoggerFactory.getLogger(ContractVerifyMojo.class); - - @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; - - /** - * jar's name; - */ - @Parameter - private String finalName; - - private static final String JAVA_SUFFIX = ".java"; - - private static final String PATH_DIRECT = - "src" + File.separator + - "main" + File.separator + - "java" + File.separator; - - private static final String CONFIG = "config.properties"; - - private static final String BLACK_PACKAGE_LIST = "black.package.list"; - - private static final String BLACK_CLASS_LIST = "black.class.list"; - - private static final String BLACK_NAME_LIST = "black.name.list"; - - @Override - public void execute() throws MojoExecutionException { - - try { - - File jarFile = copyAndManage(); - - // 首先校验MainClass - verifyMainClass(jarFile); - - Properties config = loadConfig(); - - List blackNameList = blackNameList(config); - - List blackPackageList = blackPackageList(config); - - Set blackClassSet = blackClassSet(config); - - LinkedList totalClassList = loadAllClass(jarFile); - // 该项目路径 - String projectDir = project.getBasedir().getPath(); - // 代码路径 - String codeBaseDir = projectDir + File.separator + PATH_DIRECT; - - if (!totalClassList.isEmpty()) { - - boolean isOK = true; - - for (String clazz : totalClassList) { - // 获取其包名 - String packageName = packageName(clazz); - - // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 - boolean isNameBlack = false; - for (ContractPackage blackName : blackNameList) { - isNameBlack = verifyPackage(packageName, blackName); - if (isNameBlack) { - break; - } - } - - // 假设是黑名单则打印日志 - if (isNameBlack) { - // 打印信息供检查 - LOG.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); - isOK = false; - continue; - } - - // 获取该Class对应的Java文件 - File javaFile = new File(codeBaseDir + clazz + JAVA_SUFFIX); - - boolean isNeedDelete = false; - if (!javaFile.exists()) { - // 表明不是项目中的内容,需要通过反编译获取该文件 - String source = null; - try { - source = decompileJarFile(jarFile.getPath(), clazz, true, StandardCharsets.UTF_8.name()); - if (source == null || source.length() == 0) { - throw new IllegalStateException(); - } - } catch (Exception e) { - LOG.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); - } - // 将source写入Java文件 - File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); - FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); - javaFile = sourceTempJavaFile; - isNeedDelete = true; - } - - // 解析文件中的内容 - CompilationUnit compilationUnit = JavaParser.parse(javaFile); - - MethodVisitor methodVisitor = new MethodVisitor(); - - compilationUnit.accept(methodVisitor, null); - - List imports = methodVisitor.importClasses; - - if (!imports.isEmpty()) { - for (String importClass : imports) { - if (importClass.endsWith("*")) { - // 导入的是包 - for (ContractPackage blackPackage : blackPackageList) { - String importPackageName = importClass.substring(0, importClass.length() - 2); - if (verifyPackage(importPackageName, blackPackage)) { - // 打印信息供检查 - LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); - isOK = false; - break; - } - } - } else { - // 导入的是具体的类,则判断类黑名单 + 包黑名单 - if (blackClassSet.contains(importClass)) { - // 包含导入类,该方式无法通过验证 - LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); - isOK = false; - } else { - // 判断导入的该类与黑名单导入包的对应关系 - for (ContractPackage blackPackage : blackPackageList) { - if (verifyClass(importClass, blackPackage)) { - LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); - isOK = false; - break; - } - } - } - } - } - } - if (isNeedDelete) { - javaFile.delete(); - } - } - if (!isOK) { - throw new IllegalStateException("There are many Illegal information, please check !!!"); - } - } else { - throw new IllegalStateException("There is none class !!!"); - } - } catch (Exception e) { - LOG.error(e.getMessage()); - throw new MojoExecutionException(e.getMessage()); - } - } - - private void verifyMainClass(File jarFile) throws Exception { - // 加载main-class,开始校验类型 - URL jarURL = jarFile.toURI().toURL(); - ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); - Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); - String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); - Class mainClass = classLoader.loadClass(contractMainClass); - ContractType.resolve(mainClass); - } - - private List blackNameList(Properties config) { - return blackList(config, BLACK_NAME_LIST); - } - - private Set blackClassSet(Properties config) { - Set blackClassSet = new HashSet<>(); - String attrProp = config.getProperty(BLACK_CLASS_LIST); - if (attrProp != null && attrProp.length() > 0) { - String[] attrPropArray = attrProp.split(","); - for (String attr : attrPropArray) { - blackClassSet.add(attr.trim()); - } - } - return blackClassSet; - } - - private List blackPackageList(Properties config) { - return blackList(config, BLACK_PACKAGE_LIST); - } - - private List blackList(Properties config, String attrName) { - List list = new ArrayList<>(); - String attrProp = config.getProperty(attrName); - if (attrProp != null || attrProp.length() > 0) { - String[] attrPropArray = attrProp.split(","); - for (String attr : attrPropArray) { - list.add(new ContractPackage(attr)); - } - } - return list; - } - - private boolean verifyPackage(String packageName, ContractPackage contractPackage) { - boolean verify = false; - if (packageName.equals(contractPackage.packageName)) { - // 完全相同 - verify = true; - } else if (packageName.startsWith(contractPackage.packageName) && - contractPackage.isTotal) { - // 以某个包开头 - verify = true; - } - return verify; - } - - private boolean verifyClass(String className, ContractPackage contractPackage) { - boolean verify = false; - - if (contractPackage.isTotal) { - // 表示该包下面的其他所有包都会受限制,此处需要判断起始 - if (className.startsWith(contractPackage.packageName)) { - verify = true; - } - } else { - // 表示该包必须完整匹配ClassName所在包 - // 获取ClassName所在包 - String packageName = packageNameByDot(className); - if (packageName.equals(contractPackage.packageName)) { - verify = true; - } - } - return verify; - } - - private String packageNameByDot(String className) { - String[] array = className.split("."); - if (Character.isLowerCase(array[array.length - 2].charAt(0))) { - // 如果是小写,表示非内部类 - // 获取完整包名 - return className.substring(0, className.lastIndexOf(".")); - } - // 表示为内部类,该包拼装组成 - StringBuilder buffer = new StringBuilder(); - for (String s : array) { - if (buffer.length() > 0) { - buffer.append("."); - } - if (Character.isUpperCase(s.charAt(0))) { - // 表明已经到具体类 - break; - } - buffer.append(s); - } - - if (buffer.length() == 0) { - throw new IllegalStateException(String.format("Import Class [%s] Illegal !!!", className)); - } - - return buffer.toString(); - } - - private String packageName(String clazz) { - int index = clazz.lastIndexOf("/"); - String packageName = clazz.substring(0, index); - return packageName.replaceAll("/", "."); - } - - private File copyAndManage() throws IOException { - // 首先将Jar包转换为指定的格式 - String srcJarPath = project.getBuild().getDirectory() + - File.separator + finalName + ".jar"; - - String dstJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-temp-" + System.currentTimeMillis() + ".jar"; - - File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); - - // 首先进行Copy处理 - copy(srcJar, dstJar); - - byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); - - String finalJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-jdchain.jar"; - - File finalJar = new File(finalJarPath); - - copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); - - // 删除临时文件 - FileUtils.forceDelete(dstJar); - - return finalJar; - } - - private Properties loadConfig() throws Exception { - - Properties properties = new Properties(); - - properties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG)); - - return properties; - } - - private LinkedList loadAllClass(File file) throws Exception { - JarFile jarFile = new JarFile(file); - LinkedList allClass = new LinkedList<>(); - Enumeration jarEntries = jarFile.entries(); - while (jarEntries.hasMoreElements()) { - JarEntry jarEntry = jarEntries.nextElement(); - String entryName = jarEntry.getName(); - if (entryName.endsWith(".class")) { - // 内部类,不需要处理 - if (!entryName.contains("$")) { - allClass.addLast(entryName.substring(0, entryName.length() - 6)); - } - } - } - return allClass; - } - - private String tempPath(String codeBaseDir, String clazz) { - // 获取最后的名称 - String[] classArray = clazz.split("/"); - String tempPath = codeBaseDir + classArray[classArray.length - 1] + "_" + - System.currentTimeMillis() + "_" + System.nanoTime() + JAVA_SUFFIX; - return tempPath; - } - - private static class MethodVisitor extends VoidVisitorAdapter { - - private List importClasses = new ArrayList<>(); - -// @Override -// public void visit(MethodDeclaration n, Void arg) { -// /* here you can access the attributes of the method. -// this method will be called for all methods in this -// CompilationUnit, including inner class methods */ -// super.visit(n, arg); -// } -// -// @Override -// public void visit(ClassOrInterfaceDeclaration n, Void arg) { -// super.visit(n, arg); -// } -// -// @Override -// public void visit(PackageDeclaration n, Void arg) { -// super.visit(n, arg); -// } - - @Override - public void visit(ImportDeclaration n, Void arg) { - importClasses.add(parseClass(n.toString())); - super.visit(n, arg); - } - - private String parseClass(String importInfo) { - String className = importInfo.substring(7, importInfo.length() - 2); - if (importInfo.startsWith("import static ")) { - // 获取静态方法的类信息 - className = importInfo.substring(14, importInfo.lastIndexOf(".")); - } - if (!className.contains(".")) { - throw new IllegalStateException(String.format("Import Class [%s] is Illegal !!", className)); - } - return className; - } - } - - private static class ContractPackage { - - private String packageName; - - private boolean isTotal = false; - - public ContractPackage() { - } - - public ContractPackage(String totalPackage) { - if (totalPackage.endsWith("*")) { - this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); - this.isTotal = true; - } else { - this.packageName = totalPackage; - } - } - - public String getPackageName() { - return packageName; - } - - public boolean isTotal() { - return isTotal; - } - } -} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java index 2cafd249..b0de7911 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java @@ -183,9 +183,12 @@ public class ContractVerifyMojo extends AbstractMojo { } } if (!isOK) { + // 需要将该Jar删除 + jarFile.delete(); throw new IllegalStateException("There are many Illegal information, please check !!!"); } } else { + jarFile.delete(); throw new IllegalStateException("There is none class !!!"); } } catch (Exception e) { From d7eb2a2816ec7557ec76a2f5ac337af0bd61f8f1 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 3 Jul 2019 10:52:09 +0800 Subject: [PATCH 007/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E6=89=93=E5=8C=85=E5=BC=82=E5=B8=B8=E5=88=A0=E9=99=A4=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jd/blockchain/contract/maven/ContractVerifyMojo.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java index b0de7911..2ed416bc 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java @@ -74,7 +74,13 @@ public class ContractVerifyMojo extends AbstractMojo { File jarFile = copyAndManage(); // 首先校验MainClass - verifyMainClass(jarFile); + try { + verifyMainClass(jarFile); + } catch (Exception e) { + jarFile.delete(); + LOG.error(e.getMessage()); + throw e; + } Properties config = loadConfig(); From 0722408d37096507e451e30331d61b72a2b4f78b Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 3 Jul 2019 19:33:52 +0800 Subject: [PATCH 008/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E4=B8=BA=E5=9C=A8assembly?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E4=B8=8A=E8=B0=83=E7=94=A8=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0ReadME.MD=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contract/contract-maven-plugin/ReadME.MD | 274 +++++++++++ source/contract/contract-maven-plugin/pom.xml | 65 +-- .../contract/maven/ContractCheckMojo.java | 432 +++++++++--------- .../contract/maven/ContractCompileMojo.java | 30 ++ .../contract/maven/ContractDeployExeUtil.java | 332 +++++++------- .../contract/maven/ContractDeployMojo.java | 239 +++++----- ...fyMojo.java => ContractResolveEngine.java} | 121 ++--- .../main/resources/sys-contract.properties | 19 - .../ledger/ContractDeployMojoTest.java | 42 +- .../blockchain/ledger/ContractTestBase.java | 100 ++-- .../ledger/ContractVerifyMojoTest.java | 56 +-- .../ledger/ContractVerifyTest_.java | 94 ++-- .../jd/blockchain/ledger/MyProjectStub.java | 134 +++--- 13 files changed, 1085 insertions(+), 853 deletions(-) create mode 100644 source/contract/contract-maven-plugin/ReadME.MD create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java rename source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/{ContractVerifyMojo.java => ContractResolveEngine.java} (80%) delete mode 100644 source/contract/contract-maven-plugin/src/main/resources/sys-contract.properties diff --git a/source/contract/contract-maven-plugin/ReadME.MD b/source/contract/contract-maven-plugin/ReadME.MD new file mode 100644 index 00000000..fce27392 --- /dev/null +++ b/source/contract/contract-maven-plugin/ReadME.MD @@ -0,0 +1,274 @@ +# 合约编译插件使用教程 + +### 1、maven引入 + +在pom.xml文件中引入合约编译插件: +```xml + + com.jd.blockchain + contract-maven-plugin + 1.0.0.RELEASE + + + make-contract + package + + compile + + + + + + + com.jd.chain.contracts.ContractTestInfImpl + + + contract + + + +``` + +需要说明的几点如下: + + 1)version:请根据实际JDChain发布版本确认,不同版本会有区别; + + 2)executions->execution->id:该值请随意指定; + + 3)executions->execution->phase:建议使用package及其后续阶段(若不了解phase含义,请自行查阅相关信息); + + 4)executions->execution->goals->goal:必须使用compile; + + 5)mainClass:必填,该类为需要发布的合约执行类(注意此处是类,不是接口),必须正确配置; + + 6)finalName:必填,最终在编译正常的情况下,会产生{finalName}-jdchain-contract.jar文件,只有该文件是可以发布到JDChain的合约包; + + +### 2、执行命令 +使用mvn执行命令,下面两种方式均可: + +方式一:只执行contract插件命令 +```shell + mvn clean compile contract:compile +``` + +方式二:直接执行打包命令: +```shell +mvn clean package +``` + + +### 3、合约编写要求 +合约的执行结果会对整条链产生比较深刻的影响,为了使用户能够更好、更合理的使用合约,目前JDChain约定合约编写规则包括以下几点: + +(违反其中任何一点都可能导致合约编译失败,但即使合约编译通过也不能保证合约可百分百运行正常) + + + 1)合约工程必须引入com.jd.blockchain:sdk-pack:该包中有合约正常编写需要使用的基本类; + + 2)com.jd.blockchain:sdk-pack的scope必须定义为provided; + + 3)合约发布必须通过合约编译插件进行打包:合约编译插件不但会对Jar包进行校验,同时也会加入JDChain独有的特征,只有具有该特征的Jar才能正常发布; + + 4)合约中严禁使用随机数、IO、NIO等操作; + + 5)合约打包时,请使用provided排除常用的工具包,例如FastJson、apache下的一些工具包等; + + 6)合约必须有一个接口和该接口的实现类,详细要求如下: + - a. 接口必须有@Contract注解; + - b. 接口的可调用方法上必须有@ContractEvent注解,且每个注解中的name属性不能重复; + - c. 合约方法支持入参和返回值,其主要包括所有基本类型; + + + +### 4、合约案例 + +#### 4.1、代码实例 +以下是一个可创建银行账户,指定具体金额,并可以转账的合约代码(逻辑较简单,仅供参考): + +合约接口代码如下: +```java +package com.jd.chain.contract; + + +@Contract +public interface TransferContract { + + @ContractEvent(name = "create") + String create(String address, String account, long money); + + @ContractEvent(name = "transfer") + String transfer(String address, String from, String to, long money); + + @ContractEvent(name = "read") + long read(String address, String account); + + @ContractEvent(name = "readAll") + String readAll(String address, String account); +} + +``` + +合约实现类代码如下: +```java +package com.jd.chain.contract; + +import com.alibaba.fastjson.JSON; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.KVDataVO; +import com.jd.blockchain.ledger.KVInfoVO; + +public class TransferContractImpl implements EventProcessingAware, TransferContract { + + private ContractEventContext eventContext; + + private HashDigest ledgerHash; + + @Override + public String create(String address, String account, long money) { + KVDataEntry[] kvDataEntries = eventContext.getLedger().getDataEntries(ledgerHash, address, account); + // 肯定有返回值,但若不存在则返回version=-1 + if (kvDataEntries != null && kvDataEntries.length > 0) { + long currVersion = kvDataEntries[0].getVersion(); + if (currVersion > -1) { + throw new IllegalStateException(String.format("%s -> %s already have created !!!", address, account)); + } + eventContext.getLedger().dataAccount(address).setInt64(account, money, -1L); + } else { + throw new IllegalStateException(String.format("Ledger[%s] inner Error !!!", ledgerHash.toBase58())); + } + return String.format("DataAccountAddress[%s] -> Create(By Contract Operation) Account = %s and Money = %s Success!!! \r\n", + address, account, money); + } + + @Override + public String transfer(String address, String from, String to, long money) { + // 首先查询余额 + KVDataEntry[] kvDataEntries = eventContext.getLedger().getDataEntries(ledgerHash, address, from, to); + if (kvDataEntries == null || kvDataEntries.length != 2) { + throw new IllegalStateException(String.format("%s -> %s - %s may be not created !!!", address, from, to)); + } else { + // 判断from账号中钱数量是否足够 + long fromMoney = 0L, toMoney = 0L, fromVersion = 0L, toVersion = 0L; + for (KVDataEntry kvDataEntry : kvDataEntries) { + if (kvDataEntry.getKey().equals(from)) { + fromMoney = (long) kvDataEntry.getValue(); + fromVersion = kvDataEntry.getVersion(); + } else { + toMoney = (long) kvDataEntry.getValue(); + toVersion = kvDataEntry.getVersion(); + } + } + if (fromMoney < money) { + throw new IllegalStateException(String.format("%s -> %s not have enough money !!!", address, from)); + } + long fromNewMoney = fromMoney - money; + long toNewMoney = toMoney + money; + // 重新设置 + eventContext.getLedger().dataAccount(address).setInt64(from, fromNewMoney, fromVersion); + eventContext.getLedger().dataAccount(address).setInt64(to, toNewMoney, toVersion); + } + + return String.format("DataAccountAddress[%s] transfer from [%s] to [%s] and [money = %s] Success !!!", address, from, to, money); + } + + @Override + public long read(String address, String account) { + KVDataEntry[] kvDataEntries = eventContext.getLedger().getDataEntries(ledgerHash, address, account); + if (kvDataEntries == null || kvDataEntries.length == 0) { + return -1; + } + return (long)kvDataEntries[0].getValue(); + } + + @Override + public String readAll(String address, String account) { + KVDataEntry[] kvDataEntries = eventContext.getLedger().getDataEntries(ledgerHash, address, account); + // 获取最新的版本号 + if (kvDataEntries == null || kvDataEntries.length == 0) { + return ""; + } + long newestVersion = kvDataEntries[0].getVersion(); + if (newestVersion == -1) { + return ""; + } + KVDataVO[] kvDataVOS = new KVDataVO[1]; + long[] versions = new long[(int)newestVersion + 1]; + for (int i = 0; i < versions.length; i++) { + versions[i] = i; + } + KVDataVO kvDataVO = new KVDataVO(account, versions); + + kvDataVOS[0] = kvDataVO; + + KVInfoVO kvInfoVO = new KVInfoVO(kvDataVOS); + + KVDataEntry[] allEntries = eventContext.getLedger().getDataEntries(ledgerHash, address, kvInfoVO); + + return JSON.toJSONString(allEntries); + } + + @Override + public void beforeEvent(ContractEventContext eventContext) { + this.eventContext = eventContext; + this.ledgerHash = eventContext.getCurrentLedgerHash(); + } + + @Override + public void postEvent(ContractEventContext eventContext, Exception error) { + + } +} +``` + + +#### 4.2、pom.xml文件实例 + +```xml + + + + + com.jd.chain + 1.0.0.RELEASE + 4.0.0 + + contract-samples + + contract-samples + + + + com.jd.blockchain + sdk-pack + 1.0.0.RELEASE + provided + + + + com.alibaba + fastjson + 1.2.32 + provided + + + + + + + com.jd.blockchain + contract-maven-plugin + 1.0.0.RELEASE + + + make-contract + package + + compile + + + + + + + com.jd.chain.contract.TransferContractImpl + + + contract + + + + + + +``` \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/pom.xml b/source/contract/contract-maven-plugin/pom.xml index 63f0e64c..6cca1389 100644 --- a/source/contract/contract-maven-plugin/pom.xml +++ b/source/contract/contract-maven-plugin/pom.xml @@ -10,10 +10,6 @@ contract-maven-plugin maven-plugin - - 3.3.9 - - com.jd.blockchain @@ -42,56 +38,24 @@ com.github.javaparser javaparser-core - ${javaparser.version} - org.apache.maven maven-plugin-api 3.3.9 - - org.apache.maven - maven-core - 3.3.9 - - - org.apache.maven - maven-artifact - 3.3.9 - provided - - - org.apache.maven - maven-compat - 3.3.9 - - - junit - junit - 4.12 - test - - - org.apache.maven.plugin-testing - maven-plugin-testing-harness - test - 3.3.0 - - org.apache.maven.plugin-tools maven-plugin-annotations 3.6.0 - provided - org.apache.maven.shared - maven-invoker - 3.0.1 + org.apache.maven.plugins + maven-assembly-plugin + 2.6 @@ -103,27 +67,6 @@ maven-plugin-plugin 3.5 - - + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java index 2cd5dba6..81e5f272 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java @@ -1,216 +1,216 @@ -package com.jd.blockchain.contract.maven; - -import org.apache.commons.io.FileUtils; -import org.apache.maven.model.Model; -import org.apache.maven.model.Plugin; -import org.apache.maven.model.PluginExecution; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.model.io.xpp3.MavenXpp3Writer; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.invoker.*; -import org.codehaus.plexus.util.xml.Xpp3Dom; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -@Mojo(name = "contractCheck") -public class ContractCheckMojo extends AbstractMojo { - - Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); - - public static final String CONTRACT_VERIFY = "contractVerify"; - - private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; - - private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; - - private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; - - private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; - - private static final String GOALS_VERIFY = "package"; - - private static final String GOALS_PACKAGE = "package"; - - private static final String OUT_POM_XML = "ContractPom.xml"; - - @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; - - /** - * jar's name; - */ - @Parameter - private String finalName; - - /** - * mainClass; - */ - @Parameter - private String mainClass; - /** - * ledgerVersion; - */ - @Parameter - private String ledgerVersion; - - /** - * mvnHome; - */ - @Parameter - private String mvnHome; - - /** - * first compile the class, then parse it; - */ - @Override - public void execute() throws MojoFailureException { - compileFiles(); - } - - private void compileFiles() throws MojoFailureException { - try (FileInputStream fis = new FileInputStream(project.getFile())) { - - MavenXpp3Reader reader = new MavenXpp3Reader(); - Model model = reader.read(fis); - - //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; - Plugin plugin = model.getBuild().getPluginsAsMap() - .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); - if(plugin == null){ - plugin = model.getBuild().getPluginsAsMap() - .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); - } - - if(plugin == null) { - return; - } - - model.getBuild().removePlugin(plugin); - - List plugins = new ArrayList<>(); - plugins.add(createAssembly()); - plugins.add(createContractVerify()); - - model.getBuild().setPlugins(plugins); - - handle(model); - - } catch (Exception e) { - LOG.error(e.getMessage()); - throw new MojoFailureException(e.getMessage()); - } - } - - private void invokeCompile(File file) { - InvocationRequest request = new DefaultInvocationRequest(); - - Invoker invoker = new DefaultInvoker(); - try { - request.setPomFile(file); - - request.setGoals(Collections.singletonList(GOALS_VERIFY)); - invoker.setMavenHome(new File(mvnHome)); - invoker.execute(request); - } catch (MavenInvocationException e) { - LOG.error(e.getMessage()); - throw new IllegalStateException(e); - } - } - - private Plugin createContractVerify() { - Plugin plugin = new Plugin(); - plugin.setGroupId(JDCHAIN_PACKAGE); - plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); - plugin.setVersion(ledgerVersion); - - Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); - finalNameNode.setValue(finalName); - Xpp3Dom configuration = new Xpp3Dom("configuration"); - configuration.addChild(finalNameNode); - - plugin.setConfiguration(configuration); - plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); - - return plugin; - } - - private Plugin createAssembly() { - Plugin plugin = new Plugin(); - plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); - - Xpp3Dom configuration = new Xpp3Dom("configuration"); - - Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); - mainClassNode.setValue(mainClass); - - Xpp3Dom manifest = new Xpp3Dom("manifest"); - manifest.addChild(mainClassNode); - - Xpp3Dom archive = new Xpp3Dom("archive"); - archive.addChild(manifest); - - Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); - finalNameNode.setValue(finalName); - - Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); - appendAssemblyId.setValue("false"); - - Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); - descriptorRef.setValue("jar-with-dependencies"); - Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); - descriptorRefs.addChild(descriptorRef); - - configuration.addChild(finalNameNode); - configuration.addChild(appendAssemblyId); - configuration.addChild(archive); - configuration.addChild(descriptorRefs); - - plugin.setConfiguration(configuration); - plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); - - return plugin; - } - - private List pluginExecution(String id, String phase, String goal) { - PluginExecution pluginExecution = new PluginExecution(); - pluginExecution.setId(id); - pluginExecution.setPhase(phase); - List goals = new ArrayList<>(); - goals.add(goal); - pluginExecution.setGoals(goals); - List pluginExecutions = new ArrayList<>(); - pluginExecutions.add(pluginExecution); - - return pluginExecutions; - } - - private void handle(Model model) throws IOException { - - MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - mavenXpp3Writer.write(outputStream, model); - - byte[] buffer = outputStream.toByteArray(); - - File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); - - FileUtils.writeByteArrayToFile(outPom, buffer); - - invokeCompile(outPom); - } -} +//package com.jd.blockchain.contract.maven; +// +//import org.apache.commons.io.FileUtils; +//import org.apache.maven.model.Model; +//import org.apache.maven.model.Plugin; +//import org.apache.maven.model.PluginExecution; +//import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +//import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +//import org.apache.maven.plugin.AbstractMojo; +//import org.apache.maven.plugin.MojoFailureException; +//import org.apache.maven.plugins.annotations.Mojo; +//import org.apache.maven.plugins.annotations.Parameter; +//import org.apache.maven.project.MavenProject; +//import org.apache.maven.shared.invoker.*; +//import org.codehaus.plexus.util.xml.Xpp3Dom; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.ByteArrayOutputStream; +//import java.io.File; +//import java.io.FileInputStream; +//import java.io.IOException; +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.List; +// +// +//@Mojo(name = "contractCheck") +//public class ContractCheckMojo extends AbstractMojo { +// +// Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); +// +// public static final String CONTRACT_VERIFY = "contractVerify"; +// +// private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; +// +// private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; +// +// private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; +// +// private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; +// +// private static final String GOALS_VERIFY = "package"; +// +// private static final String GOALS_PACKAGE = "package"; +// +// private static final String OUT_POM_XML = "ContractPom.xml"; +// +// @Parameter(defaultValue = "${project}", required = true, readonly = true) +// private MavenProject project; +// +// /** +// * jar's name; +// */ +// @Parameter +// private String finalName; +// +// /** +// * mainClass; +// */ +// @Parameter +// private String mainClass; +// /** +// * ledgerVersion; +// */ +// @Parameter +// private String ledgerVersion; +// +// /** +// * mvnHome; +// */ +// @Parameter +// private String mvnHome; +// +// /** +// * first compile the class, then parse it; +// */ +// @Override +// public void execute() throws MojoFailureException { +// compileFiles(); +// } +// +// private void compileFiles() throws MojoFailureException { +// try (FileInputStream fis = new FileInputStream(project.getFile())) { +// +// MavenXpp3Reader reader = new MavenXpp3Reader(); +// Model model = reader.read(fis); +// +// //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; +// Plugin plugin = model.getBuild().getPluginsAsMap() +// .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); +// if(plugin == null){ +// plugin = model.getBuild().getPluginsAsMap() +// .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); +// } +// +// if(plugin == null) { +// return; +// } +// +// model.getBuild().removePlugin(plugin); +// +// List plugins = new ArrayList<>(); +// plugins.add(createAssembly()); +// plugins.add(createContractVerify()); +// +// model.getBuild().setPlugins(plugins); +// +// handle(model); +// +// } catch (Exception e) { +// LOG.error(e.getMessage()); +// throw new MojoFailureException(e.getMessage()); +// } +// } +// +// private void invokeCompile(File file) { +// InvocationRequest request = new DefaultInvocationRequest(); +// +// Invoker invoker = new DefaultInvoker(); +// try { +// request.setPomFile(file); +// +// request.setGoals(Collections.singletonList(GOALS_VERIFY)); +// invoker.setMavenHome(new File(mvnHome)); +// invoker.execute(request); +// } catch (MavenInvocationException e) { +// LOG.error(e.getMessage()); +// throw new IllegalStateException(e); +// } +// } +// +// private Plugin createContractVerify() { +// Plugin plugin = new Plugin(); +// plugin.setGroupId(JDCHAIN_PACKAGE); +// plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); +// plugin.setVersion(ledgerVersion); +// +// Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); +// finalNameNode.setValue(finalName); +// Xpp3Dom configuration = new Xpp3Dom("configuration"); +// configuration.addChild(finalNameNode); +// +// plugin.setConfiguration(configuration); +// plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); +// +// return plugin; +// } +// +// private Plugin createAssembly() { +// Plugin plugin = new Plugin(); +// plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); +// +// Xpp3Dom configuration = new Xpp3Dom("configuration"); +// +// Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); +// mainClassNode.setValue(mainClass); +// +// Xpp3Dom manifest = new Xpp3Dom("manifest"); +// manifest.addChild(mainClassNode); +// +// Xpp3Dom archive = new Xpp3Dom("archive"); +// archive.addChild(manifest); +// +// Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); +// finalNameNode.setValue(finalName); +// +// Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); +// appendAssemblyId.setValue("false"); +// +// Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); +// descriptorRef.setValue("jar-with-dependencies"); +// Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); +// descriptorRefs.addChild(descriptorRef); +// +// configuration.addChild(finalNameNode); +// configuration.addChild(appendAssemblyId); +// configuration.addChild(archive); +// configuration.addChild(descriptorRefs); +// +// plugin.setConfiguration(configuration); +// plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); +// +// return plugin; +// } +// +// private List pluginExecution(String id, String phase, String goal) { +// PluginExecution pluginExecution = new PluginExecution(); +// pluginExecution.setId(id); +// pluginExecution.setPhase(phase); +// List goals = new ArrayList<>(); +// goals.add(goal); +// pluginExecution.setGoals(goals); +// List pluginExecutions = new ArrayList<>(); +// pluginExecutions.add(pluginExecution); +// +// return pluginExecutions; +// } +// +// private void handle(Model model) throws IOException { +// +// MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); +// +// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +// +// mavenXpp3Writer.write(outputStream, model); +// +// byte[] buffer = outputStream.toByteArray(); +// +// File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); +// +// FileUtils.writeByteArrayToFile(outPom, buffer); +// +// invokeCompile(outPom); +// } +//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java new file mode 100644 index 00000000..b0304ba0 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java @@ -0,0 +1,30 @@ +package com.jd.blockchain.contract.maven; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.assembly.mojos.SingleAssemblyMojo; +import org.apache.maven.plugins.annotations.Mojo; + +@Mojo(name = "compile") +public class ContractCompileMojo extends SingleAssemblyMojo { + + public static final String JAR_DEPENDENCE = "jar-with-dependencies"; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + // 要求必须有MainClass + try { + String mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); + super.getLog().debug("MainClass is " + mainClass); + } catch (Exception e) { + throw new MojoFailureException("MainClass is null: " + e.getMessage(), e ); + } + super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); + + super.execute(); + + ContractResolveEngine engine = new ContractResolveEngine(getLog(), getProject(), getFinalName()); + + engine.compileAndVerify(); + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java index 10aada08..bd6a1b23 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java @@ -1,176 +1,176 @@ -package com.jd.blockchain.contract.maven; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.tools.keygen.KeyGenCommand; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.net.NetworkAddress; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * @Author zhaogw - * @Date 2018/11/2 10:18 - */ -public enum ContractDeployExeUtil { - instance; - private BlockchainService bcsrv; - private Bytes contractAddress; - - public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ - PubKey pub = null; - PrivKey prv = null; - try { - prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); - pub = KeyGenCommand.readPubKey(pubPath); - - } catch (Exception e) { - e.printStackTrace(); - } - - return new BlockchainKeypair(pub, prv); - } - - public PubKey getPubKey(String pubPath){ - PubKey pub = null; - try { - if(pubPath == null){ - BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); - pub = contractKeyPair.getPubKey(); - }else { - pub = KeyGenCommand.readPubKey(pubPath); - } - - } catch (Exception e) { - e.printStackTrace(); - } - - return pub; - } - public byte[] getChainCode(String path){ - byte[] chainCode = null; - File file = null; - InputStream input = null; - try { - file = new File(path); - input = new FileInputStream(file); - chainCode = new byte[input.available()]; - input.read(chainCode); - - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if(input!=null){ - input.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return chainCode; - } - - private void register(){ - DataContractRegistry.register(TransactionContent.class); - DataContractRegistry.register(TransactionContentBody.class); - DataContractRegistry.register(TransactionRequest.class); - DataContractRegistry.register(NodeRequest.class); - DataContractRegistry.register(EndpointRequest.class); - DataContractRegistry.register(TransactionResponse.class); - DataContractRegistry.register(DataAccountKVSetOperation.class); - DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); - DataContractRegistry.register(Operation.class); - DataContractRegistry.register(ContractCodeDeployOperation.class); - DataContractRegistry.register(ContractEventSendOperation.class); - DataContractRegistry.register(DataAccountRegisterOperation.class); - DataContractRegistry.register(UserRegisterOperation.class); - } - - public BlockchainService initBcsrv(String host, int port) { - if(bcsrv!=null){ - return bcsrv; - } - NetworkAddress addr = new NetworkAddress(host, port); - GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); - bcsrv = gwsrvFact.getBlockchainService(); - return bcsrv; - } - - public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ - register(); - - TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); - txTpl.contracts().deploy(contractIdentity, chainCode); - PreparedTransaction ptx = txTpl.prepare(); - ptx.sign(ownerKey); - // 提交并等待共识返回; - TransactionResponse txResp = ptx.commit(); - - // 验证结果; - contractAddress = contractIdentity.getAddress(); - this.setContractAddress(contractAddress); - System.out.println("contract's address="+contractAddress); - return txResp.isSuccess(); - } - public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ - register(); - - BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); - initBcsrv(host,port); - return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); - } - - // 根据用户指定的公钥生成合约地址 - public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, - String ownerPassword, String chainCodePath,String pubPath){ - PubKey pubKey = getPubKey(pubPath); - BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); - byte[] chainCode = getChainCode(chainCodePath); - - BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); - HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); - initBcsrv(host,port); - return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); - } - - -// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; - -// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, -// String ownerPassword,String event,String contractArgs){ -// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); -// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); +//package com.jd.blockchain.contract.maven; // -// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; -// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); -// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); +//import com.jd.blockchain.binaryproto.DataContractRegistry; +//import com.jd.blockchain.crypto.HashDigest; +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +//import com.jd.blockchain.ledger.*; +//import com.jd.blockchain.sdk.BlockchainService; +//import com.jd.blockchain.sdk.client.GatewayServiceFactory; +//import com.jd.blockchain.tools.keygen.KeyGenCommand; +//import com.jd.blockchain.utils.Bytes; +//import com.jd.blockchain.utils.codec.Base58Utils; +//import com.jd.blockchain.utils.net.NetworkAddress; +// +//import java.io.File; +//import java.io.FileInputStream; +//import java.io.IOException; +//import java.io.InputStream; +// +///** +// * @Author zhaogw +// * @Date 2018/11/2 10:18 +// */ +//public enum ContractDeployExeUtil { +// instance; +// private BlockchainService bcsrv; +// private Bytes contractAddress; +// +// public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ +// PubKey pub = null; +// PrivKey prv = null; +// try { +// prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); +// pub = KeyGenCommand.readPubKey(pubPath); +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// return new BlockchainKeypair(pub, prv); +// } +// +// public PubKey getPubKey(String pubPath){ +// PubKey pub = null; +// try { +// if(pubPath == null){ +// BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); +// pub = contractKeyPair.getPubKey(); +// }else { +// pub = KeyGenCommand.readPubKey(pubPath); +// } +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// return pub; +// } +// public byte[] getChainCode(String path){ +// byte[] chainCode = null; +// File file = null; +// InputStream input = null; +// try { +// file = new File(path); +// input = new FileInputStream(file); +// chainCode = new byte[input.available()]; +// input.read(chainCode); +// +// } catch (IOException e) { +// e.printStackTrace(); +// } finally { +// try { +// if(input!=null){ +// input.close(); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// return chainCode; +// } +// +// private void register(){ +// DataContractRegistry.register(TransactionContent.class); +// DataContractRegistry.register(TransactionContentBody.class); +// DataContractRegistry.register(TransactionRequest.class); +// DataContractRegistry.register(NodeRequest.class); +// DataContractRegistry.register(EndpointRequest.class); +// DataContractRegistry.register(TransactionResponse.class); +// DataContractRegistry.register(DataAccountKVSetOperation.class); +// DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); +// DataContractRegistry.register(Operation.class); +// DataContractRegistry.register(ContractCodeDeployOperation.class); +// DataContractRegistry.register(ContractEventSendOperation.class); +// DataContractRegistry.register(DataAccountRegisterOperation.class); +// DataContractRegistry.register(UserRegisterOperation.class); +// } +// +// public BlockchainService initBcsrv(String host, int port) { +// if(bcsrv!=null){ +// return bcsrv; +// } +// NetworkAddress addr = new NetworkAddress(host, port); +// GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); +// bcsrv = gwsrvFact.getBlockchainService(); +// return bcsrv; +// } +// +// public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ +// register(); // -// // 签名; +// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); +// txTpl.contracts().deploy(contractIdentity, chainCode); // PreparedTransaction ptx = txTpl.prepare(); // ptx.sign(ownerKey); -// // // 提交并等待共识返回; // TransactionResponse txResp = ptx.commit(); // // // 验证结果; +// contractAddress = contractIdentity.getAddress(); +// this.setContractAddress(contractAddress); +// System.out.println("contract's address="+contractAddress); // return txResp.isSuccess(); // } - - public Bytes getContractAddress() { - return contractAddress; - } - - public void setContractAddress(Bytes contractAddress) { - this.contractAddress = contractAddress; - } -} +// public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ +// register(); +// +// BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); +// initBcsrv(host,port); +// return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); +// } +// +// // 根据用户指定的公钥生成合约地址 +// public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, +// String ownerPassword, String chainCodePath,String pubPath){ +// PubKey pubKey = getPubKey(pubPath); +// BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); +// byte[] chainCode = getChainCode(chainCodePath); +// +// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); +// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); +// initBcsrv(host,port); +// return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); +// } +// +// +//// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; +// +//// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, +//// String ownerPassword,String event,String contractArgs){ +//// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); +//// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); +//// +//// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; +//// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); +//// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); +//// +//// // 签名; +//// PreparedTransaction ptx = txTpl.prepare(); +//// ptx.sign(ownerKey); +//// +//// // 提交并等待共识返回; +//// TransactionResponse txResp = ptx.commit(); +//// +//// // 验证结果; +//// return txResp.isSuccess(); +//// } +// +// public Bytes getContractAddress() { +// return contractAddress; +// } +// +// public void setContractAddress(Bytes contractAddress) { +// this.contractAddress = contractAddress; +// } +//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java index 25da2357..89a82a36 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java @@ -1,119 +1,120 @@ -package com.jd.blockchain.contract.maven; - -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.tools.keygen.KeyGenCommand; -import com.jd.blockchain.utils.StringUtils; -import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.io.FileUtils; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -/** - * for contract remote deploy; - * @goal contractDeploy - * @phase process-sources - * @Author zhaogw - * @Date 2018/10/18 10:12 - */ - -@Mojo(name = "deploy") -public class ContractDeployMojo extends AbstractMojo { - Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); - - @Parameter - private File config; - - @Override - public void execute()throws MojoFailureException { - Properties prop = new Properties(); - InputStream input = null; - - try { - input = new FileInputStream(config); - prop.load(input); - - } catch (IOException ex) { - logger.error(ex.getMessage()); - throw new MojoFailureException("io error"); - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException e) { - logger.error(e.getMessage()); - } - } - } - int port; - try { - port = Integer.parseInt(prop.getProperty("port")); - }catch (NumberFormatException e){ - logger.error(e.getMessage()); - throw new MojoFailureException("invalid port"); - } - String host = prop.getProperty("host"); - String ledger = prop.getProperty("ledger"); - String pubKey = prop.getProperty("pubKey"); - String prvKey = prop.getProperty("prvKey"); - String password = prop.getProperty("password"); - String contractPath = prop.getProperty("contractPath"); - - - if(StringUtils.isEmpty(host)){ - logger.info("host不能为空"); - return; - } - - if(StringUtils.isEmpty(ledger)){ - logger.info("ledger不能为空."); - return; - } - if(StringUtils.isEmpty(pubKey)){ - logger.info("pubKey不能为空."); - return; - } - if(StringUtils.isEmpty(prvKey)){ - logger.info("prvKey不能为空."); - return; - } - if(StringUtils.isEmpty(contractPath)){ - logger.info("contractPath不能为空."); - return; - } - - File contract = new File(contractPath); - if (!contract.isFile()){ - logger.info("文件"+contractPath+"不存在"); - return; - } - byte[] contractBytes = FileUtils.readBytes(contractPath); - - - PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); - PubKey pub = KeyGenCommand.decodePubKey(pubKey); - BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); - HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); - - StringBuffer sb = new StringBuffer(); - sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). - append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); - logger.info(sb.toString()); - ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); - } - -} - - +//package com.jd.blockchain.contract.maven; +// +//import com.jd.blockchain.crypto.HashDigest; +//import com.jd.blockchain.crypto.PrivKey; +//import com.jd.blockchain.crypto.PubKey; +//import com.jd.blockchain.ledger.BlockchainKeypair; +//import com.jd.blockchain.tools.keygen.KeyGenCommand; +//import com.jd.blockchain.utils.StringUtils; +//import com.jd.blockchain.utils.codec.Base58Utils; +//import com.jd.blockchain.utils.io.FileUtils; +//import org.apache.maven.plugin.AbstractMojo; +//import org.apache.maven.plugin.MojoFailureException; +//import org.apache.maven.plugins.annotations.Mojo; +//import org.apache.maven.plugins.annotations.Parameter; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.File; +//import java.io.FileInputStream; +//import java.io.IOException; +//import java.io.InputStream; +//import java.util.Properties; +// +///** +// * for contract remote deploy; +// * @goal contractDeploy +// * @phase process-sources +// * @Author zhaogw +// * @Date 2018/10/18 10:12 +// */ +// +//@Mojo(name = "deploy") +//public class ContractDeployMojo extends AbstractMojo { +// Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); +// +// @Parameter +// private File config; +// +// @Override +// public void execute()throws MojoFailureException { +// +// Properties prop = new Properties(); +// InputStream input = null; +// +// try { +// input = new FileInputStream(config); +// prop.load(input); +// +// } catch (IOException ex) { +// logger.error(ex.getMessage()); +// throw new MojoFailureException("io error"); +// } finally { +// if (input != null) { +// try { +// input.close(); +// } catch (IOException e) { +// logger.error(e.getMessage()); +// } +// } +// } +// int port; +// try { +// port = Integer.parseInt(prop.getProperty("port")); +// }catch (NumberFormatException e){ +// logger.error(e.getMessage()); +// throw new MojoFailureException("invalid port"); +// } +// String host = prop.getProperty("host"); +// String ledger = prop.getProperty("ledger"); +// String pubKey = prop.getProperty("pubKey"); +// String prvKey = prop.getProperty("prvKey"); +// String password = prop.getProperty("password"); +// String contractPath = prop.getProperty("contractPath"); +// +// +// if(StringUtils.isEmpty(host)){ +// logger.info("host不能为空"); +// return; +// } +// +// if(StringUtils.isEmpty(ledger)){ +// logger.info("ledger不能为空."); +// return; +// } +// if(StringUtils.isEmpty(pubKey)){ +// logger.info("pubKey不能为空."); +// return; +// } +// if(StringUtils.isEmpty(prvKey)){ +// logger.info("prvKey不能为空."); +// return; +// } +// if(StringUtils.isEmpty(contractPath)){ +// logger.info("contractPath不能为空."); +// return; +// } +// +// File contract = new File(contractPath); +// if (!contract.isFile()){ +// logger.info("文件"+contractPath+"不存在"); +// return; +// } +// byte[] contractBytes = FileUtils.readBytes(contractPath); +// +// +// PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); +// PubKey pub = KeyGenCommand.decodePubKey(pubKey); +// BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); +// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); +// +// StringBuffer sb = new StringBuffer(); +// sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). +// append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); +// logger.info(sb.toString()); +// ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); +// } +// +//} +// +// diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java similarity index 80% rename from source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java rename to source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java index 2ed416bc..7ce0b0a1 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java @@ -6,13 +6,9 @@ import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.jd.blockchain.contract.ContractType; import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -24,32 +20,11 @@ import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import static com.jd.blockchain.contract.maven.ContractCheckMojo.CONTRACT_VERIFY; +import static com.jd.blockchain.contract.maven.ContractCompileMojo.JAR_DEPENDENCE; import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; import static com.jd.blockchain.utils.jar.ContractJarUtils.*; -/** - * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. - * This is a try of "from Initail to Abandoned". - * Since we are good at the class, why not? - * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. - * - * by zhaogw - * date 2019-06-05 16:17 - */ -@Mojo(name = CONTRACT_VERIFY) -public class ContractVerifyMojo extends AbstractMojo { - - Logger LOG = LoggerFactory.getLogger(ContractVerifyMojo.class); - - @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; - - /** - * jar's name; - */ - @Parameter - private String finalName; +public class ContractResolveEngine { private static final String JAVA_SUFFIX = ".java"; @@ -66,19 +41,49 @@ public class ContractVerifyMojo extends AbstractMojo { private static final String BLACK_NAME_LIST = "black.name.list"; - @Override - public void execute() throws MojoExecutionException { + private Log LOGGER; + private MavenProject project; + + private String finalName; + + public ContractResolveEngine(Log LOGGER, MavenProject project, String finalName) { + this.LOGGER = LOGGER; + this.project = project; + this.finalName = finalName; + } + + public void compileAndVerify() throws MojoFailureException { try { + jarCopy(); + verify(compileCustomJar()); + } catch (IOException e) { + throw new MojoFailureException("IO Error : " + e.getMessage(), e); + } catch (MojoFailureException ex) { + throw ex; + } + } - File jarFile = copyAndManage(); + private void jarCopy() throws IOException { + String srcJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-" + JAR_DEPENDENCE + ".jar"; + String dstJarPath = project.getBuild().getDirectory() + + File.separator + finalName + ".jar"; + FileUtils.copyFile(new File(srcJarPath), new File(dstJarPath)); + } + + private File compileCustomJar() throws IOException { + return copyAndManage(project, finalName); + } + private void verify(File jarFile) throws MojoFailureException { + try { // 首先校验MainClass try { verifyMainClass(jarFile); } catch (Exception e) { jarFile.delete(); - LOG.error(e.getMessage()); + LOGGER.error(e.getMessage()); throw e; } @@ -101,9 +106,13 @@ public class ContractVerifyMojo extends AbstractMojo { boolean isOK = true; for (String clazz : totalClassList) { + + LOGGER.debug(String.format("Verify Class[%s] start......", clazz)); // 获取其包名 String packageName = packageName(clazz); + LOGGER.debug(String.format("Class[%s] 's package name = %s", clazz, packageName)); + // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 boolean isNameBlack = false; for (ContractPackage blackName : blackNameList) { @@ -116,7 +125,7 @@ public class ContractVerifyMojo extends AbstractMojo { // 假设是黑名单则打印日志 if (isNameBlack) { // 打印信息供检查 - LOG.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); + LOGGER.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); isOK = false; continue; } @@ -126,6 +135,7 @@ public class ContractVerifyMojo extends AbstractMojo { boolean isNeedDelete = false; if (!javaFile.exists()) { + LOGGER.debug(String.format("Class[%s] -> Java[%s] is not exist, start decompile ...", clazz, jarFile.getPath())); // 表明不是项目中的内容,需要通过反编译获取该文件 String source = null; try { @@ -134,15 +144,18 @@ public class ContractVerifyMojo extends AbstractMojo { throw new IllegalStateException(); } } catch (Exception e) { - LOG.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); + LOGGER.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); } // 将source写入Java文件 File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); javaFile = sourceTempJavaFile; isNeedDelete = true; + } else { + LOGGER.debug(String.format("Class[%s] -> Java[%s] is exist", clazz, jarFile.getPath())); } + LOGGER.info(String.format("Parse Java File [%s] start......", javaFile.getPath())); // 解析文件中的内容 CompilationUnit compilationUnit = JavaParser.parse(javaFile); @@ -154,13 +167,14 @@ public class ContractVerifyMojo extends AbstractMojo { if (!imports.isEmpty()) { for (String importClass : imports) { + LOGGER.debug(String.format("Class[%s] read import -> [%s]", clazz, importClass)); if (importClass.endsWith("*")) { // 导入的是包 for (ContractPackage blackPackage : blackPackageList) { String importPackageName = importClass.substring(0, importClass.length() - 2); if (verifyPackage(importPackageName, blackPackage)) { // 打印信息供检查 - LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); isOK = false; break; } @@ -169,13 +183,13 @@ public class ContractVerifyMojo extends AbstractMojo { // 导入的是具体的类,则判断类黑名单 + 包黑名单 if (blackClassSet.contains(importClass)) { // 包含导入类,该方式无法通过验证 - LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); + LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); isOK = false; } else { // 判断导入的该类与黑名单导入包的对应关系 for (ContractPackage blackPackage : blackPackageList) { if (verifyClass(importClass, blackPackage)) { - LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); isOK = false; break; } @@ -187,6 +201,7 @@ public class ContractVerifyMojo extends AbstractMojo { if (isNeedDelete) { javaFile.delete(); } + LOGGER.debug(String.format("Verify Class[%s] end......", clazz)); } if (!isOK) { // 需要将该Jar删除 @@ -198,19 +213,21 @@ public class ContractVerifyMojo extends AbstractMojo { throw new IllegalStateException("There is none class !!!"); } } catch (Exception e) { - LOG.error(e.getMessage()); - throw new MojoExecutionException(e.getMessage()); + LOGGER.error(e.getMessage()); + throw new MojoFailureException(e.getMessage(), e); } } private void verifyMainClass(File jarFile) throws Exception { // 加载main-class,开始校验类型 + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass start...", jarFile.getName())); URL jarURL = jarFile.toURI().toURL(); ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); Class mainClass = classLoader.loadClass(contractMainClass); ContractType.resolve(mainClass); + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", jarFile.getName())); } private List blackNameList(Properties config) { @@ -223,6 +240,7 @@ public class ContractVerifyMojo extends AbstractMojo { if (attrProp != null && attrProp.length() > 0) { String[] attrPropArray = attrProp.split(","); for (String attr : attrPropArray) { + LOGGER.info(String.format("Config [%s] -> [%s]", BLACK_CLASS_LIST, attr)); blackClassSet.add(attr.trim()); } } @@ -239,6 +257,7 @@ public class ContractVerifyMojo extends AbstractMojo { if (attrProp != null || attrProp.length() > 0) { String[] attrPropArray = attrProp.split(","); for (String attr : attrPropArray) { + LOGGER.info(String.format("Config [%s] -> [%s]", attrName, attr)); list.add(new ContractPackage(attr)); } } @@ -310,7 +329,7 @@ public class ContractVerifyMojo extends AbstractMojo { return packageName.replaceAll("/", "."); } - private File copyAndManage() throws IOException { + private File copyAndManage(MavenProject project, String finalName) throws IOException { // 首先将Jar包转换为指定的格式 String srcJarPath = project.getBuild().getDirectory() + File.separator + finalName + ".jar"; @@ -320,8 +339,10 @@ public class ContractVerifyMojo extends AbstractMojo { File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); + LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", srcJarPath, dstJarPath)); // 首先进行Copy处理 copy(srcJar, dstJar); + LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", srcJarPath, dstJarPath)); byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); @@ -376,24 +397,6 @@ public class ContractVerifyMojo extends AbstractMojo { private List importClasses = new ArrayList<>(); -// @Override -// public void visit(MethodDeclaration n, Void arg) { -// /* here you can access the attributes of the method. -// this method will be called for all methods in this -// CompilationUnit, including inner class methods */ -// super.visit(n, arg); -// } -// -// @Override -// public void visit(ClassOrInterfaceDeclaration n, Void arg) { -// super.visit(n, arg); -// } -// -// @Override -// public void visit(PackageDeclaration n, Void arg) { -// super.visit(n, arg); -// } - @Override public void visit(ImportDeclaration n, Void arg) { importClasses.add(parseClass(n.toString())); diff --git a/source/contract/contract-maven-plugin/src/main/resources/sys-contract.properties b/source/contract/contract-maven-plugin/src/main/resources/sys-contract.properties deleted file mode 100644 index a7179906..00000000 --- a/source/contract/contract-maven-plugin/src/main/resources/sys-contract.properties +++ /dev/null @@ -1,19 +0,0 @@ -#PROJECT_BASE_DIR -PROJECT_BASE_DIR=E:\\gitCode\\block\\prototype\\ -#LEDGER_BASE_CLASS_PATH -LEDGER_BASE_CLASS_PATH=E:\\gitCode\\block\\prototype\\libs\\ - -#deploy and execute the contract; -cParam=com.jd.blockchain.contract.AssetContract3 -sParam=E:\\gitCode\\block\\prototype\\source\\sdk\\contract-sample\\src\\main\\java\\ -eParam=utf-8 -oParam=E:\\gitCode\\block\\prototype\\source\\contract\\contract-maven-plugin\\src\\test\\resources\\ -host=127.0.0.1 -port=8081 -event = issue-asset -ownerPassword=E:\\gitCode\\block\\prototype\\source\\contract\\contract-maven-plugin\\conf\\ownerPassword.txt -ownerPubPath=E:\\gitCode\\block\\prototype\\source\\contract\\contract-maven-plugin\\conf\\jd-com.pub -ownerPrvPath=E:\\gitCode\\block\\prototype\\source\\contract\\contract-maven-plugin\\conf\\jd-com.priv -chainCodePath=E:\\gitCode\\block\\prototype\\source\\contract\\contract-maven-plugin\\src\\test\\resources\\AssetContract3.contract -ledgerHash=6FEQDTQMnBGANpfX4haXuWHKHw5cZ6P1h2ocqwckYBxp4 -contractArgs=101##999##abc \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java index 442f1ba0..bb907445 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java @@ -1,21 +1,21 @@ -package com.jd.blockchain.ledger; - -import com.jd.blockchain.contract.maven.ContractDeployMojo; - -import java.lang.reflect.Field; - -/** - * for contract deploy and exe; - * @Author zhaogw - * @Date 2018/11/02 09:06 - */ -public class ContractDeployMojoTest { - private ContractDeployMojo contractDeployMojo = new ContractDeployMojo(); - - private void fieldHandle(String fieldName,Object objValue) throws NoSuchFieldException, IllegalAccessException { - Field field = contractDeployMojo.getClass().getDeclaredField(fieldName);//name为类Instance中的private属性 - field.setAccessible(true);//=true,可访问私有变量。 - Class typeClass = field.getType(); - field.set(contractDeployMojo, objValue); - } -} +//package com.jd.blockchain.ledger; +// +//import com.jd.blockchain.contract.maven.ContractDeployMojo; +// +//import java.lang.reflect.Field; +// +///** +// * for contract deploy and exe; +// * @Author zhaogw +// * @Date 2018/11/02 09:06 +// */ +//public class ContractDeployMojoTest { +// private ContractDeployMojo contractDeployMojo = new ContractDeployMojo(); +// +// private void fieldHandle(String fieldName,Object objValue) throws NoSuchFieldException, IllegalAccessException { +// Field field = contractDeployMojo.getClass().getDeclaredField(fieldName);//name为类Instance中的private属性 +// field.setAccessible(true);//=true,可访问私有变量。 +// Class typeClass = field.getType(); +// field.set(contractDeployMojo, objValue); +// } +//} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java index 09a5010f..e3065766 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractTestBase.java @@ -1,50 +1,50 @@ -package com.jd.blockchain.ledger; - -import org.apache.maven.model.Build; -import org.apache.maven.project.MavenProject; -import org.junit.Test; -import org.springframework.core.io.ClassPathResource; - -import java.io.File; - -public class ContractTestBase { - - public static MavenProject mavenProjectInit() { - MavenProject mavenProject = new MavenProject(); - mavenProject.setBuild(buildInit()); - mavenProject.setFile(file()); - return mavenProject; - } - - public static File file() { - String resDir = resourceDir(); - File file = new File(resDir); - String path = file.getParentFile().getParentFile().getPath(); - return new File(path + File.separator + "src"); - } - - public static Build buildInit() { - Build build = new Build(); - build.setDirectory(resourceDir()); - return build; - } - - public static String resourceDir() { - try { - ClassPathResource classPathResource = new ClassPathResource("complex.jar"); - return classPathResource.getFile().getParentFile().getPath(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @Test - public void testResourceDir() { - System.out.println(resourceDir()); - } - - @Test - public void testFile() { - System.out.println(file().getPath()); - } -} +//package com.jd.blockchain.ledger; +// +//import org.apache.maven.model.Build; +//import org.apache.maven.project.MavenProject; +//import org.junit.Test; +//import org.springframework.core.io.ClassPathResource; +// +//import java.io.File; +// +//public class ContractTestBase { +// +// public static MavenProject mavenProjectInit() { +// MavenProject mavenProject = new MavenProject(); +// mavenProject.setBuild(buildInit()); +// mavenProject.setFile(file()); +// return mavenProject; +// } +// +// public static File file() { +// String resDir = resourceDir(); +// File file = new File(resDir); +// String path = file.getParentFile().getParentFile().getPath(); +// return new File(path + File.separator + "src"); +// } +// +// public static Build buildInit() { +// Build build = new Build(); +// build.setDirectory(resourceDir()); +// return build; +// } +// +// public static String resourceDir() { +// try { +// ClassPathResource classPathResource = new ClassPathResource("complex.jar"); +// return classPathResource.getFile().getParentFile().getPath(); +// } catch (Exception e) { +// throw new IllegalStateException(e); +// } +// } +// +// @Test +// public void testResourceDir() { +// System.out.println(resourceDir()); +// } +// +// @Test +// public void testFile() { +// System.out.println(file().getPath()); +// } +//} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java index 570cc8e6..119f5cbf 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java @@ -1,28 +1,28 @@ -package com.jd.blockchain.ledger; - -import com.jd.blockchain.contract.maven.ContractVerifyMojo; -import org.apache.maven.plugin.testing.AbstractMojoTestCase; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - -/** - * @Author zhaogw - * @Date 2019/3/1 21:27 - */ -public class ContractVerifyMojoTest extends AbstractMojoTestCase { - Logger logger = LoggerFactory.getLogger(ContractVerifyMojoTest.class); - - @Test - public void test1() throws Exception { - File pom = getTestFile( "src/test/resources/project-to-test/pom.xml" ); - assertNotNull( pom ); - assertTrue( pom.exists() ); - - ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "contractVerify", pom ); - assertNotNull( myMojo ); - myMojo.execute(); - } -} +//package com.jd.blockchain.ledger; +// +//import com.jd.blockchain.contract.maven.ContractVerifyMojo; +//import org.apache.maven.plugin.testing.AbstractMojoTestCase; +//import org.junit.Test; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.File; +// +///** +// * @Author zhaogw +// * @Date 2019/3/1 21:27 +// */ +//public class ContractVerifyMojoTest extends AbstractMojoTestCase { +// Logger logger = LoggerFactory.getLogger(ContractVerifyMojoTest.class); +// +// @Test +// public void test1() throws Exception { +// File pom = getTestFile( "src/test/resources/project-to-test/pom.xml" ); +// assertNotNull( pom ); +// assertTrue( pom.exists() ); +// +// ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "contractVerify", pom ); +// assertNotNull( myMojo ); +// myMojo.execute(); +// } +//} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java index f3e8fea9..ea6828b6 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java @@ -1,47 +1,47 @@ -package com.jd.blockchain.ledger; - -import com.jd.blockchain.contract.maven.ContractVerifyMojo; -import org.apache.maven.project.MavenProject; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; - -import static com.jd.blockchain.ledger.ContractTestBase.mavenProjectInit; - -public class ContractVerifyTest_ { - - private MavenProject project; - - private String finalName; - - @Before - public void testInit() { - project = mavenProjectInit(); - finalName = "complex"; - } - - @Test - public void test() throws Exception { - ContractVerifyMojo contractVerifyMojo = contractVerifyMojoConf(); - contractVerifyMojo.execute(); - } - - private ContractVerifyMojo contractVerifyMojoConf() throws Exception { - ContractVerifyMojo contractVerifyMojo = new ContractVerifyMojo(); - // 为不影响其内部结构,通过反射进行私有变量赋值 - Class clazz = contractVerifyMojo.getClass(); - Field projectField = clazz.getDeclaredField("project"); - Field finalNameField = clazz.getDeclaredField("finalName"); - - // 更新权限 - projectField.setAccessible(true); - finalNameField.setAccessible(true); - - // 设置具体值 - projectField.set(contractVerifyMojo, project); - finalNameField.set(contractVerifyMojo, finalName); - - return contractVerifyMojo; - } -} +//package com.jd.blockchain.ledger; +// +//import com.jd.blockchain.contract.maven.ContractVerifyMojo; +//import org.apache.maven.project.MavenProject; +//import org.junit.Before; +//import org.junit.Test; +// +//import java.lang.reflect.Field; +// +//import static com.jd.blockchain.ledger.ContractTestBase.mavenProjectInit; +// +//public class ContractVerifyTest_ { +// +// private MavenProject project; +// +// private String finalName; +// +// @Before +// public void testInit() { +// project = mavenProjectInit(); +// finalName = "complex"; +// } +// +// @Test +// public void test() throws Exception { +// ContractVerifyMojo contractVerifyMojo = contractVerifyMojoConf(); +// contractVerifyMojo.execute(); +// } +// +// private ContractVerifyMojo contractVerifyMojoConf() throws Exception { +// ContractVerifyMojo contractVerifyMojo = new ContractVerifyMojo(); +// // 为不影响其内部结构,通过反射进行私有变量赋值 +// Class clazz = contractVerifyMojo.getClass(); +// Field projectField = clazz.getDeclaredField("project"); +// Field finalNameField = clazz.getDeclaredField("finalName"); +// +// // 更新权限 +// projectField.setAccessible(true); +// finalNameField.setAccessible(true); +// +// // 设置具体值 +// projectField.set(contractVerifyMojo, project); +// finalNameField.set(contractVerifyMojo, finalName); +// +// return contractVerifyMojo; +// } +//} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/MyProjectStub.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/MyProjectStub.java index 473115af..34614e30 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/MyProjectStub.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/MyProjectStub.java @@ -1,67 +1,67 @@ -package com.jd.blockchain.ledger; - -import org.apache.maven.model.Build; -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.plugin.testing.stubs.MavenProjectStub; -import org.codehaus.plexus.util.ReaderFactory; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * @author zhaogw - * date 2019/6/4 18:33 - */ - -public class MyProjectStub extends MavenProjectStub -{ - /** - * Default constructor - */ - public MyProjectStub() - { - MavenXpp3Reader pomReader = new MavenXpp3Reader(); - Model model; - try - { - model = pomReader.read( ReaderFactory.newXmlReader( new File( getBasedir(), "pom.xml" ) ) ); - setModel( model ); - } - catch ( Exception e ) - { - throw new RuntimeException( e ); - } - - setGroupId( model.getGroupId() ); - setArtifactId( model.getArtifactId() ); - setVersion( model.getVersion() ); - setName( model.getName() ); - setUrl( model.getUrl() ); - setPackaging( model.getPackaging() ); - - Build build = new Build(); - build.setFinalName( model.getArtifactId() ); - build.setDirectory( getBasedir() + "/target" ); - build.setSourceDirectory( getBasedir() + "/src/main/java" ); - build.setOutputDirectory( getBasedir() + "/target/classes" ); - build.setTestSourceDirectory( getBasedir() + "/src/test/java" ); - build.setTestOutputDirectory( getBasedir() + "/target/test-classes" ); - setBuild( build ); - - List compileSourceRoots = new ArrayList(); - compileSourceRoots.add( getBasedir() + "/src/main/java" ); - setCompileSourceRoots( compileSourceRoots ); - - List testCompileSourceRoots = new ArrayList(); - testCompileSourceRoots.add( getBasedir() + "/src/test/java" ); - setTestCompileSourceRoots( testCompileSourceRoots ); - } - - /** {@inheritDoc} */ - public File getBasedir() - { - return new File( super.getBasedir() + "/src/test/resources/project-to-test/" ); - } -} +//package com.jd.blockchain.ledger; +// +//import org.apache.maven.model.Build; +//import org.apache.maven.model.Model; +//import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +//import org.apache.maven.plugin.testing.stubs.MavenProjectStub; +//import org.codehaus.plexus.util.ReaderFactory; +// +//import java.io.File; +//import java.util.ArrayList; +//import java.util.List; +// +///** +// * @author zhaogw +// * date 2019/6/4 18:33 +// */ +// +//public class MyProjectStub extends MavenProjectStub +//{ +// /** +// * Default constructor +// */ +// public MyProjectStub() +// { +// MavenXpp3Reader pomReader = new MavenXpp3Reader(); +// Model model; +// try +// { +// model = pomReader.read( ReaderFactory.newXmlReader( new File( getBasedir(), "pom.xml" ) ) ); +// setModel( model ); +// } +// catch ( Exception e ) +// { +// throw new RuntimeException( e ); +// } +// +// setGroupId( model.getGroupId() ); +// setArtifactId( model.getArtifactId() ); +// setVersion( model.getVersion() ); +// setName( model.getName() ); +// setUrl( model.getUrl() ); +// setPackaging( model.getPackaging() ); +// +// Build build = new Build(); +// build.setFinalName( model.getArtifactId() ); +// build.setDirectory( getBasedir() + "/target" ); +// build.setSourceDirectory( getBasedir() + "/src/main/java" ); +// build.setOutputDirectory( getBasedir() + "/target/classes" ); +// build.setTestSourceDirectory( getBasedir() + "/src/test/java" ); +// build.setTestOutputDirectory( getBasedir() + "/target/test-classes" ); +// setBuild( build ); +// +// List compileSourceRoots = new ArrayList(); +// compileSourceRoots.add( getBasedir() + "/src/main/java" ); +// setCompileSourceRoots( compileSourceRoots ); +// +// List testCompileSourceRoots = new ArrayList(); +// testCompileSourceRoots.add( getBasedir() + "/src/test/java" ); +// setTestCompileSourceRoots( testCompileSourceRoots ); +// } +// +// /** {@inheritDoc} */ +// public File getBasedir() +// { +// return new File( super.getBasedir() + "/src/test/resources/project-to-test/" ); +// } +//} From 12feaf39b76cb16afc357c127c714ceb29c2dbff Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 9 Jul 2019 11:41:48 +0800 Subject: [PATCH 009/124] modify contract'plugin --- .../contract/maven/ContractCompileMojo.java | 8 ++++++ .../contract/maven/ContractResolveEngine.java | 2 +- .../GatewayInterceptServiceHandler.java | 3 +-- ....java => AbstractContractEventHandle.java} | 2 +- .../ContractCodeDeployOperationHandle.java | 2 +- .../JVMContractEventSendOperationHandle.java | 22 +++++++++++++--- .../ledger/ContractInvokingHandle.java | 4 +-- .../contract}/ContractJarUtils.java | 18 ++++++++----- .../BlockchainOperationFactory.java | 26 +++---------------- .../ledger/data}/ContractJarUtilsTest.java | 9 +++++-- .../blockchain/runtime/boot/HomeBooter.java | 2 +- .../runtime/modular/ModularFactory.java | 2 +- 12 files changed, 56 insertions(+), 44 deletions(-) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/{AbtractContractEventHandle.java => AbstractContractEventHandle.java} (97%) rename source/{utils/utils-common/src/main/java/com/jd/blockchain/utils/jar => ledger/ledger-model/src/main/java/com/jd/blockchain/contract}/ContractJarUtils.java (87%) rename source/{utils/utils-common/src/test/java/test/my/utils => ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data}/ContractJarUtilsTest.java (87%) diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java index b0304ba0..61a3dfff 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java @@ -15,16 +15,24 @@ public class ContractCompileMojo extends SingleAssemblyMojo { // 要求必须有MainClass try { String mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); + // 校验MainClass,要求MainClass必须不能为空 + if (mainClass == null || mainClass.length() == 0) { + throw new MojoFailureException("MainClass is NULL !!!"); + } super.getLog().debug("MainClass is " + mainClass); } catch (Exception e) { throw new MojoFailureException("MainClass is null: " + e.getMessage(), e ); } + + // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); + // 执行打包命令 super.execute(); ContractResolveEngine engine = new ContractResolveEngine(getLog(), getProject(), getFinalName()); + // 打包并进行校验 engine.compileAndVerify(); } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java index 7ce0b0a1..06619a84 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java @@ -20,9 +20,9 @@ import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import static com.jd.blockchain.contract.ContractJarUtils.*; import static com.jd.blockchain.contract.maven.ContractCompileMojo.JAR_DEPENDENCE; import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; -import static com.jd.blockchain.utils.jar.ContractJarUtils.*; public class ContractResolveEngine { diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java index 2f3d215e..44f22aad 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayInterceptServiceHandler.java @@ -1,11 +1,10 @@ package com.jd.blockchain.gateway.service; +import com.jd.blockchain.contract.ContractJarUtils; import com.jd.blockchain.gateway.PeerService; import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.utils.IllegalDataException; -import com.jd.blockchain.utils.jar.ContractJarUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbstractContractEventHandle.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbstractContractEventHandle.java index 40f6e2c2..0b2189ae 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbstractContractEventHandle.java @@ -18,7 +18,7 @@ import com.jd.blockchain.ledger.core.impl.LedgerQueryService; import com.jd.blockchain.ledger.core.impl.OperationHandleContext; @Service -public abstract class AbtractContractEventHandle implements OperationHandle { +public abstract class AbstractContractEventHandle implements OperationHandle { @Override public boolean support(Class operationType) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java index fd4dc617..ed7b1981 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger.core.impl.handles; -import com.jd.blockchain.utils.jar.ContractJarUtils; +import com.jd.blockchain.contract.ContractJarUtils; import org.springframework.stereotype.Service; import com.jd.blockchain.ledger.BytesValue; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java index da4da430..f32b7f41 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java @@ -5,12 +5,17 @@ import com.jd.blockchain.contract.engine.ContractEngine; import com.jd.blockchain.contract.engine.ContractServiceProviders; import com.jd.blockchain.ledger.core.ContractAccount; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + import static com.jd.blockchain.utils.BaseConstant.CONTRACT_SERVICE_PROVIDER; -public class JVMContractEventSendOperationHandle extends AbtractContractEventHandle { +public class JVMContractEventSendOperationHandle extends AbstractContractEventHandle { private static final ContractEngine JVM_ENGINE; + private static final Lock JVM_LOAD_LOCK = new ReentrantLock(); + static { JVM_ENGINE = ContractServiceProviders.getProvider(CONTRACT_SERVICE_PROVIDER).getEngine(); } @@ -19,9 +24,18 @@ public class JVMContractEventSendOperationHandle extends AbtractContractEventHan protected ContractCode loadContractCode(ContractAccount contract) { ContractCode contractCode = JVM_ENGINE.getContract(contract.getAddress(), contract.getChaincodeVersion()); if (contractCode == null) { - // 装载合约; - contractCode = JVM_ENGINE.setupContract(contract.getAddress(), contract.getChaincodeVersion(), - contract.getChainCode()); + JVM_LOAD_LOCK.lock(); + try { + // Double Check + contractCode = JVM_ENGINE.getContract(contract.getAddress(), contract.getChaincodeVersion()); + if (contractCode == null) { + // 装载合约; + contractCode = JVM_ENGINE.setupContract(contract.getAddress(), contract.getChaincodeVersion(), + contract.getChainCode()); + } + } finally { + JVM_LOAD_LOCK.unlock(); + } } return contractCode; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java index c0a31321..553cac68 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java @@ -8,10 +8,10 @@ import com.jd.blockchain.contract.engine.ContractCode; import com.jd.blockchain.contract.jvm.AbstractContractCode; import com.jd.blockchain.contract.jvm.ContractDefinition; import com.jd.blockchain.ledger.core.ContractAccount; -import com.jd.blockchain.ledger.core.impl.handles.AbtractContractEventHandle; +import com.jd.blockchain.ledger.core.impl.handles.AbstractContractEventHandle; import com.jd.blockchain.utils.Bytes; -public class ContractInvokingHandle extends AbtractContractEventHandle { +public class ContractInvokingHandle extends AbstractContractEventHandle { private Map contractInstances = new ConcurrentHashMap(); diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java similarity index 87% rename from source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java index 5ec66dde..5a183006 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java @@ -1,5 +1,9 @@ -package com.jd.blockchain.utils.jar; +package com.jd.blockchain.contract; +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.HashFunction; +import com.jd.blockchain.utils.io.BytesUtils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -17,13 +21,15 @@ public class ContractJarUtils { private static final String CONTRACT_MF = "META-INF/CONTRACT.MF"; - private static final int JDCHAIN_HASH_LENGTH = 69; + private static final HashFunction HASH_FUNCTION = Crypto.getHashFunction("SHA256"); private static final Random FILE_RANDOM = new Random(); + private static final byte[] JDCHAIN_MARK = "JDChain".getBytes(StandardCharsets.UTF_8); + public static void verify(byte[] chainCode) { if (chainCode == null || chainCode.length == 0) { - throw new IllegalStateException("ChainCode is empty !!!"); + throw new IllegalStateException("Contract's chaincode is empty !!!"); } // 首先生成合约文件 File jarFile = newJarFile(); @@ -51,7 +57,7 @@ public class ContractJarUtils { throw new IllegalStateException(CONTRACT_MF + " IS NULL !!!"); } byte[] bytes = IOUtils.toByteArray(inputStream); - if (bytes == null || bytes.length != JDCHAIN_HASH_LENGTH) { + if (bytes == null || bytes.length == 0) { throw new IllegalStateException(CONTRACT_MF + " IS Illegal !!!"); } // 获取对应的Hash内容 @@ -107,8 +113,8 @@ public class ContractJarUtils { } public static String contractMF(byte[] content) { - // hash=Hex(hash(content)) - return "hash:" + DigestUtils.sha256Hex(content); + HashDigest hashDigest = HASH_FUNCTION.hash(BytesUtils.concat(content, JDCHAIN_MARK)); + return "hash:" + hashDigest.toBase58(); } public static JarEntry contractMFJarEntry() { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index 93f0e3c7..00d79f35 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -1,31 +1,11 @@ package com.jd.blockchain.transaction; -import java.io.*; -import java.net.URL; -import java.nio.charset.StandardCharsets; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.utils.Bytes; + import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; - -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BytesValue; -import com.jd.blockchain.ledger.BytesValueList; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.LedgerInitOperation; -import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.jar.ContractJarUtils; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.io.FileUtils; /** * @author huanghaiquan diff --git a/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java similarity index 87% rename from source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java index 02376434..06e2bf44 100644 --- a/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java @@ -1,4 +1,4 @@ -package test.my.utils; +package test.com.jd.blockchain.ledger.data; import org.apache.commons.io.FileUtils; import org.junit.Test; @@ -7,7 +7,7 @@ import org.springframework.core.io.ClassPathResource; import java.io.File; import java.nio.charset.StandardCharsets; -import static com.jd.blockchain.utils.jar.ContractJarUtils.*; +import static com.jd.blockchain.contract.ContractJarUtils.*; import static org.junit.Assert.fail; public class ContractJarUtilsTest { @@ -59,6 +59,11 @@ public class ContractJarUtilsTest { } catch (Exception e) { fail("Verify Fail !!"); } + } + @Test + public void testSign() { + byte[] test = "zhangsan".getBytes(StandardCharsets.UTF_8); + System.out.println(contractMF(test)); } } diff --git a/source/runtime/runtime-modular-booter/src/main/java/com/jd/blockchain/runtime/boot/HomeBooter.java b/source/runtime/runtime-modular-booter/src/main/java/com/jd/blockchain/runtime/boot/HomeBooter.java index 6000f04c..1e8329b2 100644 --- a/source/runtime/runtime-modular-booter/src/main/java/com/jd/blockchain/runtime/boot/HomeBooter.java +++ b/source/runtime/runtime-modular-booter/src/main/java/com/jd/blockchain/runtime/boot/HomeBooter.java @@ -91,7 +91,7 @@ public class HomeBooter { runtimeDir.mkdirs(); } - // 以 ExtClassLoader 作为所有创建的ClassLoader的 Parrent; + // 以 ExtClassLoader 作为所有创建的ClassLoader的 Parent; ClassLoader extClassLoader = HomeBooter.class.getClassLoader().getParent(); URL[] libJars = loadClassPaths(libDir); diff --git a/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java b/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java index b9615482..b23dbfe8 100644 --- a/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java +++ b/source/runtime/runtime-modular/src/main/java/com/jd/blockchain/runtime/modular/ModularFactory.java @@ -6,7 +6,7 @@ public class ModularFactory { * 启动系统; */ public static void startSystem(String runtimeDir, boolean productMode, - ClassLoader libClassLoader,String mainClassName, ClassLoader systemClassLoader, String[] args) { + ClassLoader libClassLoader, String mainClassName, ClassLoader systemClassLoader, String[] args) { JarsModule libModule = new JarsModule("LibModule", libClassLoader); From 7ec858a7988b3d9d2710fa63b97fc822f7dd128b Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 10 Jul 2019 16:10:12 +0800 Subject: [PATCH 010/124] modify contract's classloader for check load classes! --- .../contract/maven/ContractResolveEngine.java | 46 ++++++---- .../JVMContractEventSendOperationHandle.java | 9 -- source/ledger/ledger-model/pom.xml | 1 - .../jd/blockchain/runtime/RuntimeContext.java | 80 ++++++++++++++++-- .../src/main/resources/black.config | 2 + .../sdk/samples/SDK_Contract_Check_Demo.java | 67 +++++++++++++++ .../jd/chain/contracts/ContractTestInf.java | 14 +++ .../src/main/resources/contract-jdchain.jar | Bin 0 -> 4255 bytes 8 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 source/runtime/runtime-context/src/main/resources/black.config create mode 100644 source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Contract_Check_Demo.java create mode 100644 source/sdk/sdk-samples/src/main/java/com/jd/chain/contracts/ContractTestInf.java create mode 100644 source/sdk/sdk-samples/src/main/resources/contract-jdchain.jar diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java index 06619a84..3fb2404d 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java @@ -41,6 +41,20 @@ public class ContractResolveEngine { private static final String BLACK_NAME_LIST = "black.name.list"; + private static List blackNameList; + + private static List blackPackageList; + + private static Set blackClassSet; + + static { + try { + configInit(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + private Log LOGGER; private MavenProject project; @@ -87,14 +101,6 @@ public class ContractResolveEngine { throw e; } - Properties config = loadConfig(); - - List blackNameList = blackNameList(config); - - List blackPackageList = blackPackageList(config); - - Set blackClassSet = blackClassSet(config); - LinkedList totalClassList = loadAllClass(jarFile); // 该项目路径 String projectDir = project.getBasedir().getPath(); @@ -230,34 +236,32 @@ public class ContractResolveEngine { LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", jarFile.getName())); } - private List blackNameList(Properties config) { + private static List blackNameList(Properties config) { return blackList(config, BLACK_NAME_LIST); } - private Set blackClassSet(Properties config) { + private static Set blackClassSet(Properties config) { Set blackClassSet = new HashSet<>(); String attrProp = config.getProperty(BLACK_CLASS_LIST); if (attrProp != null && attrProp.length() > 0) { String[] attrPropArray = attrProp.split(","); for (String attr : attrPropArray) { - LOGGER.info(String.format("Config [%s] -> [%s]", BLACK_CLASS_LIST, attr)); blackClassSet.add(attr.trim()); } } return blackClassSet; } - private List blackPackageList(Properties config) { + private static List blackPackageList(Properties config) { return blackList(config, BLACK_PACKAGE_LIST); } - private List blackList(Properties config, String attrName) { + private static List blackList(Properties config, String attrName) { List list = new ArrayList<>(); String attrProp = config.getProperty(attrName); if (attrProp != null || attrProp.length() > 0) { String[] attrPropArray = attrProp.split(","); for (String attr : attrPropArray) { - LOGGER.info(String.format("Config [%s] -> [%s]", attrName, attr)); list.add(new ContractPackage(attr)); } } @@ -359,11 +363,21 @@ public class ContractResolveEngine { return finalJar; } - private Properties loadConfig() throws Exception { + private static void configInit() throws Exception { + Properties config = loadConfig(); + + blackNameList = blackNameList(config); + + blackPackageList = blackPackageList(config); + + blackClassSet = blackClassSet(config); + } + + private static Properties loadConfig() throws Exception { Properties properties = new Properties(); - properties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG)); + properties.load(ContractResolveEngine.class.getResourceAsStream(File.separator + CONFIG)); return properties; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java index f32b7f41..b05e2189 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java @@ -39,13 +39,4 @@ public class JVMContractEventSendOperationHandle extends AbstractContractEventHa } return contractCode; } - -// @Override -// public AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, -// TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, -// OperationHandleContext handleContext, LedgerService ledgerService) { -// // TODO Auto-generated method stub -// return null; -// } - } diff --git a/source/ledger/ledger-model/pom.xml b/source/ledger/ledger-model/pom.xml index 6c39c1cf..ea21a207 100644 --- a/source/ledger/ledger-model/pom.xml +++ b/source/ledger/ledger-model/pom.xml @@ -37,7 +37,6 @@ com.jd.blockchain crypto-classic ${project.version} - test diff --git a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java index 569a95a5..c37077e8 100644 --- a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java +++ b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java @@ -2,11 +2,10 @@ package com.jd.blockchain.runtime; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -16,7 +15,7 @@ import com.jd.blockchain.utils.io.RuntimeIOException; public abstract class RuntimeContext { - public static interface Environment{ + public static interface Environment { boolean isProductMode(); @@ -24,6 +23,7 @@ public abstract class RuntimeContext { private static final Object mutex = new Object(); + private static volatile RuntimeContext runtimeContext; public static RuntimeContext get() { @@ -202,8 +202,78 @@ public abstract class RuntimeContext { @Override protected URLClassLoader createDynamicModuleClassLoader(URL jarURL) { - return new URLClassLoader(new URL[] { jarURL }, RuntimeContext.class.getClassLoader()); + return new ContractURLClassLoader(jarURL, RuntimeContext.class.getClassLoader()); + } + + } + + static class ContractURLClassLoader extends URLClassLoader { + + private static final String BLACK_CONFIG = "black.config"; + + private static final Set BLACK_CLASSES = new HashSet<>(); + + private static final Set BLACK_PACKAGES = new HashSet<>(); + + static { + initBlacks(); + } + + public ContractURLClassLoader(URL contractJarURL, ClassLoader parent) { + super(new URL[] { contractJarURL }, parent); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (BLACK_CLASSES.contains(name)) { + throw new IllegalStateException(String.format("Contract cannot use Class [%s]", name)); + } else { + // 判断该包是否是黑名单 + String trimName = name.trim(); + String packageName = trimName.substring(0, trimName.length() - 2); + if (BLACK_PACKAGES.contains(packageName)) { + throw new IllegalStateException(String.format("Contract cannot use Class [%s]", name)); + } + } + return super.loadClass(name); } + private static void initBlacks() { + try { + InputStream inputStream = ContractURLClassLoader.class.getResourceAsStream(File.separator + BLACK_CONFIG); + String text = FileUtils.readText(inputStream); + String[] textArray = text.split("\n"); + for (String setting : textArray) { + // 支持按照逗号分隔 + if (setting == null || setting.length() == 0) { + continue; + } + String[] settingArray = setting.split(","); + for (String set : settingArray) { + String totalClass = set.trim(); + if (totalClass.endsWith("*")) { + // 说明是包,获取具体包名 + String packageName = totalClass.substring(0, totalClass.length() - 2); + BLACK_PACKAGES.add(packageName); + } else { + // 具体的类名,直接放入集合 + BLACK_CLASSES.add(totalClass); + } + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + public static void main(String[] args) { + for (String s : BLACK_CLASSES) { + System.out.println(s); + } + + for (String s : BLACK_PACKAGES) { + System.out.println(s); + } + } } } diff --git a/source/runtime/runtime-context/src/main/resources/black.config b/source/runtime/runtime-context/src/main/resources/black.config new file mode 100644 index 00000000..d3353498 --- /dev/null +++ b/source/runtime/runtime-context/src/main/resources/black.config @@ -0,0 +1,2 @@ +java.util.Random, com.jd.blockchain.ledger.BlockchainKeyGenerator +java.io.*, java.nio.*, java.net.*, org.apache.commons.io.* \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Contract_Check_Demo.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Contract_Check_Demo.java new file mode 100644 index 00000000..7d521776 --- /dev/null +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Contract_Check_Demo.java @@ -0,0 +1,67 @@ +package com.jd.blockchain.sdk.samples; + +import com.jd.blockchain.contract.TransferContract; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.transaction.GenericValueHolder; +import com.jd.blockchain.utils.Bytes; +import com.jd.chain.contracts.ContractTestInf; + +import static com.jd.blockchain.sdk.samples.SDKDemo_Constant.readChainCodes; +import static com.jd.blockchain.transaction.ContractReturnValue.decode; + +public class SDK_Contract_Check_Demo extends SDK_Base_Demo { + + public static void main(String[] args) { + new SDK_Contract_Check_Demo().executeContract(); + } + + public void executeContract() { + + // 发布jar包 + // 定义交易模板 + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + + // 将jar包转换为二进制数据 + byte[] contractCode = readChainCodes("contract-jdchain.jar"); + + // 生成一个合约账号 + BlockchainKeypair contractDeployKey = BlockchainKeyGenerator.getInstance().generate(); + + // 生成发布合约操作 + txTpl.contracts().deploy(contractDeployKey.getIdentity(), contractCode); + + // 生成预发布交易; + PreparedTransaction ptx = txTpl.prepare(); + + // 对交易进行签名 + ptx.sign(adminKey); + + // 提交并等待共识返回; + TransactionResponse txResp = ptx.commit(); + + // 获取合约地址 + Bytes contractAddress = contractDeployKey.getAddress(); + + // 打印交易返回信息 + System.out.printf("Tx[%s] -> BlockHeight = %s, BlockHash = %s, isSuccess = %s, ExecutionState = %s \r\n", + txResp.getContentHash().toBase58(), txResp.getBlockHeight(), txResp.getBlockHash().toBase58(), + txResp.isSuccess(), txResp.getExecutionState()); + + // 打印合约地址 + System.out.printf("ContractAddress = %s \r\n", contractAddress.toBase58()); + + // 执行合约 + exeContract(contractAddress); + } + + private void exeContract(Bytes contractAddress) { + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + ContractTestInf contract = txTpl.contract(contractAddress, ContractTestInf.class); + GenericValueHolder result = decode(contract.randomChars(1024)); + commit(txTpl); + String random = result.get(); + System.out.println(random); + } + + +} diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/chain/contracts/ContractTestInf.java b/source/sdk/sdk-samples/src/main/java/com/jd/chain/contracts/ContractTestInf.java new file mode 100644 index 00000000..7e5d362a --- /dev/null +++ b/source/sdk/sdk-samples/src/main/java/com/jd/chain/contracts/ContractTestInf.java @@ -0,0 +1,14 @@ +package com.jd.chain.contracts; + +import com.jd.blockchain.contract.Contract; +import com.jd.blockchain.contract.ContractEvent; + +@Contract +public interface ContractTestInf { + + @ContractEvent(name = "print") + void print(String name, int age); + + @ContractEvent(name = "random") + String randomChars(int max); +} \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/main/resources/contract-jdchain.jar b/source/sdk/sdk-samples/src/main/resources/contract-jdchain.jar new file mode 100644 index 0000000000000000000000000000000000000000..b9e41e1d09cd6d88abc22d12856c0c182752047c GIT binary patch literal 4255 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 Date: Fri, 12 Jul 2019 10:11:08 +0800 Subject: [PATCH 011/124] temp; --- .../ledger/core/AuthorizableDataSet.java | 168 ------------------ .../blockchain/ledger/core/Authorization.java | 29 +-- .../ledger/core/AuthorizationVO.java | 42 ----- .../blockchain/ledger/core/BaseAccount.java | 6 +- .../ledger/core/LedgerPermission.java | 13 ++ .../ledger/core/LedgerSecurityManager.java | 35 ++++ .../jd/blockchain/ledger/core/Privilege.java | 54 ++++++ .../com/jd/blockchain/ledger/core/Role.java | 27 +++ 8 files changed, 134 insertions(+), 240 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizableDataSet.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizationVO.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizableDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizableDataSet.java deleted file mode 100644 index ca0a406a..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizableDataSet.java +++ /dev/null @@ -1,168 +0,0 @@ -//package com.jd.blockchain.ledger.core; -// -//import com.jd.blockchain.crypto.hash.HashDigest; -// -//import my.utils.Scratchable; -//import my.utils.io.ByteArray; -//import my.utils.io.BytesUtils; -//import my.utils.io.ExistancePolicyKVStorage; -//import my.utils.io.VersioningKVStorage; -// -///** -// * 可进行授权控制的数据集合; -// * -// * @author huanghaiquan -// * -// */ -//public class AuthorizableDataSet implements Scratchable { -// -// public static final String DATA_PREFIX = "DATA" + LedgerConsts.KEY_SEPERATOR; -//// public static final String PRIVILEGE_PREFIX = "PRVL" + LedgerConsts.KEY_SEPERATOR; -// -// private static final String DEFAULT_PRIVILEGE_KEY = "%"; -// -// private DataAccessable accessable; -// -// protected MerkleDataSet data; -// -//// private PrivilegeDataSet privileges; -// -// /** -// * Create a new Account instance; -// * -// * @param address -// * @param pubKey -// */ -// protected AuthorizableDataSet(CryptoSetting merkleTreeSetting, ExistancePolicyKVStorage simpleStorage, -// VersioningKVStorage versioningStorage) { -// this(null, merkleTreeSetting, null, simpleStorage, versioningStorage); -// } -// -// protected AuthorizableDataSet(byte[] dataRootHash, CryptoSetting merkleTreeSetting, byte[] privilegeRootHash, -// ExistancePolicyKVStorage simpleStorage, VersioningKVStorage versioningStorage) { -// this(dataRootHash, merkleTreeSetting, privilegeRootHash, simpleStorage, versioningStorage, false); -// } -// -// protected AuthorizableDataSet(byte[] dataRootHash, CryptoSetting merkleTreeSetting, byte[] privilegeRootHash, -// ExistancePolicyKVStorage simpleStorage, VersioningKVStorage versioningStorage, boolean readonly) { -// this.data = new MerkleDataSet(dataRootHash, merkleTreeSetting, -// PrefixAppender.prefix(DATA_PREFIX, simpleStorage), -// PrefixAppender.prefix(DATA_PREFIX, versioningStorage), readonly); -// -//// this.privileges = new PrivilegeDataSet(privilegeRootHash, merkleTreeSetting, -//// PrefixAppender.prefix(PRIVILEGE_PREFIX, simpleStorage), -//// PrefixAppender.prefix(PRIVILEGE_PREFIX, versioningStorage), readonly); -// } -// -// public ByteArray getDataRootHash() { -// return data.getRootHash(); -// } -// -//// public ByteArray getPrivilegeRootHash() { -//// return privileges.getRootHash(); -//// } -// -// /** -// * -// * @param userAddress -// * @param op -// * @param enable -// */ -// public void setPrivilege(String userAddress, byte op, boolean enable) { -// -// } -// -// /** -// * -// * @param op -// * @param enable -// */ -// public void setDefaultPrivilege(byte op, boolean enable) { -// } -// -// public boolean checkCurrentUserPrivilege() { -// return false; -// } -// -// /** -// * Return the latest version entry associated the specified key; If the key -// * doesn't exist, then return -1; -// * -// * @param key -// * @return -// */ -// public long getVersion(String key) { -// return data.getVersion(key); -// } -// -// protected long setString(String key, String value, long version) { -// checkWritting(); -// byte[] bytes = BytesUtils.toBytes(value, LedgerConsts.CHARSET); -// return data.setValue(key, bytes, version); -// } -// -// protected String getString(String key) { -// checkReading(); -// byte[] value = data.getValue(key); -// return BytesUtils.toString(value, LedgerConsts.CHARSET); -// } -// -// protected String getString(String key, long version) { -// checkReading(); -// byte[] value = data.getValue(key, version); -// return BytesUtils.toString(value, LedgerConsts.CHARSET); -// } -// -// protected long setValue(String key, byte[] value, long version) { -// checkWritting(); -// return data.setValue(key, value, version); -// } -// -// protected byte[] getValue(String key) { -// checkReading(); -// return data.getValue(key); -// } -// -// protected byte[] getValue(String key, long version) { -// checkReading(); -// return data.getValue(key, version); -// } -// -// private void checkWritting() { -// // Check writting enable; -// } -// -// private void checkReading() { -// // TODO Check privilege of reading; -// } -// -// // /** -// // * 数据“读”的操作码; -// // * -// // * @return -// // */ -// // protected abstract AccountPrivilege getPrivilege(); -// -// @Override -// public boolean isUpdated() { -// return data.isUpdated(); -//// return data.isUpdated()|| privileges.isUpdated(); -// } -// -// @Override -// public void commit() { -// if (data.isUpdated()) { -// data.commit(); -// } -//// if (privileges.isUpdated()) { -//// privileges.commit(); -//// } -// } -// -// @Override -// public void cancel() { -// data.cancel(); -//// privileges.cancel(); -// } -// -//} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java index cba2ffe4..18f1bf70 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java @@ -8,33 +8,8 @@ import com.jd.blockchain.ledger.DigitalSignature; * @author huanghaiquan * */ -public interface Authorization { +public class Authorization { - /** - * 被授权用户/角色的地址; - * - * @return - */ - String getAddress(); - - /** - * 授权码;
        - * - * @return - */ - byte[] getCode(); - - /** - * 授权者的签名; - * - * @return - */ - DigitalSignature getSignature(); - - // /** - // * 授权生成的时间戳; - // * @return - // */ - // long getTs(); + } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizationVO.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizationVO.java deleted file mode 100644 index 24d7f125..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AuthorizationVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.ledger.DigitalSignature; - -public class AuthorizationVO implements Authorization { - - private String address; - - private byte[] code; - - private DigitalSignature signature; - - - @Override - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - @Override - public byte[] getCode() { - return code; - } - - public void setCode(byte[] code) { - this.code = code; - } - - @Override - public DigitalSignature getSignature() { - return signature; - } - - - public void setSignature(DigitalSignature signature) { - this.signature = signature; - } - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java index 9a57c6d0..7f499363 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java @@ -64,13 +64,13 @@ public class BaseAccount implements AccountHeader, MerkleProvable, Transactional /** * Create a account instance with the specified address and pubkey and load it's - * merkle dataset with the specified root hash. which is used for storing data + * merkle dataset from the specified root hash. This merkle dateset is used for storing data * of this account.
        * * @param address * @param pubKey - * @param dataRootHash merkle root hash of account's data; if null be set, - * create a new empty merkle dataset; + * @param dataRootHash merkle root hash of account's data; if set to a null value, + * an empty merkle dataset is created; * @param cryptoSetting * @param exStorage * @param verStorage diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java new file mode 100644 index 00000000..99f6b907 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java @@ -0,0 +1,13 @@ +package com.jd.blockchain.ledger.core; + +public enum LedgerPermission { + + SET_ROLE((byte) 0); + + public final byte CODE; + + private LedgerPermission(byte code) { + this.CODE = code; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java new file mode 100644 index 00000000..11c99c9e --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -0,0 +1,35 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Set; + +/** + * + * {@link LedgerSecurityManager} implements the functions of security + * management, including authentication, authorization, data confidentiality, + * etc. + * + * @author huanghaiquan + * + */ +public class LedgerSecurityManager { + + public static final String ANONYMOUS_ROLE = "_ANONYMOUS"; + + public static final String DEFAULT_ROLE = "_DEFAULT"; + + + public Set getRoleNames(){ + throw new IllegalStateException("Not implemented!"); + } + + public Role setRole(String role, Privilege privilege) { + throw new IllegalStateException("Not implemented!"); + } + + public Role getRole(String role) { + throw new IllegalStateException("Not implemented!"); + } + + + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java new file mode 100644 index 00000000..95443f45 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java @@ -0,0 +1,54 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; + +public class Privilege { + + private BitSet permissions; + + public Privilege(byte[] codeBytes) { + permissions = BitSet.valueOf(codeBytes); + } + + public boolean isEnable(LedgerPermission permission) { + return permissions.get(getCodeIndex(permission)); + } + + public void enable(LedgerPermission permission) { + permissions.set(getCodeIndex(permission)); + } + + public void disable(LedgerPermission permission) { + permissions.clear(getCodeIndex(permission)); + } + + public static int getCodeIndex(LedgerPermission permission) { + return permission.CODE & 0xFF; + } + + public byte[] toCodeBytes() { + return permissions.toByteArray(); + } + + public boolean[] getPermissionStates() { + LedgerPermission[] PMs = LedgerPermission.values(); + + LedgerPermission maxPermission = Arrays.stream(PMs).max(new Comparator() { + @Override + public int compare(LedgerPermission o1, LedgerPermission o2) { + return getCodeIndex(o1) - getCodeIndex(o2); + } + }).get(); + + boolean[] states = new boolean[getCodeIndex(maxPermission) + 1]; + int idx = -1; + for (LedgerPermission pm : PMs) { + idx = getCodeIndex(pm); + states[idx] = permissions.get(idx); + } + + return states; + } +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java new file mode 100644 index 00000000..23149745 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java @@ -0,0 +1,27 @@ +package com.jd.blockchain.ledger.core; + +public class Role { + + private String name; + + private long version; + + private Privilege privilege; + + + + public String getName() { + return name; + } + + public long getVersion() { + return version; + } + + public Privilege getPrivilege() { + return privilege; + } + + + +} From 3a37cded205d2894ebf8f6bd144aee4d1decdf33 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 17 Jul 2019 17:56:30 +0800 Subject: [PATCH 012/124] Modify contract-maven-plugin's verification scheme to be implemented by ASM! --- source/contract/contract-maven-plugin/pom.xml | 10 +- .../contract/maven/AbstractContract.java | 98 ++++ .../contract/maven/ContractCheckMojo.java | 216 --------- .../contract/maven/ContractClass.java | 66 +++ .../contract/maven/ContractCompileMojo.java | 150 +++++- .../contract/maven/ContractConstant.java | 9 + .../contract/maven/ContractDeployExeUtil.java | 176 ------- .../contract/maven/ContractDeployMojo.java | 120 ----- .../contract/maven/ContractField.java | 43 ++ .../contract/maven/ContractMethod.java | 81 ++++ .../contract/maven/ContractResolveEngine.java | 459 ------------------ .../contract/maven/asm/ASMClassVisitor.java | 22 + .../contract/maven/asm/ASMMethodVisitor.java | 108 +++++ .../contract/maven/rule/BlackList.java | 154 ++++++ .../maven/rule/DependencyExclude.java | 93 ++++ .../contract/maven/rule/WhiteList.java | 30 ++ .../contract/maven/verify/ResolveEngine.java | 133 +++++ .../contract/maven/verify/VerifyEngine.java | 216 +++++++++ .../src/main/resources/blacks.conf | 11 + .../src/main/resources/config.properties | 8 - .../src/main/resources/provideds.conf | 22 + .../src/main/resources/whites.conf | 1 + .../blockchain/contract/ContractJarUtils.java | 89 +++- 23 files changed, 1321 insertions(+), 994 deletions(-) create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java create mode 100644 source/contract/contract-maven-plugin/src/main/resources/blacks.conf delete mode 100644 source/contract/contract-maven-plugin/src/main/resources/config.properties create mode 100644 source/contract/contract-maven-plugin/src/main/resources/provideds.conf create mode 100644 source/contract/contract-maven-plugin/src/main/resources/whites.conf diff --git a/source/contract/contract-maven-plugin/pom.xml b/source/contract/contract-maven-plugin/pom.xml index 6cca1389..9596695e 100644 --- a/source/contract/contract-maven-plugin/pom.xml +++ b/source/contract/contract-maven-plugin/pom.xml @@ -35,10 +35,6 @@ ${project.version} - - com.github.javaparser - javaparser-core - org.apache.maven @@ -58,6 +54,12 @@ 2.6 + + org.ow2.asm + asm + 5.0.4 + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java new file mode 100644 index 00000000..389dd33a --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java @@ -0,0 +1,98 @@ +package com.jd.blockchain.contract.maven; + +import com.jd.blockchain.contract.maven.rule.BlackList; +import com.jd.blockchain.contract.maven.rule.WhiteList; + +import java.util.List; + +public abstract class AbstractContract { + + protected String className; + + public String getClassName() { + return className; + } + + public String getDotClassName() { + return className.replaceAll("/", "."); + } + + protected String format(final String inputFormat) { + String formatResult; + + String outputFormat = inputFormat; + if (inputFormat.endsWith(";")) { + outputFormat = inputFormat.substring(0, inputFormat.length() - 1); + } + if (outputFormat.startsWith("[L") && outputFormat.length() > 2) { + // 说明是数组,但不显示 + formatResult = outputFormat.substring(2); + } else if (outputFormat.startsWith("[") && outputFormat.length() > 1) { + // 说明是数组 + formatResult = outputFormat.substring(1); + } else if (outputFormat.startsWith("L") && outputFormat.length() > 1) { + // 说明是非基础类型 + formatResult = outputFormat.substring(1); + } else { + formatResult = outputFormat; + } + + return formatResult; + } + + public static BlackList initBlack(List blackList) { + BlackList contractBlack = new BlackList(); + if (blackList != null && !blackList.isEmpty()) { + for (String black : blackList) { + // 首先判断该black是package还是 + String packageName = isPackageAndReturn(black); + if (packageName != null) { + // 说明是包 + contractBlack.addBlackPackage(packageName); + } else { + String[] classAndMethod = black.split("-"); + if (classAndMethod.length == 1) { + // 说明只有ClassName + contractBlack.addBlack(classAndMethod[0], BlackList.COMMON_METHOD); + } else { + contractBlack.addBlack(classAndMethod[0], classAndMethod[1]); + } + } + } + } + + return contractBlack; + } + + public static WhiteList initWhite(List whiteList) { + WhiteList contractWhite = new WhiteList(); + + if (whiteList != null && !whiteList.isEmpty()) { + for (String white : whiteList) { + String packageName = isPackageAndReturn(white); + if (packageName != null) { + // 说明是包 + contractWhite.addWhite(packageName); + } else { + contractWhite.addWhite(white); + } + } + } + + return contractWhite; + } + + /** + * 获取配置的packageName + * + * @param config + * @return + * 假设为包,则返回其包名,否则返回NULL + */ + public static String isPackageAndReturn(String config) { + if (config.endsWith("*")) { + return config.substring(0, config.length() - 2); + } + return null; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java deleted file mode 100644 index 81e5f272..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java +++ /dev/null @@ -1,216 +0,0 @@ -//package com.jd.blockchain.contract.maven; -// -//import org.apache.commons.io.FileUtils; -//import org.apache.maven.model.Model; -//import org.apache.maven.model.Plugin; -//import org.apache.maven.model.PluginExecution; -//import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -//import org.apache.maven.model.io.xpp3.MavenXpp3Writer; -//import org.apache.maven.plugin.AbstractMojo; -//import org.apache.maven.plugin.MojoFailureException; -//import org.apache.maven.plugins.annotations.Mojo; -//import org.apache.maven.plugins.annotations.Parameter; -//import org.apache.maven.project.MavenProject; -//import org.apache.maven.shared.invoker.*; -//import org.codehaus.plexus.util.xml.Xpp3Dom; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.ByteArrayOutputStream; -//import java.io.File; -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.util.ArrayList; -//import java.util.Collections; -//import java.util.List; -// -// -//@Mojo(name = "contractCheck") -//public class ContractCheckMojo extends AbstractMojo { -// -// Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); -// -// public static final String CONTRACT_VERIFY = "contractVerify"; -// -// private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; -// -// private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; -// -// private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; -// -// private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; -// -// private static final String GOALS_VERIFY = "package"; -// -// private static final String GOALS_PACKAGE = "package"; -// -// private static final String OUT_POM_XML = "ContractPom.xml"; -// -// @Parameter(defaultValue = "${project}", required = true, readonly = true) -// private MavenProject project; -// -// /** -// * jar's name; -// */ -// @Parameter -// private String finalName; -// -// /** -// * mainClass; -// */ -// @Parameter -// private String mainClass; -// /** -// * ledgerVersion; -// */ -// @Parameter -// private String ledgerVersion; -// -// /** -// * mvnHome; -// */ -// @Parameter -// private String mvnHome; -// -// /** -// * first compile the class, then parse it; -// */ -// @Override -// public void execute() throws MojoFailureException { -// compileFiles(); -// } -// -// private void compileFiles() throws MojoFailureException { -// try (FileInputStream fis = new FileInputStream(project.getFile())) { -// -// MavenXpp3Reader reader = new MavenXpp3Reader(); -// Model model = reader.read(fis); -// -// //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; -// Plugin plugin = model.getBuild().getPluginsAsMap() -// .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); -// if(plugin == null){ -// plugin = model.getBuild().getPluginsAsMap() -// .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); -// } -// -// if(plugin == null) { -// return; -// } -// -// model.getBuild().removePlugin(plugin); -// -// List plugins = new ArrayList<>(); -// plugins.add(createAssembly()); -// plugins.add(createContractVerify()); -// -// model.getBuild().setPlugins(plugins); -// -// handle(model); -// -// } catch (Exception e) { -// LOG.error(e.getMessage()); -// throw new MojoFailureException(e.getMessage()); -// } -// } -// -// private void invokeCompile(File file) { -// InvocationRequest request = new DefaultInvocationRequest(); -// -// Invoker invoker = new DefaultInvoker(); -// try { -// request.setPomFile(file); -// -// request.setGoals(Collections.singletonList(GOALS_VERIFY)); -// invoker.setMavenHome(new File(mvnHome)); -// invoker.execute(request); -// } catch (MavenInvocationException e) { -// LOG.error(e.getMessage()); -// throw new IllegalStateException(e); -// } -// } -// -// private Plugin createContractVerify() { -// Plugin plugin = new Plugin(); -// plugin.setGroupId(JDCHAIN_PACKAGE); -// plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); -// plugin.setVersion(ledgerVersion); -// -// Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); -// finalNameNode.setValue(finalName); -// Xpp3Dom configuration = new Xpp3Dom("configuration"); -// configuration.addChild(finalNameNode); -// -// plugin.setConfiguration(configuration); -// plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); -// -// return plugin; -// } -// -// private Plugin createAssembly() { -// Plugin plugin = new Plugin(); -// plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); -// -// Xpp3Dom configuration = new Xpp3Dom("configuration"); -// -// Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); -// mainClassNode.setValue(mainClass); -// -// Xpp3Dom manifest = new Xpp3Dom("manifest"); -// manifest.addChild(mainClassNode); -// -// Xpp3Dom archive = new Xpp3Dom("archive"); -// archive.addChild(manifest); -// -// Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); -// finalNameNode.setValue(finalName); -// -// Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); -// appendAssemblyId.setValue("false"); -// -// Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); -// descriptorRef.setValue("jar-with-dependencies"); -// Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); -// descriptorRefs.addChild(descriptorRef); -// -// configuration.addChild(finalNameNode); -// configuration.addChild(appendAssemblyId); -// configuration.addChild(archive); -// configuration.addChild(descriptorRefs); -// -// plugin.setConfiguration(configuration); -// plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); -// -// return plugin; -// } -// -// private List pluginExecution(String id, String phase, String goal) { -// PluginExecution pluginExecution = new PluginExecution(); -// pluginExecution.setId(id); -// pluginExecution.setPhase(phase); -// List goals = new ArrayList<>(); -// goals.add(goal); -// pluginExecution.setGoals(goals); -// List pluginExecutions = new ArrayList<>(); -// pluginExecutions.add(pluginExecution); -// -// return pluginExecutions; -// } -// -// private void handle(Model model) throws IOException { -// -// MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); -// -// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -// -// mavenXpp3Writer.write(outputStream, model); -// -// byte[] buffer = outputStream.toByteArray(); -// -// File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); -// -// FileUtils.writeByteArrayToFile(outPom, buffer); -// -// invokeCompile(outPom); -// } -//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java new file mode 100644 index 00000000..b9d3de7b --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java @@ -0,0 +1,66 @@ +package com.jd.blockchain.contract.maven; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ContractClass extends AbstractContract { + + // 若出现同名的方法则进行合并(将两个方法中涉及到的内容合并在一起) + private Map methods = new ConcurrentHashMap<>(); + + public ContractClass(String className) { + if (className.contains(".")) { + this.className = className.replaceAll("\\.", "/"); + } else { + this.className = className; + } + } + + /** + * 返回构造方法 + * + * @return + */ + public ContractMethod constructor() { + return methods.get(ContractConstant.METHOD_INIT); + } + + /** + * 返回该类的所有变量 + * + * @return + */ + public List fields() { + + List fields = new ArrayList<>(); + + // 构造方法 + ContractMethod initMethod = constructor(); + if (initMethod != null) { + fields.addAll(initMethod.getClassFieldList(className)); + } + // CLINIT方法 + ContractMethod clInitMethod = methods.get(ContractConstant.METHOD_CLINIT); + if (clInitMethod != null) { + fields.addAll(clInitMethod.getClassFieldList(className)); + } + return fields; + } + + public synchronized ContractMethod method(String methodName) { + if (methods.containsKey(methodName)) { + return methods.get(methodName); + } + ContractMethod method = new ContractMethod(this.className, methodName); + + methods.put(methodName, method); + + return method; + } + + public Map getMethods() { + return methods; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java index 61a3dfff..3ddf15a0 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java @@ -1,20 +1,94 @@ package com.jd.blockchain.contract.maven; +import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.maven.rule.BlackList; +import com.jd.blockchain.contract.maven.rule.WhiteList; +import com.jd.blockchain.contract.maven.rule.DependencyExclude; +import com.jd.blockchain.contract.maven.verify.ResolveEngine; +import com.jd.blockchain.contract.maven.verify.VerifyEngine; +import org.apache.commons.io.FileUtils; +import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.assembly.mojos.SingleAssemblyMojo; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.IOException; +import java.util.Set; @Mojo(name = "compile") public class ContractCompileMojo extends SingleAssemblyMojo { public static final String JAR_DEPENDENCE = "jar-with-dependencies"; + public static final String SCOPE_PROVIDED = "provided"; + + public static final String SCOPE_COMPILE = "compile"; + + private DependencyExclude dependencyExclude = new DependencyExclude(); + + private static BlackList black; + + private static WhiteList white; + + static { + init(); + } + @Override public void execute() throws MojoExecutionException, MojoFailureException { + // 首先对MainClass进行校验,要求必须有MainClass + String mainClass = mainClassVerify(); + + // 将JDChain本身代码之外的代码移除(不打包进整个Jar) + handleArtifactExclude(super.getProject().getDependencyArtifacts()); + + // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 + super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); + + // 执行打包命令 + super.execute(); + + // 将本次打包好的文件重新命名,以便于后续重新打包需要 + // 把文件改名,然后重新再生成一个文件 + File dstFile; + try { + dstFile = rename(getProject(), getFinalName()); + } catch (IOException e) { + getLog().error(e); + throw new MojoFailureException(e.getMessage()); + } + + // 首先校验该类的Jar包中是否包含不符合规范的命名,以及该类的代码中的部分解析 + File finalJarFile = verify(dstFile, mainClass); + + // 将所有的依赖的jar包全部打包进一个包中,以便于进行ASM检查 + handleArtifactCompile(super.getProject().getDependencyArtifacts()); + + // 然后再打包一次,本次打包完成后,其中的代码包含所有的class(JDK自身的除外) + super.execute(); + + // 对代码中的一些规则进行校验,主要是校验其是否包含一些不允许使用的类、包、方法等 + verify(mainClass); + + // 删除中间的一些文件 + try { + FileUtils.forceDelete(dstFile); + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } + + // 若执行到此处没有异常则表明打包成功,打印打包成功消息 + getLog().info(String.format("JDChain's Contract compile success, path = %s !", finalJarFile.getPath())); + } + + private String mainClassVerify() throws MojoFailureException { // 要求必须有MainClass + String mainClass; try { - String mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); + mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); // 校验MainClass,要求MainClass必须不能为空 if (mainClass == null || mainClass.length() == 0) { throw new MojoFailureException("MainClass is NULL !!!"); @@ -23,16 +97,76 @@ public class ContractCompileMojo extends SingleAssemblyMojo { } catch (Exception e) { throw new MojoFailureException("MainClass is null: " + e.getMessage(), e ); } + return mainClass; + } - // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 - super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); + private void handleArtifactExclude(Set artifacts) { + for (Artifact artifact : artifacts) { + String groupId = artifact.getGroupId(), artifactId = artifact.getArtifactId(); + if (dependencyExclude.isExclude(groupId, artifactId)) { + getLog().info(String.format("GroupId[%s] ArtifactId[%s] belongs to DependencyExclude !!!", groupId, artifactId)); + // 属于排除的名单之中 + artifact.setScope(SCOPE_PROVIDED); + } + } + } - // 执行打包命令 - super.execute(); + private void handleArtifactCompile(Set artifacts) { + for (Artifact artifact : artifacts) { + if (artifact.getScope().equals(SCOPE_PROVIDED)) { + // 将所有的provided设置为compile,以便于后续编译 + artifact.setScope(SCOPE_COMPILE); + } + } + } + + private File rename(MavenProject project, String finalName) throws IOException { + String srcJarPath = jarPath(project, finalName); + String dstJarPath = project.getBuild().getDirectory() + + File.separator + finalName + ".jar"; + File dstFile = new File(dstJarPath); + FileUtils.copyFile(new File(srcJarPath), dstFile); + FileUtils.forceDelete(new File(srcJarPath)); + return dstFile; + } + + private String jarPath(MavenProject project, String finalName) { + return project.getBuild().getDirectory() + + File.separator + finalName + "-" + JAR_DEPENDENCE + ".jar"; + } + + private void verify(String mainClass) throws MojoFailureException { + try { - ContractResolveEngine engine = new ContractResolveEngine(getLog(), getProject(), getFinalName()); + File jarFile = new File(jarPath(getProject(), getFinalName())); - // 打包并进行校验 - engine.compileAndVerify(); + VerifyEngine verifyEngine = new VerifyEngine(getLog(), jarFile, mainClass, black, white); + + verifyEngine.verify(); + + // 校验完成后将该jar包删除 + FileUtils.forceDelete(jarFile); + + } catch (Exception e) { + getLog().error(e); + throw new MojoFailureException(e.getMessage()); + } + } + + private File verify(File jarFile, String mainClass) throws MojoFailureException { + + ResolveEngine resolveEngine = new ResolveEngine(getLog(), jarFile, mainClass); + + return resolveEngine.verify(); + + } + + private static void init() { + try { + black = AbstractContract.initBlack(ContractJarUtils.loadBlackConf()); + white = AbstractContract.initWhite(ContractJarUtils.loadWhiteConf()); + } catch (Exception e) { + throw new IllegalStateException(e); + } } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java new file mode 100644 index 00000000..a94fb624 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java @@ -0,0 +1,9 @@ +package com.jd.blockchain.contract.maven; + +public class ContractConstant { + + public static final String METHOD_INIT = ""; + + public static final String METHOD_CLINIT = ""; + +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java deleted file mode 100644 index bd6a1b23..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java +++ /dev/null @@ -1,176 +0,0 @@ -//package com.jd.blockchain.contract.maven; -// -//import com.jd.blockchain.binaryproto.DataContractRegistry; -//import com.jd.blockchain.crypto.HashDigest; -//import com.jd.blockchain.crypto.PrivKey; -//import com.jd.blockchain.crypto.PubKey; -//import com.jd.blockchain.ledger.*; -//import com.jd.blockchain.sdk.BlockchainService; -//import com.jd.blockchain.sdk.client.GatewayServiceFactory; -//import com.jd.blockchain.tools.keygen.KeyGenCommand; -//import com.jd.blockchain.utils.Bytes; -//import com.jd.blockchain.utils.codec.Base58Utils; -//import com.jd.blockchain.utils.net.NetworkAddress; -// -//import java.io.File; -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.io.InputStream; -// -///** -// * @Author zhaogw -// * @Date 2018/11/2 10:18 -// */ -//public enum ContractDeployExeUtil { -// instance; -// private BlockchainService bcsrv; -// private Bytes contractAddress; -// -// public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ -// PubKey pub = null; -// PrivKey prv = null; -// try { -// prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); -// pub = KeyGenCommand.readPubKey(pubPath); -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// return new BlockchainKeypair(pub, prv); -// } -// -// public PubKey getPubKey(String pubPath){ -// PubKey pub = null; -// try { -// if(pubPath == null){ -// BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); -// pub = contractKeyPair.getPubKey(); -// }else { -// pub = KeyGenCommand.readPubKey(pubPath); -// } -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// return pub; -// } -// public byte[] getChainCode(String path){ -// byte[] chainCode = null; -// File file = null; -// InputStream input = null; -// try { -// file = new File(path); -// input = new FileInputStream(file); -// chainCode = new byte[input.available()]; -// input.read(chainCode); -// -// } catch (IOException e) { -// e.printStackTrace(); -// } finally { -// try { -// if(input!=null){ -// input.close(); -// } -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// return chainCode; -// } -// -// private void register(){ -// DataContractRegistry.register(TransactionContent.class); -// DataContractRegistry.register(TransactionContentBody.class); -// DataContractRegistry.register(TransactionRequest.class); -// DataContractRegistry.register(NodeRequest.class); -// DataContractRegistry.register(EndpointRequest.class); -// DataContractRegistry.register(TransactionResponse.class); -// DataContractRegistry.register(DataAccountKVSetOperation.class); -// DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); -// DataContractRegistry.register(Operation.class); -// DataContractRegistry.register(ContractCodeDeployOperation.class); -// DataContractRegistry.register(ContractEventSendOperation.class); -// DataContractRegistry.register(DataAccountRegisterOperation.class); -// DataContractRegistry.register(UserRegisterOperation.class); -// } -// -// public BlockchainService initBcsrv(String host, int port) { -// if(bcsrv!=null){ -// return bcsrv; -// } -// NetworkAddress addr = new NetworkAddress(host, port); -// GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); -// bcsrv = gwsrvFact.getBlockchainService(); -// return bcsrv; -// } -// -// public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ -// register(); -// -// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); -// txTpl.contracts().deploy(contractIdentity, chainCode); -// PreparedTransaction ptx = txTpl.prepare(); -// ptx.sign(ownerKey); -// // 提交并等待共识返回; -// TransactionResponse txResp = ptx.commit(); -// -// // 验证结果; -// contractAddress = contractIdentity.getAddress(); -// this.setContractAddress(contractAddress); -// System.out.println("contract's address="+contractAddress); -// return txResp.isSuccess(); -// } -// public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ -// register(); -// -// BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); -// initBcsrv(host,port); -// return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); -// } -// -// // 根据用户指定的公钥生成合约地址 -// public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, -// String ownerPassword, String chainCodePath,String pubPath){ -// PubKey pubKey = getPubKey(pubPath); -// BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); -// byte[] chainCode = getChainCode(chainCodePath); -// -// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); -// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -// initBcsrv(host,port); -// return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); -// } -// -// -//// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; -// -//// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, -//// String ownerPassword,String event,String contractArgs){ -//// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); -//// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -//// -//// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; -//// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); -//// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); -//// -//// // 签名; -//// PreparedTransaction ptx = txTpl.prepare(); -//// ptx.sign(ownerKey); -//// -//// // 提交并等待共识返回; -//// TransactionResponse txResp = ptx.commit(); -//// -//// // 验证结果; -//// return txResp.isSuccess(); -//// } -// -// public Bytes getContractAddress() { -// return contractAddress; -// } -// -// public void setContractAddress(Bytes contractAddress) { -// this.contractAddress = contractAddress; -// } -//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java deleted file mode 100644 index 89a82a36..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java +++ /dev/null @@ -1,120 +0,0 @@ -//package com.jd.blockchain.contract.maven; -// -//import com.jd.blockchain.crypto.HashDigest; -//import com.jd.blockchain.crypto.PrivKey; -//import com.jd.blockchain.crypto.PubKey; -//import com.jd.blockchain.ledger.BlockchainKeypair; -//import com.jd.blockchain.tools.keygen.KeyGenCommand; -//import com.jd.blockchain.utils.StringUtils; -//import com.jd.blockchain.utils.codec.Base58Utils; -//import com.jd.blockchain.utils.io.FileUtils; -//import org.apache.maven.plugin.AbstractMojo; -//import org.apache.maven.plugin.MojoFailureException; -//import org.apache.maven.plugins.annotations.Mojo; -//import org.apache.maven.plugins.annotations.Parameter; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.File; -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.io.InputStream; -//import java.util.Properties; -// -///** -// * for contract remote deploy; -// * @goal contractDeploy -// * @phase process-sources -// * @Author zhaogw -// * @Date 2018/10/18 10:12 -// */ -// -//@Mojo(name = "deploy") -//public class ContractDeployMojo extends AbstractMojo { -// Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); -// -// @Parameter -// private File config; -// -// @Override -// public void execute()throws MojoFailureException { -// -// Properties prop = new Properties(); -// InputStream input = null; -// -// try { -// input = new FileInputStream(config); -// prop.load(input); -// -// } catch (IOException ex) { -// logger.error(ex.getMessage()); -// throw new MojoFailureException("io error"); -// } finally { -// if (input != null) { -// try { -// input.close(); -// } catch (IOException e) { -// logger.error(e.getMessage()); -// } -// } -// } -// int port; -// try { -// port = Integer.parseInt(prop.getProperty("port")); -// }catch (NumberFormatException e){ -// logger.error(e.getMessage()); -// throw new MojoFailureException("invalid port"); -// } -// String host = prop.getProperty("host"); -// String ledger = prop.getProperty("ledger"); -// String pubKey = prop.getProperty("pubKey"); -// String prvKey = prop.getProperty("prvKey"); -// String password = prop.getProperty("password"); -// String contractPath = prop.getProperty("contractPath"); -// -// -// if(StringUtils.isEmpty(host)){ -// logger.info("host不能为空"); -// return; -// } -// -// if(StringUtils.isEmpty(ledger)){ -// logger.info("ledger不能为空."); -// return; -// } -// if(StringUtils.isEmpty(pubKey)){ -// logger.info("pubKey不能为空."); -// return; -// } -// if(StringUtils.isEmpty(prvKey)){ -// logger.info("prvKey不能为空."); -// return; -// } -// if(StringUtils.isEmpty(contractPath)){ -// logger.info("contractPath不能为空."); -// return; -// } -// -// File contract = new File(contractPath); -// if (!contract.isFile()){ -// logger.info("文件"+contractPath+"不存在"); -// return; -// } -// byte[] contractBytes = FileUtils.readBytes(contractPath); -// -// -// PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); -// PubKey pub = KeyGenCommand.decodePubKey(pubKey); -// BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); -// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -// -// StringBuffer sb = new StringBuffer(); -// sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). -// append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); -// logger.info(sb.toString()); -// ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); -// } -// -//} -// -// diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java new file mode 100644 index 00000000..f4b0f396 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java @@ -0,0 +1,43 @@ +package com.jd.blockchain.contract.maven; + +public class ContractField extends AbstractContract { + + private String fieldName; + + private String fieldType; + + private boolean isStatic; + + public ContractField(String className, String fieldName, String fieldType) { + this(className, fieldName, fieldType, false); + } + + public ContractField(String className, String fieldName, String fieldType, boolean isStatic) { + this.className = format(className); + this.fieldName = fieldName; + this.fieldType = format(fieldType); + this.isStatic = isStatic; + } + + public String getFieldName() { + return fieldName; + } + + public String getFieldType() { + return fieldType; + } + + public boolean isStatic() { + return isStatic; + } + + @Override + public String toString() { + return "ContractField{" + + "className='" + className + '\'' + + ", fieldName='" + fieldName + '\'' + + ", fieldType='" + fieldType + '\'' + + ", isStatic=" + isStatic + + '}'; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java new file mode 100644 index 00000000..6dc2e3a7 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java @@ -0,0 +1,81 @@ +package com.jd.blockchain.contract.maven; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ContractMethod extends AbstractContract { + + private String methodName; + + private String[] paramTypes; + + private String[] returnTypes; + + private List fieldList = new ArrayList<>(); + + private List methodList = new ArrayList<>(); + + public ContractMethod(String className, String methodName) { + this(className, methodName, null, null); + } + + public ContractMethod(String className, String methodName, String[] paramTypes, String[] returnTypes) { + this.className = format(className); + this.methodName = methodName; + this.paramTypes = paramTypes; + this.returnTypes = returnTypes; + } + + public void addMethod(String className, String methodName, String[] paramTypes, String[] returnTypes) { + methodList.add(new ContractMethod(className, methodName, paramTypes, returnTypes)); + } + + public void addField(String className, String fieldName, String fieldType) { + this.fieldList.add(new ContractField(className, fieldName, fieldType)); + } + + public void addStaticField(String className, String fieldName, String fieldType) { + this.fieldList.add(new ContractField(className, fieldName, fieldType, true)); + } + + public String getMethodName() { + return methodName; + } + + public String[] getParamTypes() { + return paramTypes; + } + + public List getAllFieldList() { + return fieldList; + } + + public List getClassFieldList(String cName) { + List classFieldList = new ArrayList<>(); + if (!fieldList.isEmpty()) { + for (ContractField field : fieldList) { + if (field.getClassName().equals(cName)) { + classFieldList.add(field); + } + } + } + return classFieldList; + } + + public List getMethodList() { + return methodList; + } + + @Override + public String toString() { + return "ContractMethod{" + + "className='" + className + '\'' + + ", methodName='" + methodName + '\'' + + ", paramTypes=" + Arrays.toString(paramTypes) + + ", returnTypes=" + Arrays.toString(returnTypes) + + ", fieldList=" + fieldList + + ", methodList=" + methodList + + '}'; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java deleted file mode 100644 index 3fb2404d..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java +++ /dev/null @@ -1,459 +0,0 @@ -package com.jd.blockchain.contract.maven; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import com.jd.blockchain.contract.ContractType; -import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.project.MavenProject; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import static com.jd.blockchain.contract.ContractJarUtils.*; -import static com.jd.blockchain.contract.maven.ContractCompileMojo.JAR_DEPENDENCE; -import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; - -public class ContractResolveEngine { - - private static final String JAVA_SUFFIX = ".java"; - - private static final String PATH_DIRECT = - "src" + File.separator + - "main" + File.separator + - "java" + File.separator; - - private static final String CONFIG = "config.properties"; - - private static final String BLACK_PACKAGE_LIST = "black.package.list"; - - private static final String BLACK_CLASS_LIST = "black.class.list"; - - private static final String BLACK_NAME_LIST = "black.name.list"; - - private static List blackNameList; - - private static List blackPackageList; - - private static Set blackClassSet; - - static { - try { - configInit(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - private Log LOGGER; - - private MavenProject project; - - private String finalName; - - public ContractResolveEngine(Log LOGGER, MavenProject project, String finalName) { - this.LOGGER = LOGGER; - this.project = project; - this.finalName = finalName; - } - - public void compileAndVerify() throws MojoFailureException { - try { - jarCopy(); - verify(compileCustomJar()); - } catch (IOException e) { - throw new MojoFailureException("IO Error : " + e.getMessage(), e); - } catch (MojoFailureException ex) { - throw ex; - } - } - - private void jarCopy() throws IOException { - String srcJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-" + JAR_DEPENDENCE + ".jar"; - String dstJarPath = project.getBuild().getDirectory() + - File.separator + finalName + ".jar"; - FileUtils.copyFile(new File(srcJarPath), new File(dstJarPath)); - } - - private File compileCustomJar() throws IOException { - return copyAndManage(project, finalName); - } - - private void verify(File jarFile) throws MojoFailureException { - try { - // 首先校验MainClass - try { - verifyMainClass(jarFile); - } catch (Exception e) { - jarFile.delete(); - LOGGER.error(e.getMessage()); - throw e; - } - - LinkedList totalClassList = loadAllClass(jarFile); - // 该项目路径 - String projectDir = project.getBasedir().getPath(); - // 代码路径 - String codeBaseDir = projectDir + File.separator + PATH_DIRECT; - - if (!totalClassList.isEmpty()) { - - boolean isOK = true; - - for (String clazz : totalClassList) { - - LOGGER.debug(String.format("Verify Class[%s] start......", clazz)); - // 获取其包名 - String packageName = packageName(clazz); - - LOGGER.debug(String.format("Class[%s] 's package name = %s", clazz, packageName)); - - // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 - boolean isNameBlack = false; - for (ContractPackage blackName : blackNameList) { - isNameBlack = verifyPackage(packageName, blackName); - if (isNameBlack) { - break; - } - } - - // 假设是黑名单则打印日志 - if (isNameBlack) { - // 打印信息供检查 - LOGGER.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); - isOK = false; - continue; - } - - // 获取该Class对应的Java文件 - File javaFile = new File(codeBaseDir + clazz + JAVA_SUFFIX); - - boolean isNeedDelete = false; - if (!javaFile.exists()) { - LOGGER.debug(String.format("Class[%s] -> Java[%s] is not exist, start decompile ...", clazz, jarFile.getPath())); - // 表明不是项目中的内容,需要通过反编译获取该文件 - String source = null; - try { - source = decompileJarFile(jarFile.getPath(), clazz, true, StandardCharsets.UTF_8.name()); - if (source == null || source.length() == 0) { - throw new IllegalStateException(); - } - } catch (Exception e) { - LOGGER.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); - } - // 将source写入Java文件 - File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); - FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); - javaFile = sourceTempJavaFile; - isNeedDelete = true; - } else { - LOGGER.debug(String.format("Class[%s] -> Java[%s] is exist", clazz, jarFile.getPath())); - } - - LOGGER.info(String.format("Parse Java File [%s] start......", javaFile.getPath())); - // 解析文件中的内容 - CompilationUnit compilationUnit = JavaParser.parse(javaFile); - - MethodVisitor methodVisitor = new MethodVisitor(); - - compilationUnit.accept(methodVisitor, null); - - List imports = methodVisitor.importClasses; - - if (!imports.isEmpty()) { - for (String importClass : imports) { - LOGGER.debug(String.format("Class[%s] read import -> [%s]", clazz, importClass)); - if (importClass.endsWith("*")) { - // 导入的是包 - for (ContractPackage blackPackage : blackPackageList) { - String importPackageName = importClass.substring(0, importClass.length() - 2); - if (verifyPackage(importPackageName, blackPackage)) { - // 打印信息供检查 - LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); - isOK = false; - break; - } - } - } else { - // 导入的是具体的类,则判断类黑名单 + 包黑名单 - if (blackClassSet.contains(importClass)) { - // 包含导入类,该方式无法通过验证 - LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); - isOK = false; - } else { - // 判断导入的该类与黑名单导入包的对应关系 - for (ContractPackage blackPackage : blackPackageList) { - if (verifyClass(importClass, blackPackage)) { - LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); - isOK = false; - break; - } - } - } - } - } - } - if (isNeedDelete) { - javaFile.delete(); - } - LOGGER.debug(String.format("Verify Class[%s] end......", clazz)); - } - if (!isOK) { - // 需要将该Jar删除 - jarFile.delete(); - throw new IllegalStateException("There are many Illegal information, please check !!!"); - } - } else { - jarFile.delete(); - throw new IllegalStateException("There is none class !!!"); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - throw new MojoFailureException(e.getMessage(), e); - } - } - - private void verifyMainClass(File jarFile) throws Exception { - // 加载main-class,开始校验类型 - LOGGER.debug(String.format("Verify Jar [%s] 's MainClass start...", jarFile.getName())); - URL jarURL = jarFile.toURI().toURL(); - ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); - Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); - String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); - Class mainClass = classLoader.loadClass(contractMainClass); - ContractType.resolve(mainClass); - LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", jarFile.getName())); - } - - private static List blackNameList(Properties config) { - return blackList(config, BLACK_NAME_LIST); - } - - private static Set blackClassSet(Properties config) { - Set blackClassSet = new HashSet<>(); - String attrProp = config.getProperty(BLACK_CLASS_LIST); - if (attrProp != null && attrProp.length() > 0) { - String[] attrPropArray = attrProp.split(","); - for (String attr : attrPropArray) { - blackClassSet.add(attr.trim()); - } - } - return blackClassSet; - } - - private static List blackPackageList(Properties config) { - return blackList(config, BLACK_PACKAGE_LIST); - } - - private static List blackList(Properties config, String attrName) { - List list = new ArrayList<>(); - String attrProp = config.getProperty(attrName); - if (attrProp != null || attrProp.length() > 0) { - String[] attrPropArray = attrProp.split(","); - for (String attr : attrPropArray) { - list.add(new ContractPackage(attr)); - } - } - return list; - } - - private boolean verifyPackage(String packageName, ContractPackage contractPackage) { - boolean verify = false; - if (packageName.equals(contractPackage.packageName)) { - // 完全相同 - verify = true; - } else if (packageName.startsWith(contractPackage.packageName) && - contractPackage.isTotal) { - // 以某个包开头 - verify = true; - } - return verify; - } - - private boolean verifyClass(String className, ContractPackage contractPackage) { - boolean verify = false; - - if (contractPackage.isTotal) { - // 表示该包下面的其他所有包都会受限制,此处需要判断起始 - if (className.startsWith(contractPackage.packageName)) { - verify = true; - } - } else { - // 表示该包必须完整匹配ClassName所在包 - // 获取ClassName所在包 - String packageName = packageNameByDot(className); - if (packageName.equals(contractPackage.packageName)) { - verify = true; - } - } - return verify; - } - - private String packageNameByDot(String className) { - String[] array = className.split("."); - if (Character.isLowerCase(array[array.length - 2].charAt(0))) { - // 如果是小写,表示非内部类 - // 获取完整包名 - return className.substring(0, className.lastIndexOf(".")); - } - // 表示为内部类,该包拼装组成 - StringBuilder buffer = new StringBuilder(); - for (String s : array) { - if (buffer.length() > 0) { - buffer.append("."); - } - if (Character.isUpperCase(s.charAt(0))) { - // 表明已经到具体类 - break; - } - buffer.append(s); - } - - if (buffer.length() == 0) { - throw new IllegalStateException(String.format("Import Class [%s] Illegal !!!", className)); - } - - return buffer.toString(); - } - - private String packageName(String clazz) { - int index = clazz.lastIndexOf("/"); - String packageName = clazz.substring(0, index); - return packageName.replaceAll("/", "."); - } - - private File copyAndManage(MavenProject project, String finalName) throws IOException { - // 首先将Jar包转换为指定的格式 - String srcJarPath = project.getBuild().getDirectory() + - File.separator + finalName + ".jar"; - - String dstJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-temp-" + System.currentTimeMillis() + ".jar"; - - File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); - - LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", srcJarPath, dstJarPath)); - // 首先进行Copy处理 - copy(srcJar, dstJar); - LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", srcJarPath, dstJarPath)); - - byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); - - String finalJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-jdchain.jar"; - - File finalJar = new File(finalJarPath); - - copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); - - // 删除临时文件 - FileUtils.forceDelete(dstJar); - - return finalJar; - } - - private static void configInit() throws Exception { - Properties config = loadConfig(); - - blackNameList = blackNameList(config); - - blackPackageList = blackPackageList(config); - - blackClassSet = blackClassSet(config); - } - - private static Properties loadConfig() throws Exception { - - Properties properties = new Properties(); - - properties.load(ContractResolveEngine.class.getResourceAsStream(File.separator + CONFIG)); - - return properties; - } - - private LinkedList loadAllClass(File file) throws Exception { - JarFile jarFile = new JarFile(file); - LinkedList allClass = new LinkedList<>(); - Enumeration jarEntries = jarFile.entries(); - while (jarEntries.hasMoreElements()) { - JarEntry jarEntry = jarEntries.nextElement(); - String entryName = jarEntry.getName(); - if (entryName.endsWith(".class")) { - // 内部类,不需要处理 - if (!entryName.contains("$")) { - allClass.addLast(entryName.substring(0, entryName.length() - 6)); - } - } - } - return allClass; - } - - private String tempPath(String codeBaseDir, String clazz) { - // 获取最后的名称 - String[] classArray = clazz.split("/"); - String tempPath = codeBaseDir + classArray[classArray.length - 1] + "_" + - System.currentTimeMillis() + "_" + System.nanoTime() + JAVA_SUFFIX; - return tempPath; - } - - private static class MethodVisitor extends VoidVisitorAdapter { - - private List importClasses = new ArrayList<>(); - - @Override - public void visit(ImportDeclaration n, Void arg) { - importClasses.add(parseClass(n.toString())); - super.visit(n, arg); - } - - private String parseClass(String importInfo) { - String className = importInfo.substring(7, importInfo.length() - 2); - if (importInfo.startsWith("import static ")) { - // 获取静态方法的类信息 - className = importInfo.substring(14, importInfo.lastIndexOf(".")); - } - if (!className.contains(".")) { - throw new IllegalStateException(String.format("Import Class [%s] is Illegal !!", className)); - } - return className; - } - } - - private static class ContractPackage { - - private String packageName; - - private boolean isTotal = false; - - public ContractPackage() { - } - - public ContractPackage(String totalPackage) { - if (totalPackage.endsWith("*")) { - this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); - this.isTotal = true; - } else { - this.packageName = totalPackage; - } - } - - public String getPackageName() { - return packageName; - } - - public boolean isTotal() { - return isTotal; - } - } -} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java new file mode 100644 index 00000000..27bfeee4 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java @@ -0,0 +1,22 @@ +package com.jd.blockchain.contract.maven.asm; + +import com.jd.blockchain.contract.maven.ContractClass; +import com.jd.blockchain.contract.maven.ContractMethod; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class ASMClassVisitor extends ClassVisitor { + + private ContractClass contractClass; + + public ASMClassVisitor(ContractClass contractClass) { + super(Opcodes.ASM5); + this.contractClass = contractClass; + } + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor superMV = super.visitMethod(access, name, desc, signature, exceptions); + ContractMethod method = this.contractClass.method(name); + return new ASMMethodVisitor(superMV, method); + } +} \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java new file mode 100644 index 00000000..03488203 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java @@ -0,0 +1,108 @@ +package com.jd.blockchain.contract.maven.asm; + +import com.jd.blockchain.contract.maven.ContractMethod; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypePath; + +import java.util.ArrayList; +import java.util.List; + +public class ASMMethodVisitor extends MethodVisitor { + + private ContractMethod method; + + public ASMMethodVisitor(MethodVisitor mv, ContractMethod method) { + super(Opcodes.ASM5, mv); + this.method = method; + } + + @Override + public void visitFieldInsn(int type, String cName, String fName, String fType) { + if (type == 178 || type == 179) { + this.method.addStaticField(cName, fName, fType); + } else { + this.method.addField(cName, fName, fType); + } + super.visitFieldInsn(type, cName, fName, fType); + } + + @Override + public void visitMethodInsn(int type, String cName, String mName, String params, boolean b) { + ParamsAndReturn paramsAndReturn = resolveParamsAndReturn(params); + this.method.addMethod(cName, mName, paramsAndReturn.paramTypes, paramsAndReturn.returnTypes); + super.visitMethodInsn(type, cName, mName, params, b); + } + + private ParamsAndReturn resolveParamsAndReturn(String params) { + // 格式: + // 1、(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + // 2、()I + // 3、(Ljava/lang/String;)V + // 4、()V + // 5、([Ljava/lang/Object;)Ljava/util/List; false + // 从上面分析可以得出:括号内的是入参,右括号后面的是返回值,其中V表示Void,即空; + String[] paramArray = params.split("\\)"); + String paramTypeChars = ""; + if (!paramArray[0].equals("(")) { + // 表明入参不为空 + paramTypeChars = paramArray[0].split("\\(")[1]; + } + String returnTypeChars = paramArray[1]; + return new ParamsAndReturn(paramTypeChars, returnTypeChars); + } + + static class ParamsAndReturn { + + String[] paramTypes; + + String[] returnTypes; + + public ParamsAndReturn(String paramsTypeChars, String returnTypeChars) { + initParamsType(paramsTypeChars); + initReturnType(returnTypeChars); + } + + private void initParamsType(String paramsTypeChars) { + List paramList = handleTypes(paramsTypeChars); + if (!paramList.isEmpty()) { + this.paramTypes = new String[paramList.size()]; + paramList.toArray(this.paramTypes); + } + } + + private void initReturnType(String returnTypeChar) { + // 按照分号分隔 + List returnList = handleTypes(returnTypeChar); + if (!returnList.isEmpty()) { + this.returnTypes = new String[returnList.size()]; + returnList.toArray(this.returnTypes); + } + } + + private List handleTypes(String typeChars) { + String[] types = typeChars.split(";"); + List typeList = new ArrayList<>(); + if (types.length > 0) { + for (String type : types) { + if (type.length() > 0) { + if (type.startsWith("[L") && type.length() > 2) { + // 说明是数组 + typeList.add(type.substring(2) + "[]"); + } else if (type.startsWith("[") && type.length() > 1) { + // 说明是数组 + typeList.add(type.substring(1)); + } else if (type.startsWith("L") && type.length() > 1) { + // 说明是非基础类型 + typeList.add(type.substring(1)); + } else { + typeList.add(type); + } + } + } + } + return typeList; + } + } +} \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java new file mode 100644 index 00000000..97044df6 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java @@ -0,0 +1,154 @@ +package com.jd.blockchain.contract.maven.rule; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class BlackList { + + public static final String COMMON_METHOD = "*"; + + public static final String INIT_METHOD = "init"; + + // 合约黑名单 + private final Map blackClassMap = new ConcurrentHashMap<>(); + + private final List blackPackages = new ArrayList<>(); + + public synchronized BlackList addBlack(String className, String methodName) { + String trimClassName = className.trim(); + BlackClass blackClass = blackClassMap.get(trimClassName); + if (blackClass != null) { + blackClass.addMethod(methodName); + } else { + blackClass = new BlackClass(trimClassName); + blackClass.addMethod(methodName); + blackClassMap.put(trimClassName, blackClass); + } + return this; + } + + public synchronized BlackList addBlack(Class clazz, String methodName) { + return addBlack(clazz.getName(), methodName); + } + + public synchronized BlackList addBlack(Class clazz) { + return addBlack(clazz.getName(), COMMON_METHOD); + } + + public synchronized BlackList addBlackPackage(String packageName) { + blackPackages.add(packageName.trim() + "."); // 末尾增加一个点,防止后续判断是拼凑 + return this; + } + + public boolean isBlackClass(String className) { + if (isContainsPackage(className)) { + return true; + } + BlackClass blackClass = blackClassMap.get(className); + if (blackClass == null) { + return false; + } + return blackClass.isBlack(); + } + + public boolean isBlack(Class clazz, String methodName) { + // 判断该Class是否属于黑名单 + if (isCurrentClassBlack(clazz, methodName)) { + return true; + } + // 当前Class不是黑名单的情况下,处理其对应的父类和接口 + // 获取该Class对应的接口和父类列表 + Set> superClassAndAllInterfaces = new HashSet<>(); + + loadSuperClassAndAllInterfaces(clazz, superClassAndAllInterfaces); + + // 循环判断每个父类和接口 + for (Class currClass : superClassAndAllInterfaces) { + if (isCurrentClassBlack(currClass, methodName)) { + return true; + } + } + return false; + } + + public boolean isCurrentClassBlack(Class clazz, String methodName) { + + String packageName = clazz.getPackage().getName(); + for (String bp : blackPackages) { + if (packageName.equals(bp) || packageName.startsWith(bp)) { + return true; + } + } + // 判断该类本身是否属于黑名单 + String className = clazz.getName(); + BlackClass blackClass = blackClassMap.get(className); + if (blackClass != null) { + // 判断其方法 + return blackClass.isBlack(methodName); + } + return false; + } + + public boolean isBlackField(Class clazz) { + return isBlack(clazz, INIT_METHOD); + } + + private boolean isContainsPackage(String className) { + for (String bp : blackPackages) { + if (className.equals(bp) || className.startsWith(bp)) { + return true; + } + } + return false; + } + + private void loadSuperClassAndAllInterfaces(Class currentClass, Set> allClassList) { + if (currentClass == null) { + return; + } + + if (!allClassList.contains(currentClass)) { + allClassList.add(currentClass); + // 处理其父类 + Class superClass = currentClass.getSuperclass(); + loadSuperClassAndAllInterfaces(superClass, allClassList); + + // 处理其所有接口 + Class[] allInterfaces = currentClass.getInterfaces(); + if (allInterfaces != null && allInterfaces.length > 0) { + for (Class intf : allInterfaces) { + loadSuperClassAndAllInterfaces(intf, allClassList); + } + } + } + } + + private static class BlackClass { + + String className; + + Set methods = new HashSet<>(); + + BlackClass(String className) { + this.className = className; + } + + void addMethod(String methodName) { + methods.add(methodName); + } + + boolean isBlack(String methodName) { + // 假设method为*则表示所有的方法 + if (methods.contains(COMMON_METHOD)) { + return true; + } + return methods.contains(methodName); + } + + boolean isBlack() { + return isBlack(COMMON_METHOD); + } + } +} + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java new file mode 100644 index 00000000..6c3dca44 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java @@ -0,0 +1,93 @@ +package com.jd.blockchain.contract.maven.rule; + +import com.jd.blockchain.contract.ContractJarUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DependencyExclude { + + private static final String COMMON_ARTIFACTID = "*"; + + private static final String CONFIG = "provided.conf"; + + private static final Map> DEPENDENCYS = new ConcurrentHashMap<>(); + + static { + try { + init(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static void init() throws Exception { + List readLines = ContractJarUtils.loadConfig(CONFIG); + if (!readLines.isEmpty()) { + for (String line : readLines) { + // groupId/artifactId + String[] lines = line.split(","); + if (lines.length > 0) { + for (String depend : lines) { + String[] depends = depend.split("/"); + if (depends.length == 2) { + String groupId = depends[0], artifactId = depends[1]; + Dependency dependency = new Dependency(groupId, artifactId); + addDependency(dependency); + } + } + } + } + } + } + + private synchronized static void addDependency(Dependency dependency) { + String groupId = dependency.groupId; + if (!DEPENDENCYS.containsKey(groupId)) { + List dependencies = new ArrayList<>(); + dependencies.add(dependency); + DEPENDENCYS.put(groupId, dependencies); + } else { + List dependencies = DEPENDENCYS.get(groupId); + dependencies.add(dependency); + } + } + + public boolean isExclude(String groupId, String artifactId) { + List dependencies = DEPENDENCYS.get(groupId); + + if (dependencies == null || dependencies.isEmpty()) { + return false; + } + + for (Dependency dependency : dependencies) { + if (dependency.isEqualArtifactId(artifactId)) { + return true; + } + } + + return false; + } + + + static class Dependency { + + String groupId; + + String artifactId; + + public Dependency(String groupId, String artifactId) { + this.groupId = groupId; + this.artifactId = artifactId; + } + + public boolean isEqualArtifactId(String artiId) { + if (artifactId.equals(COMMON_ARTIFACTID)) { + return true; + } + return artifactId.equals(artiId); + } + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java new file mode 100644 index 00000000..eeb1e250 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java @@ -0,0 +1,30 @@ +package com.jd.blockchain.contract.maven.rule; + +import java.util.ArrayList; +import java.util.List; + +public class WhiteList { + + // 合约白名单(白名单通常数量较少,主要是JDChain内部包) + private final List whiteClasses = new ArrayList<>(); + + public void addWhite(String className) { + whiteClasses.add(className.trim()); + } + + public boolean isWhite(Class clazz) { + String className = clazz.getName(); + return isWhite(className); + } + + public boolean isWhite(String className) { + for (String white : whiteClasses) { + if (white.equals(className) || className.startsWith(white)) { + return true; + } + } + return false; + } +} + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java new file mode 100644 index 00000000..059f7337 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java @@ -0,0 +1,133 @@ +package com.jd.blockchain.contract.maven.verify; + +import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.ContractType; +import org.apache.commons.io.FileUtils; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import static com.jd.blockchain.contract.ContractJarUtils.*; + +public class ResolveEngine { + + private Log LOGGER; + + private File jarFile; + + private String mainClass; + + public ResolveEngine(Log LOGGER, File jarFile, String mainClass) { + this.LOGGER = LOGGER; + this.jarFile = jarFile; + this.mainClass = mainClass; + } + + public File verify() throws MojoFailureException { + try { + // 首先校验MainClass + ClassLoader classLoader = verifyMainClass(jarFile); + + // 检查jar包中所有的class的命名,要求其包名不能为com.jd.blockchain.* + LinkedList totalClasses = loadAllClass(jarFile); + + if (!totalClasses.isEmpty()) { + + for (String clazz : totalClasses) { + + String dotClassName = dotClassName(clazz); + + LOGGER.debug(String.format("Verify Dependency Class[%s] start......", dotClassName)); + // 获取其包名 + Class currentClass = classLoader.loadClass(dotClassName); + + String packageName = currentClass.getPackage().getName(); + + if (ContractJarUtils.isJDChainPackage(packageName)) { + throw new IllegalStateException(String.format("Class[%s]'s package[%s] cannot start with %s !", + dotClassName, packageName, ContractJarUtils.JDCHAIN_PACKAGE)); + } + + LOGGER.debug(String.format("Verify Class[%s] end......", clazz)); + } + } + + // 处理完成之后,生成finalName-JDChain-Contract.jar + return compileCustomJar(); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + throw new MojoFailureException(e.getMessage()); + } + } + + private File compileCustomJar() throws IOException { + + String fileParentPath = jarFile.getParentFile().getPath(); + + String jarFileName = jarFile.getName(); + + String fileName = jarFileName.substring(0, jarFileName.lastIndexOf(".")); + + // 首先将Jar包转换为指定的格式 + String dstJarPath = fileParentPath + File.separator + + fileName + "-temp-" + System.currentTimeMillis() + ".jar"; + + File srcJar = jarFile, dstJar = new File(dstJarPath); + + LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", jarFile.getPath(), dstJarPath)); + // 首先进行Copy处理 + copy(srcJar, dstJar); + + LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", jarFile.getPath(), dstJarPath)); + + byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + + String finalJarPath = fileParentPath + File.separator + fileName + "-JDChain-Contract.jar"; + + File finalJar = new File(finalJarPath); + + // 生成最终的Jar文件 + copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); + + // 删除临时文件 + FileUtils.forceDelete(dstJar); + + return finalJar; + } + + private ClassLoader verifyMainClass(File jarFile) throws Exception { + // 加载main-class,开始校验类型 + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass start...", jarFile.getName())); + URL jarURL = jarFile.toURI().toURL(); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}); + Class mClass = classLoader.loadClass(mainClass); + ContractType.resolve(mClass); + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", jarFile.getName())); + return classLoader; + } + + private LinkedList loadAllClass(File file) throws Exception { + JarFile jarFile = new JarFile(file); + LinkedList allClass = new LinkedList<>(); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (entryName.endsWith(".class")) { + // 内部类,不需要处理 + if (!entryName.contains("$")) { + allClass.addLast(entryName.substring(0, entryName.length() - 6)); + } + } + } + return allClass; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java new file mode 100644 index 00000000..ea3d723c --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java @@ -0,0 +1,216 @@ +package com.jd.blockchain.contract.maven.verify; + +import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.maven.ContractClass; +import com.jd.blockchain.contract.maven.ContractField; +import com.jd.blockchain.contract.maven.ContractMethod; +import com.jd.blockchain.contract.maven.asm.ASMClassVisitor; +import com.jd.blockchain.contract.maven.rule.BlackList; +import com.jd.blockchain.contract.maven.rule.WhiteList; +import org.apache.maven.plugin.logging.Log; +import org.objectweb.asm.ClassReader; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static com.jd.blockchain.contract.ContractJarUtils.loadAllClasses; + + +public class VerifyEngine { + + private Log LOGGER; + + private File jarFile; + + private String mainClass; + + private BlackList black; + + private WhiteList white; + + // 代表的只是当前方法,不代表该方法中的内部方法 + private Set haveManagedMethods = new HashSet<>(); + + // 代表的是处理的参数 + private Set haveManagedFields = new HashSet<>(); + + public VerifyEngine(Log LOGGER, File jarFile, String mainClass, BlackList black, WhiteList white) { + this.LOGGER = LOGGER; + this.jarFile = jarFile; + this.mainClass = mainClass; + this.black = black; + this.white = white; + } + + public void verify() throws Exception { + // 加载所有的jar,然后ASM获取MAP + URL jarURL = jarFile.toURI().toURL(); + + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarURL}); + + // 解析Jar包中所有的Class + Map allContractClasses = resolveClasses(jarClasses()); + + // 开始处理MainClass + verify(urlClassLoader, allContractClasses); + + } + + public void verify(URLClassLoader urlClassLoader, Map allContractClasses) throws Exception { + // 获取MainClass + String mainClassKey = convertClassKey(mainClass); + ContractClass mainContractClass = allContractClasses.get(mainClassKey); + if (mainContractClass == null) { + LOGGER.error(String.format("Load Main Class = [%s] NULL !!!", mainClass)); + throw new IllegalStateException("MainClass is NULL !!!"); + } + // 校验该Class中所有方法 + Map methods = mainContractClass.getMethods(); + if (!methods.isEmpty()) { + for (Map.Entry entry : methods.entrySet()) { + ContractMethod method = entry.getValue(); + verify(urlClassLoader, allContractClasses, method); + } + } + } + + public void verify(URLClassLoader urlClassLoader, Map allContractClasses, ContractMethod method) throws Exception { + // 获取方法中涉及到的所有的Class及Method + // 首先判断该方法对应的Class是否由urlClassLoader加载 + // 首先判断该ClassName对应方法是否处理过 + String managedKey = managedKey(method); + if (haveManagedMethods.contains(managedKey)) { + return; + } + // 将该方法设置为已处理 + haveManagedMethods.add(managedKey); + String dotClassName = method.getDotClassName(); + Class dotClass = urlClassLoader.loadClass(dotClassName); + + if (dotClass == null) { + return; + } + String dotClassLoader = null; + ClassLoader classLoader = dotClass.getClassLoader(); + + if (classLoader != null) { + dotClassLoader = dotClass.getClassLoader().toString(); + } + + if (dotClassLoader != null && dotClassLoader.contains("URLClassLoader")) { + + // 说明是URLClassLoader,这个需要先从黑名单和白名单列表中操作 + // 首先判断是否是黑名单,黑名单优先级最高 + if (black.isBlack(dotClass, method.getMethodName())) { + throw new IllegalStateException(String.format("Class [%s] Method [%s] is Black !!!", dotClassName, method.getMethodName())); + } else { + // 不是黑名单的情况下,判断是否为白名单 + if (white.isWhite(dotClass)) { + return; + } + // 如果不属于白名单,则需要判断其子方法 + List innerMethods = method.getMethodList(); + if (!innerMethods.isEmpty()) { + for (ContractMethod innerMethod : innerMethods) { + // 需要重新从AllMap中获取,因为生成时并未处理其关联关系 + ContractClass innerClass = allContractClasses.get(innerMethod.getClassName()); + if (innerClass != null) { + ContractMethod verifyMethod = innerClass.method(innerMethod.getMethodName()); + verify(urlClassLoader, allContractClasses, verifyMethod); + } else { + verify(urlClassLoader, allContractClasses, innerMethod); + } + } + } + List innerFields = method.getAllFieldList(); + if (!innerFields.isEmpty()) { + for (ContractField innerField : innerFields) { + verify(urlClassLoader, innerField); + } + } + } + } else { + // 非URLClassLoader加载的类,只需要做判断即可 + // 1、不再需要获取其方法; + // 2、只需要判断黑名单不需要判断白名单 + if (black.isBlack(dotClass, method.getMethodName())) { + throw new IllegalStateException(String.format("Class [%s] Method [%s] is Black !!!", dotClassName, method.getMethodName())); + } + } + } + + public void verify(URLClassLoader urlClassLoader, ContractField field) throws Exception { + // 获取方法中涉及到的所有的Class及Method + // 首先判断该方法对应的Class是否由urlClassLoader加载 + // 首先判断该ClassName对应方法是否处理过 + String managedKey = managedKey(field); + if (haveManagedFields.contains(managedKey)) { + return; + } + // 将该字段设置为已读 + haveManagedFields.add(managedKey); + + Class dotClass = urlClassLoader.loadClass(field.getDotClassName()); + + if (dotClass == null) { + return; + } + + if (black.isBlackField(dotClass)) { + throw new IllegalStateException(String.format("Class [%s] Field [%s] is Black !!!", field.getDotClassName(), field.getFieldName())); + } + } + + private Map jarClasses() throws Exception { + return loadAllClasses(jarFile); + } + + private Map resolveClasses(Map allClasses) { + + Map allContractClasses = new ConcurrentHashMap<>(); + + for (Map.Entry entry : allClasses.entrySet()) { + byte[] classContent = entry.getValue(); + if (classContent == null || classContent.length == 0) { + continue; + } + String className = entry.getKey().substring(0, entry.getKey().length() - 6); + + String dotClassName = ContractJarUtils.dotClassName(className); + if (white.isWhite(dotClassName) || black.isBlackClass(dotClassName)) { + continue; + } + + LOGGER.info(String.format("Resolve Class [%s] ...", className)); + + ContractClass contractClass = new ContractClass(className); + ClassReader cr = new ClassReader(classContent); + cr.accept(new ASMClassVisitor(contractClass), ClassReader.SKIP_DEBUG); + allContractClasses.put(className, contractClass); + } + return allContractClasses; + } + + private String convertClassKey(final String classKey) { + String newClassKey = classKey; + if (classKey.endsWith(".class")) { + newClassKey = classKey.substring(0, classKey.length() - 6); + } + newClassKey = newClassKey.replaceAll("\\.", "/"); + return newClassKey; + } + + private String managedKey(ContractMethod method) { + return method.getDotClassName() + "-" + method.getMethodName(); + } + + private String managedKey(ContractField field) { + return field.getDotClassName() + "--" + field.getFieldName(); + } +} diff --git a/source/contract/contract-maven-plugin/src/main/resources/blacks.conf b/source/contract/contract-maven-plugin/src/main/resources/blacks.conf new file mode 100644 index 00000000..533c4850 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/resources/blacks.conf @@ -0,0 +1,11 @@ +java.io.* +java.nio.* +java.net.* +java.sql.* +java.lang.reflect.* +java.lang.Class +java.lang.ClassLoader +java.util.Random +java.lang.System-currentTimeMillis +java.lang.System-nanoTime +com.jd.blockchain.ledger.BlockchainKeyGenerator-generate \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/resources/config.properties b/source/contract/contract-maven-plugin/src/main/resources/config.properties deleted file mode 100644 index b54de2fb..00000000 --- a/source/contract/contract-maven-plugin/src/main/resources/config.properties +++ /dev/null @@ -1,8 +0,0 @@ -#black.name.list:打包为合约Jar后,每个Class文件不允许使用或包含的名称,默认不允许使用com.jd.blockchain.*及一些内部已经引用的包 -black.name.list=com.jd.blockchain.*, com.alibaba.fastjson.*, org.apache.commons.io.*, org.apache.commons.codec.*, io.netty.* - -#black.package.list:打包为合约中的每个Class都不允许使用的包列表,某个包下面的所有包通过.*表示 -black.package.list=java.io.*, java.nio.*, java.net.*, org.apache.commons.io.* - -#black.class.list:打包为合约中的每个Class都不允许使用的类列表 -black.class.list=java.util.Random, com.jd.blockchain.ledger.BlockchainKeyGenerator \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/resources/provideds.conf b/source/contract/contract-maven-plugin/src/main/resources/provideds.conf new file mode 100644 index 00000000..b2df66eb --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/resources/provideds.conf @@ -0,0 +1,22 @@ +com.jd.blockchain/* +com.alibaba/fastjson +org.slf4j/* +org.apache.logging.log4j/* +org.aspectj/* +redis.clients/* +org.rocksdb/* +io.grpc/* +org.apache.commons/* +org.apache.httpcomponents/* +org.apache.logging.log4j/* +org.reflections/reflections +com.google.guava/guava +commons-cli/commons-cli +commons-codec/commons-codec +commons-httpclient/commons-httpclient +commons-io/commons-io +io.netty/* +org.slf4j/* +org.springframework.boot/* +org.springframework.security/* +org.springframework/* \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/resources/whites.conf b/source/contract/contract-maven-plugin/src/main/resources/whites.conf new file mode 100644 index 00000000..a495db60 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/resources/whites.conf @@ -0,0 +1 @@ +com.jd.blockchain.* \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java index 5a183006..d27323f2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java @@ -4,21 +4,23 @@ import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.HashFunction; import com.jd.blockchain.utils.io.BytesUtils; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.*; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.Random; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; public class ContractJarUtils { + public static final String BLACK_CONF = "black.conf"; + + public static final String WHITE_CONF = "white.conf"; + private static final String CONTRACT_MF = "META-INF/CONTRACT.MF"; private static final HashFunction HASH_FUNCTION = Crypto.getHashFunction("SHA256"); @@ -27,6 +29,87 @@ public class ContractJarUtils { private static final byte[] JDCHAIN_MARK = "JDChain".getBytes(StandardCharsets.UTF_8); + public static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; + + public static boolean isJDChainPackage(String packageName) { + if (packageName.equals(JDCHAIN_PACKAGE)) { + return true; + } + return packageName.startsWith(JDCHAIN_PACKAGE + "."); + } + + public static List loadWhiteConf() { + + return resolveConfig(WHITE_CONF); + } + + public static List loadBlackConf() { + return resolveConfig(BLACK_CONF); + } + + public static List resolveConfig(String fileName) { + List configs = new ArrayList<>(); + + try { + List readLines = loadConfig(fileName); + if (!readLines.isEmpty()) { + for (String readLine : readLines) { + String[] lines = readLine.split(","); + configs.addAll(Arrays.asList(lines)); + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + + return configs; + } + + public static List loadConfig(String fileName) throws Exception { + + return IOUtils.readLines( + ContractJarUtils.class.getResourceAsStream(File.separator + fileName)); + } + + public static Map loadAllClasses(final File jar) throws Exception { + Map allClasses = new HashMap<>(); + JarFile jarFile = new JarFile(jar); + Enumeration jarEntries = jarFile.entries(); + while(jarEntries.hasMoreElements()){ + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (verify(entryName)) { + byte[] classContent = readStream(jarFile.getInputStream(jarEntry)); + if (classContent != null && classContent.length > 0) { + allClasses.put(entryName, classContent); + } + } + } + jarFile.close(); + + return allClasses; + } + + private static boolean verify(String entryName) { + + if (entryName.endsWith(".class") + && !entryName.startsWith("META-INF") + && !entryName.contains("-") + && entryName.contains("/")) { + return true; + } + return false; + } + + public static String dotClassName(String className) { + String dotClassName = className; + if (className.endsWith(".class")) { + dotClassName = className.substring(0, className.length() - 6); + } + dotClassName = dotClassName.replaceAll("/", "."); + return dotClassName; + } + public static void verify(byte[] chainCode) { if (chainCode == null || chainCode.length == 0) { throw new IllegalStateException("Contract's chaincode is empty !!!"); From c7359284de4d7a9bfcb6625580b3a043e685099d Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 09:49:26 +0800 Subject: [PATCH 013/124] add .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..b179f30c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +jdk: + - openjdk8 From 8cdd542470aafcbe7a51d5fe61f445b7f25c4d86 Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 09:57:34 +0800 Subject: [PATCH 014/124] Update .travis.yml --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index b179f30c..b8d5bb25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,9 @@ language: java jdk: - openjdk8 + +before_install: + - cd ./source + +script: + - mvn clean package -Dmaven.test.skip=true From f265fd359005881c392182c432122db0552bc920 Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:13:23 +0800 Subject: [PATCH 015/124] add core-0.1.4.jar --- tools/core-0.1.4.jar | Bin 0 -> 67196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/core-0.1.4.jar diff --git a/tools/core-0.1.4.jar b/tools/core-0.1.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..85ea3a4a69aaa5b600d609edc4004765aad4510c GIT binary patch literal 67196 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
        r@GhCqJP5{`yn#)b2OrKi8KPuP_S@XvIqa_tzon z==2*-9jL71zi32ugb0;$pt6oX#{REL|7gS>x8I;QKiLp&1tB|~Cx&EN8tC+|K<$BP z`70F!-0`@DD&gqv0B9lq0d5mWNP!5zodNg{C%QjVK|q4nE$~0X`Tf1qO2`Tv_0F5I z0Bv1#4+;vHs=rb}z>&`_GJfb$9RM>7kcr(tR01;O_fH800nYxn2>6Ep;E>gT41Iee zWjyE>DZj3*{X-=nLn+?im&0zs|9xOZ$g)Gmw!48BN8SSen@D$%WXL#HH)OoHTgd-z zM4YDGBI56(VnD(n9bn(U z_499m-|7wsy4};+hq*8jvm`ZzPzN{U+h| zg&fkw-VMs3@;B699PU9D3escE4GGlr8|i1EAk6+DnUMZaZkR?bzcK&II|?KR(*3{< zhp+QD&Oh$mAkE!xX!pB+qy5$R9kO7Mmd!UPkKW%<|I5x95(;S(cms_c`VIP*horx= z5QL;dnxWm$eTV-)bV#E#NI8&Kt8e6>Pyc`9{93X8esc$T>FGwy($ei>{#)h$_iV_+ z*Bf@#*6r+H&zS!$BXZ`-8l{o%K)}N}^pTNIYzW!ta&o1D9D%>v>9iWQ6X|X@_Uy#ba8!pk&zvkYk Z{!|rUfkzi8C_&&a2@4A9EdYUn`ae`fJ)Zyo literal 0 HcmV?d00001 From dbec216b258fabf48d2927f2f3b4f72a13fb8b64 Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:16:55 +0800 Subject: [PATCH 016/124] Update .travis.yml --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8d5bb25..2fc39ada 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ jdk: - openjdk8 before_install: - - cd ./source + - cd ./tools + - mvn install:install-file -Dfile=core-0.1.4.jar -DgroupId=com.yahoo.ycsb -DartifactId=core -Dversion=0.1.4 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true + - cd ../source script: - mvn clean package -Dmaven.test.skip=true From cd8c3812c4f17fbc815fb1e488a44ba6c45d9bdc Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:28:30 +0800 Subject: [PATCH 017/124] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b98db84..b2a01e2e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/) +[![Build Status](https://travis-ci.com/blockchain-jd-com/jdchain.svg?branch=release%2F1.0.1)](https://travis-ci.org/blockchain-jd-com/jdchain) ------------------------------------------------------------------------ @@ -623,4 +625,4 @@ JD区块链的核心对象包括: } -``` \ No newline at end of file +``` From 04d9c81f6f78f1dc58d72d560a9b3f9828c4c52b Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Thu, 18 Jul 2019 10:45:01 +0800 Subject: [PATCH 018/124] add ci --- .travis.yml | 11 +++++++++++ README.md | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..18634199 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: java +jdk: + - openjdk8 + +before_install: + - cd ./tools + - mvn install:install-file -Dfile=core-0.1.4.jar -DgroupId=com.yahoo.ycsb -DartifactId=core -Dversion=0.1.4 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true + - cd ../source + +script: + - mvn clean package \ No newline at end of file diff --git a/README.md b/README.md index 6b98db84..50bb30f4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [TOC] #JD区块链 - [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) - +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/) +[![Build Status](https://travis-ci.com/blockchain-jd-com/jdchain.svg?branch=master)](https://travis-ci.org/blockchain-jd-com/jdchain) ------------------------------------------------------------------------ ### 版本修订历史 From da2f86123a13ad93d0a7893bbfcef6c4be97c541 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Thu, 18 Jul 2019 10:47:04 +0800 Subject: [PATCH 019/124] add ci --- .travis.yml | 11 +++++++++++ README.md | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..18634199 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: java +jdk: + - openjdk8 + +before_install: + - cd ./tools + - mvn install:install-file -Dfile=core-0.1.4.jar -DgroupId=com.yahoo.ycsb -DartifactId=core -Dversion=0.1.4 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true + - cd ../source + +script: + - mvn clean package \ No newline at end of file diff --git a/README.md b/README.md index 6b98db84..50bb30f4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [TOC] #JD区块链 - [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) - +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/) +[![Build Status](https://travis-ci.com/blockchain-jd-com/jdchain.svg?branch=master)](https://travis-ci.org/blockchain-jd-com/jdchain) ------------------------------------------------------------------------ ### 版本修订历史 From 08af766dc1705fa5f07793c440202545f64a0feb Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:48:39 +0800 Subject: [PATCH 020/124] add core-0.1.4.jar --- tools/core-0.1.4.jar | Bin 0 -> 67196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/core-0.1.4.jar diff --git a/tools/core-0.1.4.jar b/tools/core-0.1.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..85ea3a4a69aaa5b600d609edc4004765aad4510c GIT binary patch literal 67196 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 Z{!|rUfkzi8C_&&a2@4A9EdYUn`ae`fJ)Zyo literal 0 HcmV?d00001 From 7acbe0f994b760e7cc30152276fafc144ea00aa2 Mon Sep 17 00:00:00 2001 From: jdchain <48241540+jdchain@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:49:19 +0800 Subject: [PATCH 021/124] add core-0.1.4.jar --- tools/core-0.1.4.jar | Bin 0 -> 67196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/core-0.1.4.jar diff --git a/tools/core-0.1.4.jar b/tools/core-0.1.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..85ea3a4a69aaa5b600d609edc4004765aad4510c GIT binary patch literal 67196 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 Z{!|rUfkzi8C_&&a2@4A9EdYUn`ae`fJ)Zyo literal 0 HcmV?d00001 From 7752a533f17c2294fa868b7ab66c35f9e2f3cf09 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Thu, 18 Jul 2019 11:03:36 +0800 Subject: [PATCH 022/124] =?UTF-8?q?=E5=88=A0=E9=99=A4TimeFormat=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../initializer/LedgerInitPropertiesTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java index 703b86b4..431980e8 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java @@ -21,8 +21,9 @@ import com.jd.blockchain.utils.codec.HexUtils; public class LedgerInitPropertiesTest { private static String expectedCreatedTimeStr = "2019-08-01 14:26:58.069+0800"; - - @Test + + // 此测试在非东八区服务器环境下测试会存在问题,但属于正常,因此不再测试 +// @Test public void testTimeFormat() throws ParseException { SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); Date time = timeFormat.parse(expectedCreatedTimeStr); @@ -42,12 +43,12 @@ public class LedgerInitPropertiesTest { String actualLedgerSeed = HexUtils.encode(initProps.getLedgerSeed()); assertEquals(expectedLedgerSeed, actualLedgerSeed); - SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); - long expectedTs = timeFormat.parse(expectedCreatedTimeStr).getTime(); - assertEquals(expectedTs, initProps.getCreatedTime()); - - String createdTimeStr = timeFormat.format(new Date(initProps.getCreatedTime())); - assertEquals(expectedCreatedTimeStr, createdTimeStr); +// SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); +// long expectedTs = timeFormat.parse(expectedCreatedTimeStr).getTime(); +// assertEquals(expectedTs, initProps.getCreatedTime()); +// +// String createdTimeStr = timeFormat.format(new Date(initProps.getCreatedTime())); +// assertEquals(expectedCreatedTimeStr, createdTimeStr); assertEquals("com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider", initProps.getConsensusProvider()); From 2916c05b57b600f208fe6773640a17eb2e6cee2a Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Thu, 18 Jul 2019 11:14:01 +0800 Subject: [PATCH 023/124] =?UTF-8?q?=E5=88=A0=E9=99=A4TimeFormat=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../initializer/LedgerInitPropertiesTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java index 703b86b4..431980e8 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java @@ -21,8 +21,9 @@ import com.jd.blockchain.utils.codec.HexUtils; public class LedgerInitPropertiesTest { private static String expectedCreatedTimeStr = "2019-08-01 14:26:58.069+0800"; - - @Test + + // 此测试在非东八区服务器环境下测试会存在问题,但属于正常,因此不再测试 +// @Test public void testTimeFormat() throws ParseException { SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); Date time = timeFormat.parse(expectedCreatedTimeStr); @@ -42,12 +43,12 @@ public class LedgerInitPropertiesTest { String actualLedgerSeed = HexUtils.encode(initProps.getLedgerSeed()); assertEquals(expectedLedgerSeed, actualLedgerSeed); - SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); - long expectedTs = timeFormat.parse(expectedCreatedTimeStr).getTime(); - assertEquals(expectedTs, initProps.getCreatedTime()); - - String createdTimeStr = timeFormat.format(new Date(initProps.getCreatedTime())); - assertEquals(expectedCreatedTimeStr, createdTimeStr); +// SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); +// long expectedTs = timeFormat.parse(expectedCreatedTimeStr).getTime(); +// assertEquals(expectedTs, initProps.getCreatedTime()); +// +// String createdTimeStr = timeFormat.format(new Date(initProps.getCreatedTime())); +// assertEquals(expectedCreatedTimeStr, createdTimeStr); assertEquals("com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider", initProps.getConsensusProvider()); From 10a977ce9dfb6fff2f0033a153438fe9f3fe1b7b Mon Sep 17 00:00:00 2001 From: Zhao Guangwei Date: Tue, 30 Jul 2019 17:20:28 +0800 Subject: [PATCH 024/124] Update pom.xml remove the log4j2 in scope test; --- source/pom.xml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/source/pom.xml b/source/pom.xml index eaaad8c5..e87943f0 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -92,11 +92,6 @@ mockito-core test - - org.springframework.boot - spring-boot-starter-log4j2 - test - @@ -513,4 +508,4 @@ - \ No newline at end of file + From 266f8e81adcc7a89b3a4871f1ae41ed576cf6ba5 Mon Sep 17 00:00:00 2001 From: Zhao Guangwei Date: Tue, 30 Jul 2019 17:21:48 +0800 Subject: [PATCH 025/124] Update pom.xml add the log4j2 in scope test; --- source/utils/utils-http/pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/utils/utils-http/pom.xml b/source/utils/utils-http/pom.xml index 53053a54..e62e9564 100644 --- a/source/utils/utils-http/pom.xml +++ b/source/utils/utils-http/pom.xml @@ -43,6 +43,11 @@ org.springframework spring-beans + + org.springframework.boot + spring-boot-starter-log4j2 + test + - \ No newline at end of file + From 0266ba1fbc1efe83f67538715ab8206c9cec5e69 Mon Sep 17 00:00:00 2001 From: zhaoguangwei Date: Tue, 30 Jul 2019 17:25:31 +0800 Subject: [PATCH 026/124] =?UTF-8?q?starter-log4j2=E8=B0=83=E6=95=B4?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/pom.xml | 5 -- source/utils/utils-http/pom.xml | 146 +++++++++++++++++--------------- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/source/pom.xml b/source/pom.xml index 1633c32e..8bf5c664 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -92,11 +92,6 @@ mockito-core test - - org.springframework.boot - spring-boot-starter-log4j2 - test - diff --git a/source/utils/utils-http/pom.xml b/source/utils/utils-http/pom.xml index cf3a6ed7..91292ad9 100644 --- a/source/utils/utils-http/pom.xml +++ b/source/utils/utils-http/pom.xml @@ -1,71 +1,77 @@ - - 4.0.0 - - com.jd.blockchain - utils - 1.0.1.RELEASE - - - - 0.5.0-SNAPSHOT - 0.5.0-SNAPSHOT - - - utils-http - - - com.jd.blockchain - utils-common - ${project.version} - - - com.jd.blockchain - utils-serialize - ${project.version} - - - com.jd.blockchain - utils-web-server - test - ${project.version} - - - - org.apache.httpcomponents - httpclient - - - org.springframework - spring-core - - - org.springframework - spring-beans - - - - - - + + 4.0.0 + + com.jd.blockchain + utils + 1.0.1.RELEASE + + + + 0.5.0-SNAPSHOT + 0.5.0-SNAPSHOT + + + utils-http + + + com.jd.blockchain + utils-common + ${project.version} + + + com.jd.blockchain + utils-serialize + ${project.version} + + + com.jd.blockchain + utils-web-server + test + ${project.version} + + + + org.apache.httpcomponents + httpclient + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + + org.springframework.boot + spring-boot-starter-log4j2 + test + + + + + + \ No newline at end of file From 8bfadfeab58a832d82aaf56d83b5c11cb9b79e8f Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 30 Jul 2019 20:54:59 +0800 Subject: [PATCH 027/124] Improved examples of contracts; --- .../contract/samples/AssetContract.java | 20 ++--- .../contract/samples/AssetContractImpl.java | 86 +++++++++---------- .../sdk/samples/SDKDemo_Contract.java | 21 ++--- 3 files changed, 60 insertions(+), 67 deletions(-) diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContract.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContract.java index a6b91415..065e86d2 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContract.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContract.java @@ -4,7 +4,7 @@ import com.jd.blockchain.contract.Contract; import com.jd.blockchain.contract.ContractEvent; /** - * 示例:一个“资产管理”智能合约; + * 示例:一个“资产管理”智能合约; * * @author huanghaiquan * @@ -15,10 +15,8 @@ public interface AssetContract { /** * 发行资产; * - * @param amount - * 新发行的资产数量; - * @param assetHolderAddress - * 新发行的资产的持有账户; + * @param amount 新发行的资产数量; + * @param assetHolderAddress 新发行的资产的持有账户; */ @ContractEvent(name = "issue-asset") void issue(long amount, String assetHolderAddress); @@ -26,14 +24,12 @@ public interface AssetContract { /** * 转移资产 * - * @param fromAddress - * 转出账户; - * @param toAddress - * 转入账户; - * @param amount - * 转移的资产数额; + * @param fromAddress 转出账户; + * @param toAddress 转入账户; + * @param amount 转移的资产数额; + * @return 返回转出账户的余额; */ @ContractEvent(name = "transfer-asset") - void transfer(String fromAddress, String toAddress, long amount); + long transfer(String fromAddress, String toAddress, long amount); } \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java index 02258ed2..803d9d5c 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java @@ -11,6 +11,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.KVDataObject; +import com.jd.blockchain.utils.Bytes; /** * 示例:一个“资产管理”智能合约的实现; @@ -48,59 +49,54 @@ public class AssetContractImpl implements EventProcessingAware, AssetContract { // 查询当前值; KVDataEntry[] kvEntries = eventContext.getLedger().getDataEntries(currentLedgerHash(), ASSET_ADDRESS, KEY_TOTAL, assetHolderAddress); - + // 计算资产的发行总数; KVDataObject currTotal = (KVDataObject) kvEntries[0]; long newTotal = currTotal.longValue() + amount; - eventContext.getLedger().dataAccount(ASSET_ADDRESS).setInt64(KEY_TOTAL, newTotal, - currTotal.getVersion()); + eventContext.getLedger().dataAccount(ASSET_ADDRESS).setInt64(KEY_TOTAL, newTotal, currTotal.getVersion()); // 分配到持有者账户; KVDataObject holderAmount = (KVDataObject) kvEntries[1]; long newHodlerAmount = holderAmount.longValue() + amount; - eventContext.getLedger().dataAccount(ASSET_ADDRESS).setInt64(assetHolderAddress, newHodlerAmount, - holderAmount.getVersion()).setText("K2", "info2", -1).setText("k3", "info3", 3); - + eventContext.getLedger().dataAccount(ASSET_ADDRESS) + .setInt64(assetHolderAddress, newHodlerAmount, holderAmount.getVersion()).setText("K2", "info2", -1) + .setText("k3", "info3", 3); + } @Override - public void transfer(String fromAddress, String toAddress, long amount) { - // if (amount < 0) { - // throw new ContractError("The amount is negative!"); - // } - // if (amount == 0) { - // return; - // } - // - // //校验“转出账户”是否已签名; - // checkSignerPermission(fromAddress); - // - // // 查询现有的余额; - // Set keys = new HashSet<>(); - // keys.add(fromAddress); - // keys.add(toAddress); - // StateMap origBalances = - // eventContext.getLedger().getStates(currentLedgerHash(), ASSET_ADDRESS, keys); - // KVDataObject fromBalance = origBalances.get(fromAddress); - // KVDataObject toBalance = origBalances.get(toAddress); - // - // //检查是否余额不足; - // if ((fromBalance.longValue() - amount) < 0) { - // throw new ContractError("Insufficient balance!"); - // } - // - // // 把数据的更改写入到账本; - // SimpleStateMap newBalances = new SimpleStateMap(origBalances.getAccount(), - // origBalances.getAccountVersion(), - // origBalances.getStateVersion()); - // KVDataObject newFromBalance = fromBalance.newLong(fromBalance.longValue() - - // amount); - // KVDataObject newToBalance = toBalance.newLong(toBalance.longValue() + - // amount); - // newBalances.setValue(newFromBalance); - // newBalances.setValue(newToBalance); - // - // eventContext.getLedger().updateState(ASSET_ADDRESS).setStates(newBalances); + public long transfer(String fromAddress, String toAddress, long amount) { + if (amount < 0) { + throw new ContractException("The amount is negative!"); + } + if (amount > 20000) { + throw new ContractException("The amount exceeds the limit of 20000!"); + } + + // 校验“转出账户”是否已签名; + checkSignerPermission(fromAddress); + + // 查询现有的余额; + KVDataEntry[] origBalances = eventContext.getLedger().getDataEntries(currentLedgerHash(), ASSET_ADDRESS, + fromAddress, toAddress); + KVDataEntry fromBalanceKV = origBalances[0]; + KVDataEntry toBalanceKV = origBalances[1]; + long fromBalance = fromBalanceKV.getVersion() == -1 ? 0 : (long) fromBalanceKV.getValue(); + long toBalance = toBalanceKV.getVersion() == -1 ? 0 : (long) toBalanceKV.getValue(); + + // 检查是否余额不足; + + if ((fromBalance - amount) < 0) { + throw new ContractException("The balance is insufficient and the transfer failed!"); + } + fromBalance = fromBalance + amount; + toBalance = toBalance + amount; + + // 把数据的更改写入到账本; + eventContext.getLedger().dataAccount(fromAddress).setInt64(ASSET_ADDRESS, fromBalance, fromBalanceKV.getVersion()); + eventContext.getLedger().dataAccount(toAddress).setInt64(ASSET_ADDRESS, toBalance, toBalanceKV.getVersion()); + + return -1; } // ------------------------------------------------------------- @@ -117,9 +113,9 @@ public class AssetContractImpl implements EventProcessingAware, AssetContract { throw new ContractException("Permission Error! -- The requestors is not exactlly being owners!"); } - Map ownerMap = new HashMap<>(); + Map ownerMap = new HashMap<>(); for (BlockchainIdentity o : owners) { - ownerMap.put(o.getAddress().toBase58(), o); + ownerMap.put(o.getAddress(), o); } for (BlockchainIdentity r : requestors) { if (!ownerMap.containsKey(r.getAddress())) { diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java index 3059aaac..06ec1801 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Contract.java @@ -11,6 +11,8 @@ import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.transaction.ContractReturnValue; +import com.jd.blockchain.transaction.LongValueHolder; import com.jd.blockchain.utils.io.ByteArray; import com.jd.blockchain.utils.net.NetworkAddress; import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; @@ -49,8 +51,6 @@ public class SDKDemo_Contract { BlockchainService service = serviceFactory.getBlockchainService(); HashDigest ledgerHash = getLedgerHash(); - // 发起交易; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); // -------------------------------------- // 一个贸易账户,贸易结算后的利润将通过一个合约账户来执行利润分配; @@ -71,25 +71,26 @@ public class SDKDemo_Contract { // 备注信息; Remark remark = new Remark(); String remarkJSON = JSONSerializeUtils.serializeToJSON(remark); - + + // 发起交易; + TransactionTemplate txTemp = service.newTransaction(ledgerHash); + AssetContract assetContract = txTemp.contract(profitDistributionContract, AssetContract.class); assetContract.issue(1000, receiptorAccount1); - assetContract.transfer(receiptorAccount1, receiptorAccount2, 600); - -// assetContract. - - // -------------------------------------- + LongValueHolder balance = ContractReturnValue.decode(assetContract.transfer(receiptorAccount1, receiptorAccount2, 600)); // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); - String txHash = ByteArray.toBase64(prepTx.getHash().toBytes()); // 使用私钥进行签名; - AsymmetricKeypair keyPair = getSponsorKey(); + AsymmetricKeypair keyPair = getSponsorKey();//示例方法,取发起人的私钥; prepTx.sign(keyPair); // 提交交易; prepTx.commit(); + + //获取返回值; + System.out.println("balance = " + balance.get()); } private static HashDigest getLedgerHash() { From 009f86dab3342d515a708836753ea499d6ea6b31 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 7 Aug 2019 14:29:02 +0800 Subject: [PATCH 028/124] Completed the definition of role privilege, and dealed some naming conflicts; --- .../com/jd/blockchain/consts/DataCodes.java | 13 ++- .../conf/application-gw.properties | 0 .../gateway/boot/GatewayBooter.java | 10 ++- .../ledger/core/AbstractPrivilege.java | 63 ++++++++++++++ .../blockchain/ledger/core/Authorization.java | 2 - .../jd/blockchain/ledger/core/Consensus.java | 21 ----- .../ledger/core/LedgerAdminPrivilege.java | 5 -- ...ermission.java => LedgerInitProposal.java} | 4 +- ...nData.java => LedgerInitProposalData.java} | 6 +- .../ledger/core/LedgerPermission.java | 87 ++++++++++++++++++- .../ledger/core/LedgerPrivilege.java | 20 +++++ .../ledger/core/LedgerSecurityManager.java | 2 +- .../jd/blockchain/ledger/core/P2PRealm.java | 21 ----- .../jd/blockchain/ledger/core/Privilege.java | 51 +---------- .../com/jd/blockchain/ledger/core/Role.java | 4 +- .../blockchain/ledger/core/RolePrivilege.java | 68 +++++++++++++++ .../blockchain/ledger/core/TxPermission.java | 34 ++++++++ .../blockchain/ledger/core/TxPrivilege.java | 14 +++ .../intgr/consensus/ConsensusTest.java | 6 +- .../intgr/perf/GlobalPerformanceTest.java | 6 +- .../intgr/perf/LedgerInitializeTest.java | 8 +- .../intgr/perf/LedgerInitializeWebTest.java | 20 ++--- .../com/jd/blockchain/intgr/perf/Utils.java | 8 +- .../initializer/LedgerInitializeTest.java | 8 +- .../LedgerInitializeWeb4Nodes.java | 2 +- .../LedgerInitializeWeb4SingleStepsTest.java | 20 ++--- .../web/LedgerInitConsensusService.java | 4 +- .../web/LedgerInitMessageConverter.java | 8 +- .../web/LedgerInitializeWebController.java | 28 +++--- .../web/PermissionResponseConverter.java | 2 +- .../mocker/MockerLedgerInitializer.java | 12 +-- .../mocker/node/NodeWebContext.java | 4 +- 32 files changed, 377 insertions(+), 184 deletions(-) create mode 100644 source/deployment/deployment-gateway/conf/application-gw.properties create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Consensus.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminPrivilege.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{LedgerInitPermission.java => LedgerInitProposal.java} (92%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{LedgerInitPermissionData.java => LedgerInitProposalData.java} (71%) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/P2PRealm.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index e2f8d65b..4b670ba4 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -8,11 +8,11 @@ package com.jd.blockchain.consts; */ public interface DataCodes { - public static final int BYTES_VALUE = 0x80; + public static final int BYTES_VALUE = 0x080; - public static final int BYTES_VALUE_LIST = 0x81; + public static final int BYTES_VALUE_LIST = 0x081; - public static final int BLOCK_CHAIN_IDENTITY = 0x90; + public static final int BLOCK_CHAIN_IDENTITY = 0x090; public static final int BLOCK = 0x100; @@ -52,12 +52,17 @@ public interface DataCodes { public static final int TX_RESPONSE = 0x350; public static final int TX_OP_RESULT = 0x360; + + // enum types of permissions; + public static final int ENUM_TX_PERMISSIONS = 0x401; + public static final int ENUM_LEDGER_PERMISSIONS = 0x402; + // contract types of metadata; public static final int METADATA = 0x600; public static final int METADATA_INIT_SETTING = 0x610; - public static final int METADATA_INIT_PERMISSION = 0x611; + public static final int METADATA_INIT_PROPOSAL = 0x611; public static final int METADATA_INIT_DECISION = 0x612; diff --git a/source/deployment/deployment-gateway/conf/application-gw.properties b/source/deployment/deployment-gateway/conf/application-gw.properties new file mode 100644 index 00000000..e69de29b diff --git a/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java b/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java index 93ca75ff..f6c637ad 100644 --- a/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java +++ b/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java @@ -18,15 +18,13 @@ public class GatewayBooter { writePID(); GatewayServerBooter.main(args); } catch (Exception e) { + e.printStackTrace(); System.err.println("Error!!! --[" + e.getClass().getName() + "] " + e.getMessage()); } } private static final void writePID() throws Exception { - URL url = GatewayBooter.class - .getProtectionDomain() - .getCodeSource() - .getLocation(); + URL url = GatewayBooter.class.getProtectionDomain().getCodeSource().getLocation(); String currPath = java.net.URLDecoder.decode(url.getPath(), "UTF-8"); if (currPath.contains("!/")) { currPath = currPath.substring(5, currPath.indexOf("!/")); @@ -40,6 +38,10 @@ public class GatewayBooter { String pidFilePath = homeDir + File.separator + "bin" + File.separator + "PID.log"; File pidFile = new File(pidFilePath); if (!pidFile.exists()) { + File dir = pidFile.getParentFile(); + if (!dir.exists()) { + dir.mkdirs(); + } pidFile.createNewFile(); } String name = ManagementFactory.getRuntimeMXBean().getName(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java new file mode 100644 index 00000000..f4925a3d --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java @@ -0,0 +1,63 @@ +package com.jd.blockchain.ledger.core; + +import java.util.BitSet; + +import com.jd.blockchain.utils.io.BytesSerializable; + +/** + * LedgerPrivilege 账本特权是授权给特定角色的权限代码序列; + * + * @author huanghaiquan + * + */ +public abstract class AbstractPrivilege> implements Privilege, BytesSerializable { + + private BitSet permissions; + + public AbstractPrivilege(byte[] codeBytes) { + permissions = BitSet.valueOf(codeBytes); + } + + public boolean isEnable(E permission) { + return permissions.get(getCodeIndex(permission)); + } + + public void enable(E permission) { + permissions.set(getCodeIndex(permission)); + } + + public void disable(E permission) { + permissions.clear(getCodeIndex(permission)); + } + +// private int getCodeIndex(E permission) { +// return permission.CODE & 0xFF; +// } + + protected abstract int getCodeIndex(E permission); + + @Override + public byte[] toBytes() { + return permissions.toByteArray(); + } + +// public boolean[] getPermissionStates() { +// LedgerPermission[] PMs = LedgerPermission.values(); +// +// LedgerPermission maxPermission = Arrays.stream(PMs).max(new Comparator() { +// @Override +// public int compare(LedgerPermission o1, LedgerPermission o2) { +// return getCodeIndex(o1) - getCodeIndex(o2); +// } +// }).get(); +// +// boolean[] states = new boolean[getCodeIndex(maxPermission) + 1]; +// int idx = -1; +// for (LedgerPermission pm : PMs) { +// idx = getCodeIndex(pm); +// states[idx] = permissions.get(idx); +// } +// +// return states; +// } +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java index 18f1bf70..44c86d57 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java @@ -1,7 +1,5 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.ledger.DigitalSignature; - /** * {@link Authorization} 抽象了对特定用户/角色的授权信息; * diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Consensus.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Consensus.java deleted file mode 100644 index da7b6104..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Consensus.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jd.blockchain.ledger.core; - - -/** - * @author hhq - * @version 1.0 - * @created 14-6��-2018 12:13:32 - */ -public class Consensus { - - public P2PRealm m_P2PRealm; - - public Consensus(){ - - } - - public void finalize() throws Throwable { - - } - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminPrivilege.java deleted file mode 100644 index b436b5da..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminPrivilege.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.jd.blockchain.ledger.core; - -public enum LedgerAdminPrivilege { - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposal.java similarity index 92% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposal.java index 7ca9e15c..2baaacbd 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermission.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposal.java @@ -13,8 +13,8 @@ import com.jd.blockchain.ledger.LedgerInitOperation; * @author huanghaiquan * */ -@DataContract(code = DataCodes.METADATA_INIT_PERMISSION) -public interface LedgerInitPermission { +@DataContract(code = DataCodes.METADATA_INIT_PROPOSAL) +public interface LedgerInitProposal { /** * 做出许可的参与方 ID; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermissionData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java similarity index 71% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermissionData.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java index ead25a56..6b6c79aa 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitPermissionData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java @@ -2,7 +2,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.SignatureDigest; -public class LedgerInitPermissionData implements LedgerInitPermission { +public class LedgerInitProposalData implements LedgerInitProposal { private int participantId; @@ -11,10 +11,10 @@ public class LedgerInitPermissionData implements LedgerInitPermission { /** * a private contructor for deserialize; */ - private LedgerInitPermissionData() { + private LedgerInitProposalData() { } - public LedgerInitPermissionData(int participantId, SignatureDigest initTxSignature) { + public LedgerInitProposalData(int participantId, SignatureDigest initTxSignature) { this.participantId = participantId; this.transactionSignature = initTxSignature; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java index 99f6b907..e5b6b751 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java @@ -1,9 +1,94 @@ package com.jd.blockchain.ledger.core; +import com.jd.blockchain.binaryproto.EnumContract; +import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +/** + * 账本相关的权限,这些权限属于全局性的; + * + * @author huanghaiquan + * + */ +@EnumContract(code = DataCodes.ENUM_LEDGER_PERMISSIONS) public enum LedgerPermission { - SET_ROLE((byte) 0); + /** + * 设置角色权限;
        + * 包括:创建角色、设置角色的权限代码、分配用户角色; + */ + SET_ROLE_PERMISSION((byte) 0x01), + + /** + * 设置共识协议;
        + */ + SET_CONSENSUS((byte) 0x02), + + /** + * 设置密码体系;
        + */ + SET_CRYPTO((byte) 0x03), + + /** + * 注册参与方;
        + */ + REGISTER_PARTICIPANT((byte) 0x04), + + /** + * 设置参与方的权限;
        + * + * 如果不具备此项权限,则无法设置参与方的“提交交易”、“参与共识”的权限; + */ + SET_PARTICIPANT_PERMISSION((byte) 0x05), + + /** + * 参与方核准交易;
        + * + * 如果不具备此项权限,则无法作为网关节点接入并签署由终端提交的交易; + */ + APPROVE_TX((byte) 0x06), + + /** + * 参与方共识交易;
        + * + * 如果不具备此项权限,则无法作为共识节点接入并对交易进行共识; + */ + CONSENSUS_TX((byte) 0x07), + + /** + * 注册用户;
        + * + * 如果不具备此项权限,则无法注册用户; + */ + REGISTER_USER((byte) 0x08), + + /** + * 设置用户属性;
        + */ + SET_USER_ATTRIBUTES((byte) 0x09), + + /** + * 注册数据账户;
        + */ + REGISTER_DATA_ACCOUNT((byte) 0x0A), + + /** + * 写入数据账户;
        + */ + WRITE_DATA_ACCOUNT((byte) 0x0B), + + /** + * 注册合约;
        + */ + REGISTER_CONTRACT((byte) 0x0C), + + /** + * 升级合约 + */ + UPGRADE_CONTRACT((byte) 0x0D); + @EnumField(type = PrimitiveType.INT8) public final byte CODE; private LedgerPermission(byte code) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java new file mode 100644 index 00000000..f4efbecb --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java @@ -0,0 +1,20 @@ +package com.jd.blockchain.ledger.core; + +/** + * LedgerPrivilege 账本特权是授权给特定角色的权限代码序列; + * + * @author huanghaiquan + * + */ +public class LedgerPrivilege extends AbstractPrivilege { + + public LedgerPrivilege(byte[] codeBytes) { + super(codeBytes); + } + + @Override + protected int getCodeIndex(LedgerPermission permission) { + return permission.CODE & 0xFF; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java index 11c99c9e..a44dc575 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -22,7 +22,7 @@ public class LedgerSecurityManager { throw new IllegalStateException("Not implemented!"); } - public Role setRole(String role, Privilege privilege) { + public Role setRole(String role, LedgerPrivilege privilege) { throw new IllegalStateException("Not implemented!"); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/P2PRealm.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/P2PRealm.java deleted file mode 100644 index c7480e07..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/P2PRealm.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jd.blockchain.ledger.core; - - -/** - * @author hhq - * @version 1.0 - * @created 14-6��-2018 12:13:33 - */ -public class P2PRealm { - - public Peer m_Peer; - - public P2PRealm(){ - - } - - public void finalize() throws Throwable { - - } - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java index 95443f45..25f6c9eb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java @@ -1,54 +1,5 @@ package com.jd.blockchain.ledger.core; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Comparator; +public interface Privilege> { -public class Privilege { - - private BitSet permissions; - - public Privilege(byte[] codeBytes) { - permissions = BitSet.valueOf(codeBytes); - } - - public boolean isEnable(LedgerPermission permission) { - return permissions.get(getCodeIndex(permission)); - } - - public void enable(LedgerPermission permission) { - permissions.set(getCodeIndex(permission)); - } - - public void disable(LedgerPermission permission) { - permissions.clear(getCodeIndex(permission)); - } - - public static int getCodeIndex(LedgerPermission permission) { - return permission.CODE & 0xFF; - } - - public byte[] toCodeBytes() { - return permissions.toByteArray(); - } - - public boolean[] getPermissionStates() { - LedgerPermission[] PMs = LedgerPermission.values(); - - LedgerPermission maxPermission = Arrays.stream(PMs).max(new Comparator() { - @Override - public int compare(LedgerPermission o1, LedgerPermission o2) { - return getCodeIndex(o1) - getCodeIndex(o2); - } - }).get(); - - boolean[] states = new boolean[getCodeIndex(maxPermission) + 1]; - int idx = -1; - for (LedgerPermission pm : PMs) { - idx = getCodeIndex(pm); - states[idx] = permissions.get(idx); - } - - return states; - } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java index 23149745..7ece80cf 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java @@ -6,7 +6,7 @@ public class Role { private long version; - private Privilege privilege; + private LedgerPrivilege privilege; @@ -18,7 +18,7 @@ public class Role { return version; } - public Privilege getPrivilege() { + public LedgerPrivilege getPrivilege() { return privilege; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java new file mode 100644 index 00000000..19949af5 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java @@ -0,0 +1,68 @@ +package com.jd.blockchain.ledger.core; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import com.jd.blockchain.utils.io.BytesEncoding; +import com.jd.blockchain.utils.io.BytesSerializable; + +/** + * 表示赋予角色的特权码; + * + * @author huanghaiquan + * + */ +public class RolePrivilege implements BytesSerializable { + + // 权限码的数量;目前有2种:账本权限 + 交易权限; + private static final int SEGMENT_COUNT = 2; + + private LedgerPrivilege ledgerPrivilege; + + private TxPrivilege txPrivilege; + + public Privilege getTxPrivilege() { + return txPrivilege; + } + + public Privilege getLedgerPrivilege() { + return ledgerPrivilege; + } + + public RolePrivilege(byte[] priviledgeCodes) { + byte[][] bytesSegments = decodeBytes(priviledgeCodes); + ledgerPrivilege = new LedgerPrivilege(bytesSegments[0]); + txPrivilege = new TxPrivilege(bytesSegments[1]); + } + + private byte[] encodeBytes(byte[]... bytes) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // write one byte; + out.write(bytes.length); + for (int i = 0; i < bytes.length; i++) { + BytesEncoding.writeInTiny(bytes[i], out); + } + return out.toByteArray(); + } + + private byte[][] decodeBytes(byte[] bytes) { + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + // read one byte; + int len = in.read(); + if (len < 1 || len > SEGMENT_COUNT) { + throw new IllegalStateException("Decoded illegal privilege bytes!"); + } + byte[][] bytesSegments = new byte[len][]; + for (int i = 0; i < bytesSegments.length; i++) { + bytesSegments[i] = BytesEncoding.readInTiny(in); + } + return bytesSegments; + } + + @Override + public byte[] toBytes() { + // 保持和解码时一致的顺序; + return encodeBytes(ledgerPrivilege.toBytes(), txPrivilege.toBytes()); + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java new file mode 100644 index 00000000..b73ceaa3 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java @@ -0,0 +1,34 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.EnumContract; +import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +/** + * TxPermission 交易权限表示一个用户可以发起的交易类型; + * + * @author huanghaiquan + * + */ +@EnumContract(code = DataCodes.ENUM_TX_PERMISSIONS) +public enum TxPermission { + + /** + * 交易中包含指令操作; + */ + COMMAND((byte) 0x01), + + /** + * 交易中包含合约操作; + */ + CONTRACT((byte) 0x02); + + @EnumField(type = PrimitiveType.INT8) + public final byte CODE; + + private TxPermission(byte code) { + this.CODE = code; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java new file mode 100644 index 00000000..30e45a18 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java @@ -0,0 +1,14 @@ +package com.jd.blockchain.ledger.core; + +public class TxPrivilege extends AbstractPrivilege { + + public TxPrivilege(byte[] codeBytes) { + super(codeBytes); + } + + @Override + protected int getCodeIndex(TxPermission permission) { + return permission.CODE & 0xFF; + } + +} diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java index 9eb45aee..d5532f63 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java @@ -26,7 +26,7 @@ import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; @@ -326,7 +326,7 @@ public class ConsensusTest { return controller.getInitTxContent(); } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return controller.getLocalPermission(); } @@ -398,7 +398,7 @@ public class ConsensusTest { return invoker.start(); } - public LedgerInitPermission preparePermision(PrivKey privKey, LedgerInitProperties setting, + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, ConsensusSettings csProps) { return controller.prepareLocalPermission(id, privKey, setting, csProps); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java index f4c21fe7..b2f1c135 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java @@ -28,7 +28,7 @@ import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; @@ -326,7 +326,7 @@ public class GlobalPerformanceTest { return controller.getInitTxContent(); } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return controller.getLocalPermission(); } @@ -400,7 +400,7 @@ public class GlobalPerformanceTest { return invoker.start(); } - public LedgerInitPermission preparePermision(PrivKey privKey, LedgerInitProperties setting, + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, ConsensusSettings csProps) { return controller.prepareLocalPermission(id, privKey, setting, csProps); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index dbd8b21b..f4b24b36 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -21,7 +21,7 @@ import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; @@ -251,10 +251,10 @@ public class LedgerInitializeTest { } @Override - public LedgerInitPermission requestPermission(int requesterId, SignatureDigest signature) { - ThreadInvoker invoker = new ThreadInvoker() { + public LedgerInitProposal requestPermission(int requesterId, SignatureDigest signature) { + ThreadInvoker invoker = new ThreadInvoker() { @Override - protected LedgerInitPermission invoke() { + protected LedgerInitProposal invoke() { return initCsService.requestPermission(requesterId, signature); } }; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index 569472d9..c6ca3fdc 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -26,7 +26,7 @@ import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; @@ -112,10 +112,10 @@ public class LedgerInitializeWebTest { PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); // 测试生成“账本初始化许可”; - LedgerInitPermission permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); - LedgerInitPermission permission1 = testPreparePermisssion(node1, privkey1, initSetting, csProps); - LedgerInitPermission permission2 = testPreparePermisssion(node2, privkey2, initSetting, csProps); - LedgerInitPermission permission3 = testPreparePermisssion(node3, privkey3, initSetting, csProps); + LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); + LedgerInitProposal permission1 = testPreparePermisssion(node1, privkey1, initSetting, csProps); + LedgerInitProposal permission2 = testPreparePermisssion(node2, privkey2, initSetting, csProps); + LedgerInitProposal permission3 = testPreparePermisssion(node3, privkey3, initSetting, csProps); TransactionContent initTxContent0 = node0.getInitTxContent(); TransactionContent initTxContent1 = node1.getInitTxContent(); @@ -205,9 +205,9 @@ public class LedgerInitializeWebTest { testRequestDecision(node3, node2, initCsService2); } - private LedgerInitPermission testPreparePermisssion(NodeWebContext node, PrivKey privKey, + private LedgerInitProposal testPreparePermisssion(NodeWebContext node, PrivKey privKey, LedgerInitProperties setting, ConsensusSettings csProps) { - LedgerInitPermission permission = node.preparePermision(privKey, setting, csProps); + LedgerInitProposal permission = node.preparePermision(privKey, setting, csProps); return permission; } @@ -215,7 +215,7 @@ public class LedgerInitializeWebTest { private void testRequestPermission(NodeWebContext fromNode, PrivKey fromPrivkey, NodeWebContext targetNode, LedgerInitConsensusService targetNodeService) { SignatureDigest reqSignature = fromNode.createPermissionRequestSignature(fromNode.getId(), fromPrivkey); - LedgerInitPermission targetPermission = targetNodeService.requestPermission(fromNode.getId(), reqSignature); + LedgerInitProposal targetPermission = targetNodeService.requestPermission(fromNode.getId(), reqSignature); } private void testRequestDecision(NodeWebContext fromNode, NodeWebContext targetNode, @@ -374,7 +374,7 @@ public class LedgerInitializeWebTest { return controller.getInitTxContent(); } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return controller.getLocalPermission(); } @@ -457,7 +457,7 @@ public class LedgerInitializeWebTest { return invoker.start(); } - public LedgerInitPermission preparePermision(PrivKey privKey, LedgerInitProperties setting, + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, ConsensusSettings csProps) { return controller.prepareLocalPermission(id, privKey, setting, csProps); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index 80b133ad..591f738a 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -18,7 +18,7 @@ import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnectionFactory; @@ -202,10 +202,10 @@ public class Utils { } @Override - public LedgerInitPermission requestPermission(int requesterId, SignatureDigest signature) { - ThreadInvoker invoker = new ThreadInvoker() { + public LedgerInitProposal requestPermission(int requesterId, SignatureDigest signature) { + ThreadInvoker invoker = new ThreadInvoker() { @Override - protected LedgerInitPermission invoke() { + protected LedgerInitProposal invoke() { return initCsService.requestPermission(requesterId, signature); } }; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index 09ddf134..ffdcaa80 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; @@ -301,10 +301,10 @@ public class LedgerInitializeTest { } @Override - public LedgerInitPermission requestPermission(int requesterId, SignatureDigest signature) { - ThreadInvoker invoker = new ThreadInvoker() { + public LedgerInitProposal requestPermission(int requesterId, SignatureDigest signature) { + ThreadInvoker invoker = new ThreadInvoker() { @Override - protected LedgerInitPermission invoke() { + protected LedgerInitProposal invoke() { return initCsService.requestPermission(requesterId, signature); } }; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index a37afc62..9415e3b6 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -213,7 +213,7 @@ public class LedgerInitializeWeb4Nodes { return controller.getInitTxContent(); } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return controller.getLocalPermission(); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java index 2cf5701d..64371207 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java @@ -27,7 +27,7 @@ import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnection; @@ -116,10 +116,10 @@ public class LedgerInitializeWeb4SingleStepsTest { PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); // 测试生成“账本初始化许可”; - LedgerInitPermission permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); - LedgerInitPermission permission1 = testPreparePermisssion(node1, privkey1, initSetting, csProps); - LedgerInitPermission permission2 = testPreparePermisssion(node2, privkey2, initSetting, csProps); - LedgerInitPermission permission3 = testPreparePermisssion(node3, privkey3, initSetting, csProps); + LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); + LedgerInitProposal permission1 = testPreparePermisssion(node1, privkey1, initSetting, csProps); + LedgerInitProposal permission2 = testPreparePermisssion(node2, privkey2, initSetting, csProps); + LedgerInitProposal permission3 = testPreparePermisssion(node3, privkey3, initSetting, csProps); TransactionContent initTxContent0 = node0.getInitTxContent(); TransactionContent initTxContent1 = node1.getInitTxContent(); @@ -240,9 +240,9 @@ public class LedgerInitializeWeb4SingleStepsTest { testRequestDecision(node3, node2, initCsService2); } - private LedgerInitPermission testPreparePermisssion(NodeWebContext node, PrivKey privKey, + private LedgerInitProposal testPreparePermisssion(NodeWebContext node, PrivKey privKey, LedgerInitProperties setting, ConsensusSettings csProps) { - LedgerInitPermission permission = node.preparePermision(privKey, setting, csProps); + LedgerInitProposal permission = node.preparePermision(privKey, setting, csProps); assertEquals(node.getId(), permission.getParticipantId()); assertNotNull(permission.getTransactionSignature()); @@ -253,7 +253,7 @@ public class LedgerInitializeWeb4SingleStepsTest { private void testRequestPermission(NodeWebContext fromNode, PrivKey fromPrivkey, NodeWebContext targetNode, LedgerInitConsensusService targetNodeService) { SignatureDigest reqSignature = fromNode.createPermissionRequestSignature(fromNode.getId(), fromPrivkey); - LedgerInitPermission targetPermission = targetNodeService.requestPermission(fromNode.getId(), reqSignature); + LedgerInitProposal targetPermission = targetNodeService.requestPermission(fromNode.getId(), reqSignature); assertEquals(targetNode.getId(), targetPermission.getParticipantId()); assertEquals(targetNode.getLocalPermission().getTransactionSignature(), targetPermission.getTransactionSignature()); @@ -312,7 +312,7 @@ public class LedgerInitializeWeb4SingleStepsTest { return controller.getInitTxContent(); } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return controller.getLocalPermission(); } @@ -385,7 +385,7 @@ public class LedgerInitializeWeb4SingleStepsTest { return invoker.start(); } - public LedgerInitPermission preparePermision(PrivKey privKey, LedgerInitProperties setting, + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, ConsensusSettings csProps) { return controller.prepareLocalPermission(id, privKey, setting, csProps); } diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConsensusService.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConsensusService.java index d9699560..46ae01b5 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConsensusService.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConsensusService.java @@ -2,7 +2,7 @@ package com.jd.blockchain.tools.initializer.web; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.utils.http.HttpAction; import com.jd.blockchain.utils.http.HttpMethod; import com.jd.blockchain.utils.http.HttpService; @@ -21,7 +21,7 @@ public interface LedgerInitConsensusService { * 请求者的私钥对 “id” + “账本种子” 做出的签名;只有签名合法且参与者是初始化配置中的参与方才能获得有效返回,否则将被拒绝; */ @HttpAction(path = "/legerinit/permission/{requesterId}", method = HttpMethod.POST, contentType = LedgerInitMessageConverter.CONTENT_TYPE_VALUE, responseConverter = PermissionResponseConverter.class) - LedgerInitPermission requestPermission(@PathParam(name = "requesterId") int requesterId, + LedgerInitProposal requestPermission(@PathParam(name = "requesterId") int requesterId, @RequestBody(converter = SignatureDigestRequestBodyConverter.class) SignatureDigest signature); /** diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitMessageConverter.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitMessageConverter.java index 5658f9f6..6361d6a1 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitMessageConverter.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitMessageConverter.java @@ -17,8 +17,8 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; -import com.jd.blockchain.ledger.core.LedgerInitPermissionData; +import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerInitProposalData; import com.jd.blockchain.utils.io.BytesUtils; /** @@ -38,10 +38,10 @@ public class LedgerInitMessageConverter implements HttpMessageConverter private static final Map, Class> SUPPORTED_CONTRACT_TYPES = new HashMap<>(); static { - DataContractRegistry.register(LedgerInitPermission.class); + DataContractRegistry.register(LedgerInitProposal.class); DataContractRegistry.register(LedgerInitDecision.class); - SUPPORTED_CONTRACT_TYPES.put(LedgerInitPermission.class, LedgerInitPermissionData.class); + SUPPORTED_CONTRACT_TYPES.put(LedgerInitProposal.class, LedgerInitProposalData.class); SUPPORTED_CONTRACT_TYPES.put(LedgerInitDecision.class, LedgerInitDecisionData.class); // SUPPORTED_CONTRACT_TYPES.add(LedgerInitResponse.class); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index 116a381a..09de6518 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -46,8 +46,8 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; -import com.jd.blockchain.ledger.core.LedgerInitPermissionData; +import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerInitProposalData; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerTransactionContext; import com.jd.blockchain.storage.service.DbConnection; @@ -84,7 +84,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private final SignatureFunction SIGN_FUNC; - private volatile LedgerInitPermission localPermission; + private volatile LedgerInitProposal localPermission; private TransactionContent initTxContent; @@ -92,7 +92,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private volatile LedgerInitSetting ledgerInitSetting; - private volatile LedgerInitPermission[] permissions; + private volatile LedgerInitProposal[] permissions; private volatile NetworkAddress[] initializerAddresses; @@ -140,7 +140,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI return initTxContent; } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return localPermission; } @@ -319,13 +319,13 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI return defCryptoSetting; } - public LedgerInitPermission prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, + public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, ConsensusSettings consensusProps) { CryptoSetting defCryptoSetting = createDefaultCryptoSetting(); return prepareLocalPermission(currentId, privKey, ledgerProps, consensusProps, defCryptoSetting); } - public LedgerInitPermission prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, + public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, ConsensusSettings csSettings, CryptoSetting cryptoSetting) { // 创建初始化配置; LedgerInitSettingData initSetting = new LedgerInitSettingData(); @@ -383,10 +383,10 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI // 对初始交易签名,生成当前参与者的账本初始化许可; SignatureDigest permissionSign = SignatureUtils.sign(initTxContent, privKey); - LedgerInitPermissionData permission = new LedgerInitPermissionData(currentId, permissionSign); + LedgerInitProposalData permission = new LedgerInitProposalData(currentId, permissionSign); this.currentId = currentId; - this.permissions = new LedgerInitPermission[initSetting.getConsensusParticipants().length]; + this.permissions = new LedgerInitProposal[initSetting.getConsensusParticipants().length]; this.permissions[currentId] = permission; this.localPermission = permission; @@ -493,7 +493,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI continue; } PubKey pubKey = participants[i].getPubKey(); - LedgerInitPermission permission = (LedgerInitPermission) results[i].getValue(); + LedgerInitProposal permission = (LedgerInitProposal) results[i].getValue(); if (permission.getParticipantId() != participants[i].getId()) { prompter.error("\r\nThe id of received permission isn't equal to it's participant ! --[Id=%s][name=%s]", participants[i].getAddress(), participants[i].getName()); @@ -534,16 +534,16 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI * @param latch * @return */ - private InvocationResult doRequestPermission(int targetId, SignatureDigest reqAuthSign, + private InvocationResult doRequestPermission(int targetId, SignatureDigest reqAuthSign, CountDownLatch latch) { - InvocationResult result = new InvocationResult<>(); + InvocationResult result = new InvocationResult<>(); try { LedgerInitConsensusService initConsensus = connectToParticipant(targetId); Thread thrd = new Thread(new Runnable() { @Override public void run() { try { - LedgerInitPermission permission = initConsensus.requestPermission(currentId, reqAuthSign); + LedgerInitProposal permission = initConsensus.requestPermission(currentId, reqAuthSign); result.setValue(permission); } catch (Exception e) { result.setError(e); @@ -561,7 +561,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI @RequestMapping(path = "/legerinit/permission/{requesterId}", method = RequestMethod.POST, produces = LedgerInitMessageConverter.CONTENT_TYPE_VALUE, consumes = LedgerInitMessageConverter.CONTENT_TYPE_VALUE) @Override - public LedgerInitPermission requestPermission(@PathVariable(name = "requesterId") int requesterId, + public LedgerInitProposal requestPermission(@PathVariable(name = "requesterId") int requesterId, @RequestBody SignatureDigest signature) { if (requesterId == currentId) { throw new LedgerInitException("There is a id conflict!"); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java index ab5b0308..e07fa4d7 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java @@ -3,7 +3,7 @@ package com.jd.blockchain.tools.initializer.web; import java.io.InputStream; import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.ledger.core.LedgerInitPermissionData; +import com.jd.blockchain.ledger.core.LedgerInitProposalData; import com.jd.blockchain.tools.initializer.LedgerInitException; import com.jd.blockchain.utils.http.HttpServiceContext; import com.jd.blockchain.utils.http.ResponseConverter; diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java index 0c2361fa..962c79ae 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java @@ -45,7 +45,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon private final SignatureFunction SIGN_FUNC; - private volatile LedgerInitPermission localPermission; + private volatile LedgerInitProposal localPermission; private TransactionContent initTxContent; @@ -92,7 +92,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon return initTxContent; } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return localPermission; } @@ -129,7 +129,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon prompter.info("Init settings and sign permision..."); - prepareLocalPermission(currentId, privKey, ledgerInitProps, null, cryptoSetting); + prepareLocalProposal(currentId, privKey, ledgerInitProps, null, cryptoSetting); try { // 连接数据库; @@ -201,7 +201,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon return defCryptoSetting; } - public LedgerInitPermission prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, + public LedgerInitProposal prepareLocalProposal(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, ConsensusSettings csSettings, CryptoSetting cryptoSetting) { // 创建初始化配置; LedgerInitSettingData initSetting = new LedgerInitSettingData(); @@ -249,7 +249,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon // 对初始交易签名,生成当前参与者的账本初始化许可; SignatureDigest permissionSign = SignatureUtils.sign(initTxContent, privKey); - localPermission = new LedgerInitPermissionData(currentId, permissionSign); + localPermission = new LedgerInitProposalData(currentId, permissionSign); this.currentId = currentId; return localPermission; @@ -300,7 +300,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon } @Override - public LedgerInitPermission requestPermission(int requesterId, SignatureDigest signature) { + public LedgerInitProposal requestPermission(int requesterId, SignatureDigest signature) { return localPermission; } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java index 93096613..38bc0575 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java @@ -4,7 +4,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.core.LedgerInitDecision; -import com.jd.blockchain.ledger.core.LedgerInitPermission; +import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.mocker.config.LedgerInitWebConfiguration; @@ -48,7 +48,7 @@ public class NodeWebContext { return controller.getInitTxContent(); } - public LedgerInitPermission getLocalPermission() { + public LedgerInitProposal getLocalPermission() { return controller.getLocalPermission(); } From e68e4996357b7cde5c438b18187a561e397a91d8 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 8 Aug 2019 11:27:46 +0800 Subject: [PATCH 029/124] Implements storage that separates LedgerSetting from LedgerMetadata; --- .../ledger/core/LedgerAdminAccount.java | 149 ++++++++++++------ .../ledger/core/LedgerConfiguration.java | 6 +- .../core/impl/LedgerRepositoryImpl.java | 2 +- .../core/impl/LedgerTransactionalEditor.java | 4 +- .../blockchain/ledger/LedgerMetaDataTest.java | 6 +- .../jd/blockchain/ledger/LedgerMetadata.java | 58 ++++--- .../blockchain/ledger/LedgerMetadata_V2.java | 27 ++++ ...LedgerSetting.java => LedgerSettings.java} | 2 +- 8 files changed, 178 insertions(+), 76 deletions(-) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java rename source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/{LedgerSetting.java => LedgerSettings.java} (95%) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java index c22a4ad2..89e1bc77 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java @@ -1,7 +1,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.LedgerSetting; +import com.jd.blockchain.ledger.LedgerSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,9 +29,11 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { public static final String LEDGER_META_PREFIX = "MTA" + LedgerConsts.KEY_SEPERATOR; public static final String LEDGER_PARTICIPANT_PREFIX = "PAR" + LedgerConsts.KEY_SEPERATOR; - public static final String LEDGER_PRIVILEGE_PREFIX = "PVL" + LedgerConsts.KEY_SEPERATOR; + public static final String LEDGER_SETTING_PREFIX = "SET" + LedgerConsts.KEY_SEPERATOR; + public static final String LEDGER_PRIVILEGE_PREFIX = "PRL" + LedgerConsts.KEY_SEPERATOR; private final Bytes metaPrefix; + private final Bytes settingPrefix; private final Bytes privilegePrefix; private LedgerMetadata origMetadata; @@ -44,19 +46,26 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { *
        * 对 LedgerMetadata 修改的新配置不能立即生效,需要达成共识后,在下一次区块计算中才生效; */ - private LedgerSetting previousSetting; + private LedgerSettings previousSettings; + + private HashDigest previousSettingHash; /** * 账本的参与节点; */ private ParticipantDataSet participants; + /** + * 账本参数配置; + */ + private LedgerSettings settings; + // /** // * 账本的全局权限设置; // */ // private PrivilegeDataSet privileges; - private ExPolicyKVStorage settingsStorage; + private ExPolicyKVStorage storage; private HashDigest adminAccountHash; @@ -80,7 +89,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { * 只在新建账本时调用此方法; * * @param ledgerSeed - * @param setting + * @param settings * @param partiList * @param exPolicyStorage * @param versioningStorage @@ -88,6 +97,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { public LedgerAdminAccount(LedgerInitSetting initSetting, String keyPrefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage versioningStorage) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); + this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); this.privilegePrefix = Bytes.fromString(keyPrefix + LEDGER_PRIVILEGE_PREFIX); ParticipantNode[] parties = initSetting.getConsensusParticipants(); @@ -108,15 +118,15 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { this.metadata = new LedgerMetadataImpl(); this.metadata.setSeed(initSetting.getLedgerSeed()); // 新配置; - this.metadata.setting = new LedgerConfiguration(initSetting.getConsensusProvider(), - initSetting.getConsensusSettings(), initSetting.getCryptoSetting()); - this.previousSetting = new LedgerConfiguration(initSetting.getConsensusProvider(), - initSetting.getConsensusSettings(), initSetting.getCryptoSetting()); + this.settings = new LedgerConfiguration(initSetting.getConsensusProvider(), initSetting.getConsensusSettings(), + initSetting.getCryptoSetting()); + this.previousSettings = new LedgerConfiguration(settings); + this.previousSettingHash = null; this.adminAccountHash = null; // 基于原配置初始化参与者列表; String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataSet(previousSetting.getCryptoSetting(), partiPrefix, exPolicyStorage, + this.participants = new ParticipantDataSet(previousSettings.getCryptoSetting(), partiPrefix, exPolicyStorage, versioningStorage); for (ParticipantNode p : parties) { @@ -124,20 +134,23 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { } // 初始化其它属性; - this.settingsStorage = exPolicyStorage; + this.storage = exPolicyStorage; this.readonly = false; } public LedgerAdminAccount(HashDigest adminAccountHash, String keyPrefix, ExPolicyKVStorage kvStorage, VersioningKVStorage versioningKVStorage, boolean readonly) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); + this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); this.privilegePrefix = Bytes.fromString(keyPrefix + LEDGER_PRIVILEGE_PREFIX); - this.settingsStorage = kvStorage; + this.storage = kvStorage; this.readonly = readonly; - this.origMetadata = loadAndVerifySettings(adminAccountHash); + this.origMetadata = loadAndVerifyMetadata(adminAccountHash); this.metadata = new LedgerMetadataImpl(origMetadata); + this.settings = loadAndVerifySettings(metadata.getSettingsHash()); // 复制记录一份配置作为上一个区块的原始配置,该实例仅供读取,不做修改,也不会回写到存储; - this.previousSetting = new LedgerConfiguration(metadata.getSetting()); + this.previousSettings = new LedgerConfiguration(settings); + this.previousSettingHash = metadata.getSettingsHash(); this.adminAccountHash = adminAccountHash; // this.privileges = new PrivilegeDataSet(metadata.getPrivilegesHash(), // metadata.getSetting().getCryptoSetting(), @@ -151,23 +164,49 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { // PrefixAppender.prefix(LEDGER_PARTICIPANT_PREFIX, versioningKVStorage), // readonly); String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), previousSetting.getCryptoSetting(), + this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), partiPrefix, kvStorage, versioningKVStorage, readonly); } - private LedgerMetadata loadAndVerifySettings(HashDigest adminAccountHash) { - // String base58Hash = adminAccountHash.toBase58(); - // String key = encodeMetadataKey(base58Hash); + private LedgerSettings loadAndVerifySettings(HashDigest settingsHash) { + if (settingsHash == null) { + return null; + } + Bytes key = encodeSettingsKey(settingsHash); + byte[] bytes = storage.get(key); + HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); + if (!hashFunc.verify(adminAccountHash, bytes)) { + String errorMsg = "Verification of the hash for ledger setting failed! --[HASH=" + key + "]"; + LOGGER.error(errorMsg); + throw new LedgerException(errorMsg); + } + return deserializeSettings(bytes); + } + + private LedgerSettings deserializeSettings(byte[] bytes) { + return BinaryProtocol.decode(bytes); + } + + private byte[] serializeSetting(LedgerSettings setting) { + return BinaryProtocol.encode(setting, LedgerSettings.class); + } + + private LedgerMetadata loadAndVerifyMetadata(HashDigest adminAccountHash) { Bytes key = encodeMetadataKey(adminAccountHash); - byte[] bytes = settingsStorage.get(key); + byte[] bytes = storage.get(key); HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); if (!hashFunc.verify(adminAccountHash, bytes)) { - LOGGER.error("The hash verification of ledger settings fail! --[HASH=" + key + "]"); - throw new LedgerException("The hash verification of ledger settings fail!"); + String errorMsg = "Verification of the hash for ledger metadata failed! --[HASH=" + key + "]"; + LOGGER.error(errorMsg); + throw new LedgerException(errorMsg); } return deserializeMetadata(bytes); } + private Bytes encodeSettingsKey(HashDigest settingsHash) { + return settingPrefix.concat(settingsHash); + } + private Bytes encodeMetadataKey(HashDigest metadataHash) { // return LEDGER_META_PREFIX + metadataHash; // return metaPrefix + metadataHash; @@ -188,12 +227,12 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { * 返回原来的账本配置; * *
        - * 此方法总是返回从上一个区块加载的账本配置,即时调用 {@link #setLedgerSetting(LedgerSetting)} 做出了新的更改; + * 此方法总是返回从上一个区块加载的账本配置,即时调用 {@link #setLedgerSetting(LedgerSettings)} 做出了新的更改; * * @return */ - public LedgerSetting getPreviousSetting() { - return previousSetting; + public LedgerSettings getPreviousSetting() { + return previousSettings; } /** @@ -201,8 +240,8 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { * * @return */ - public LedgerSetting getSetting() { - return metadata.getSetting(); + public LedgerSettings getSetting() { + return settings; } /** @@ -210,11 +249,12 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { * * @param ledgerSetting */ - public void setLedgerSetting(LedgerSetting ledgerSetting) { + public void setLedgerSetting(LedgerSettings ledgerSetting) { if (readonly) { throw new IllegalArgumentException("This merkle dataset is readonly!"); } - metadata.setSetting(ledgerSetting); + settings = ledgerSetting; + updated = true; } @Override @@ -259,14 +299,34 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { if (!isUpdated()) { return; } + // 计算并更新参与方集合的根哈希; participants.commit(); - metadata.setParticipantsHash(participants.getRootHash()); + HashFunction hashFunc = Crypto.getHashFunction(previousSettings.getCryptoSetting().getHashAlgorithm()); + + // 计算并更新参数配置的哈希; + if (settings == null) { + throw new LedgerException("Missing ledger settings!"); + } + byte[] settingsBytes = serializeSetting(settings); + HashDigest settingsHash = hashFunc.hash(settingsBytes); + metadata.setSettingsHash(settingsHash); + if (previousSettingHash == null || !previousSettingHash.equals(settingsHash)) { + Bytes settingsKey = encodeSettingsKey(settingsHash); + boolean nx = storage.set(settingsKey, settingsBytes, ExPolicy.NOT_EXISTING); + if (!nx) { + String base58MetadataHash = settingsHash.toBase58(); + // 有可能发生了并发写入冲突,不同的节点都向同一个存储服务器上写入数据; + String errMsg = "Ledger metadata already exist! --[MetadataHash=" + base58MetadataHash + "]"; + LOGGER.warn(errMsg); + throw new LedgerException(errMsg); + } + } + // 基于之前的密码配置来计算元数据的哈希; byte[] metadataBytes = serializeMetadata(metadata); - HashFunction hashFunc = Crypto - .getHashFunction(previousSetting.getCryptoSetting().getHashAlgorithm()); + HashDigest metadataHash = hashFunc.hash(metadataBytes); if (adminAccountHash == null || !adminAccountHash.equals(metadataHash)) { // update modify; @@ -274,14 +334,13 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { // String metadataKey = encodeMetadataKey(base58MetadataHash); Bytes metadataKey = encodeMetadataKey(metadataHash); - boolean nx = settingsStorage.set(metadataKey, metadataBytes, ExPolicy.NOT_EXISTING); + boolean nx = storage.set(metadataKey, metadataBytes, ExPolicy.NOT_EXISTING); if (!nx) { + String base58MetadataHash = metadataHash.toBase58(); // 有可能发生了并发写入冲突,不同的节点都向同一个存储服务器上写入数据; - // throw new LedgerException( - // "Ledger metadata already exist! --[LedgerMetadataHash=" + base58MetadataHash - // + "]"); - // LOGGER.warn("Ledger metadata already exist! --[MetadataHash=" + - // base58MetadataHash + "]"); + String errMsg = "Ledger metadata already exist! --[MetadataHash=" + base58MetadataHash + "]"; + LOGGER.warn(errMsg); + throw new LedgerException(errMsg); } adminAccountHash = metadataHash; @@ -311,17 +370,20 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { private byte[] seed; - private LedgerSetting setting; +// private LedgerSetting setting; private HashDigest participantsHash; + private HashDigest settingsHash; + public LedgerMetadataImpl() { } public LedgerMetadataImpl(LedgerMetadata metadata) { this.seed = metadata.getSeed(); - this.setting = metadata.getSetting(); +// this.setting = metadata.getSetting(); this.participantsHash = metadata.getParticipantsHash(); + this.settingsHash = metadata.getSettingsHash(); } @Override @@ -330,8 +392,8 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { } @Override - public LedgerSetting getSetting() { - return setting; + public HashDigest getSettingsHash() { + return settingsHash; } @Override @@ -343,9 +405,8 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { this.seed = seed; } - public void setSetting(LedgerSetting setting) { - // copy a new instance; - this.setting = new LedgerConfiguration(setting); + public void setSettingsHash(HashDigest settingHash) { + this.settingsHash = settingHash; } public void setParticipantsHash(HashDigest participantsHash) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerConfiguration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerConfiguration.java index 5605003c..e727c5ad 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerConfiguration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerConfiguration.java @@ -1,10 +1,10 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.LedgerSetting; +import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.utils.Bytes; -public class LedgerConfiguration implements LedgerSetting { +public class LedgerConfiguration implements LedgerSettings { private String consensusProvider; @@ -16,7 +16,7 @@ public class LedgerConfiguration implements LedgerSetting { this.cryptoSetting = new CryptoConfig(); } - public LedgerConfiguration(LedgerSetting origSetting) { + public LedgerConfiguration(LedgerSettings origSetting) { if (origSetting != null) { this.consensusProvider = origSetting.getConsensusProvider(); this.consensusSetting = origSetting.getConsensusSetting(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java index 1fe559b7..9505aaad 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java @@ -478,7 +478,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { return newDataSet; } - static TransactionSet newTransactionSet(LedgerSetting ledgerSetting, String keyPrefix, + static TransactionSet newTransactionSet(LedgerSettings ledgerSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) { // TransactionSet transactionSet = new // TransactionSet(ledgerSetting.getCryptoSetting(), diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java index c4b696c0..1f63302f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java @@ -13,7 +13,7 @@ import com.jd.blockchain.ledger.IllegalTransactionException; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerDataSnapshot; import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.LedgerSetting; +import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.LedgerTransaction; import com.jd.blockchain.ledger.OperationResult; import com.jd.blockchain.ledger.TransactionContent; @@ -115,7 +115,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { * @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能; * @return */ - public static LedgerTransactionalEditor createEditor(LedgerBlock previousBlock, LedgerSetting ledgerSetting, + public static LedgerTransactionalEditor createEditor(LedgerBlock previousBlock, LedgerSettings ledgerSetting, String ledgerKeyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) { // new block; HashDigest ledgerHash = previousBlock.getLedgerHash(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java index 401c5de2..7fbef523 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java @@ -8,7 +8,7 @@ import static org.junit.Assert.assertTrue; import java.util.Random; import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.LedgerSetting; +import com.jd.blockchain.ledger.LedgerSettings; import org.junit.Before; import org.junit.Test; @@ -119,8 +119,8 @@ public class LedgerMetaDataTest { LedgerConfiguration ledgerConfiguration = new LedgerConfiguration(consensusProvider, new Bytes(csSettingsBytes), cryptoConfig); - byte[] encodeBytes = BinaryProtocol.encode(ledgerConfiguration, LedgerSetting.class); - LedgerSetting deLedgerConfiguration = BinaryProtocol.decode(encodeBytes); + byte[] encodeBytes = BinaryProtocol.encode(ledgerConfiguration, LedgerSettings.class); + LedgerSettings deLedgerConfiguration = BinaryProtocol.decode(encodeBytes); // verify start assertTrue(ledgerConfiguration.getConsensusSetting().equals(deLedgerConfiguration.getConsensusSetting())); assertEquals(ledgerConfiguration.getCryptoSetting().getAutoVerifyHash(), diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java index 4d7a57b0..6ab04c89 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java @@ -6,31 +6,45 @@ import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; import com.jd.blockchain.crypto.HashDigest; -@DataContract(code = DataCodes.METADATA) +/** + * 账本的元数据; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.METADATA, name = "LEDGER-METADATA") public interface LedgerMetadata { - /** - * 账本的初始化种子; - * - * @return - */ - @DataField(order = 1, primitiveType = PrimitiveType.BYTES) - byte[] getSeed(); + /** + * 账本的初始化种子; + * + * @return + */ + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + byte[] getSeed(); - /** - * 共识参与方的默克尔树的根; - * - * @return - */ - @DataField(order = 2, primitiveType = PrimitiveType.BYTES) - HashDigest getParticipantsHash(); + /** + * 共识参与方的默克尔树的根; + * + * @return + */ + @DataField(order = 2, primitiveType = PrimitiveType.BYTES) + HashDigest getParticipantsHash(); - /** - * 账本配置; - * - * @return - */ - @DataField(order = 3, refContract = true) - LedgerSetting getSetting(); +// /** +// * 账本配置; +// * +// * @return +// */ +// @DataField(order = 3, refContract = true) +// LedgerSetting getSetting(); + + /** + * 账本配置的哈希; + * + * @return + */ + @DataField(order = 3, primitiveType = PrimitiveType.BYTES) + HashDigest getSettingsHash(); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java new file mode 100644 index 00000000..1fbe075e --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java @@ -0,0 +1,27 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.crypto.HashDigest; + +/** + * {@link LedgerMetadata_V2} 是 {@link LedgerMetadata} 的升级版本,新增加了 + * {@link #getPrivilegeHash()} 属性; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.METADATA, name = "LEDGER-METADATA-V2") +public interface LedgerMetadata_V2 extends LedgerMetadata { + + /** + * 加入新的版本; + * + * @return + */ + @DataField(order = 4, primitiveType = PrimitiveType.BYTES) + HashDigest getPrivilegeHash(); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSetting.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java similarity index 95% rename from source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSetting.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java index 1a0441bd..76e2ad2c 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSetting.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java @@ -8,7 +8,7 @@ import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.utils.Bytes; @DataContract(code = DataCodes.METADATA_LEDGER_SETTING) -public interface LedgerSetting { +public interface LedgerSettings { @DataField(order=0, primitiveType=PrimitiveType.TEXT) String getConsensusProvider(); From 6ac03c4c694c6df3a9de0b49d5b3b0fa2d72e232 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 8 Aug 2019 14:05:51 +0800 Subject: [PATCH 030/124] Added manager project; --- source/deployment/deployment-peer/pom.xml | 5 ++ .../src/main/resources/assembly.xml | 2 + source/manager/pom.xml | 53 +++++++++++++++++++ source/pom.xml | 1 + 4 files changed, 61 insertions(+) create mode 100644 source/manager/pom.xml diff --git a/source/deployment/deployment-peer/pom.xml b/source/deployment/deployment-peer/pom.xml index f9c3e56f..d5f818ac 100644 --- a/source/deployment/deployment-peer/pom.xml +++ b/source/deployment/deployment-peer/pom.xml @@ -15,6 +15,11 @@ peer ${project.version} + + com.jd.blockchain + manager + ${project.version} + com.jd.blockchain runtime-modular diff --git a/source/deployment/deployment-peer/src/main/resources/assembly.xml b/source/deployment/deployment-peer/src/main/resources/assembly.xml index 5e64c93e..e68857e2 100644 --- a/source/deployment/deployment-peer/src/main/resources/assembly.xml +++ b/source/deployment/deployment-peer/src/main/resources/assembly.xml @@ -36,6 +36,7 @@ com.jd.blockchain:runtime-modular com.jd.blockchain:runtime-modular-booter com.jd.blockchain:peer + com.jd.blockchain:manager com.jd.blockchain:deployment-peer @@ -52,6 +53,7 @@ com.jd.blockchain:runtime-modular com.jd.blockchain:runtime-modular-booter com.jd.blockchain:peer + com.jd.blockchain:manager com.jd.blockchain:deployment-peer diff --git a/source/manager/pom.xml b/source/manager/pom.xml new file mode 100644 index 00000000..cd6c9205 --- /dev/null +++ b/source/manager/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + + com.jd.blockchain + jdchain-root + 1.1.0-SNAPSHOT + + manager + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + \ No newline at end of file diff --git a/source/pom.xml b/source/pom.xml index 50aecdec..867606b1 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -32,6 +32,7 @@ storage gateway peer + manager sdk tools test From 40d8a8ee20e21bf91d304bde41df5a25c5a87cec Mon Sep 17 00:00:00 2001 From: zhaoguangwei Date: Thu, 8 Aug 2019 15:14:02 +0800 Subject: [PATCH 031/124] add the xml parse; --- .../src/main/java/com/jd/blockchain/ledger/KVDataObject.java | 2 ++ .../com/jd/blockchain/sdk/converters/ClientResolveUtil.java | 1 + 2 files changed, 3 insertions(+) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/KVDataObject.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/KVDataObject.java index 2769631c..1e8247ee 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/KVDataObject.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/KVDataObject.java @@ -79,6 +79,8 @@ public class KVDataObject implements KVDataEntry { return BytesUtils.toLong(bytesValue.getValue().toBytes()); case JSON: return bytesValue.getValue().toUTF8String(); + case XML: + return bytesValue.getValue().toUTF8String(); default: throw new IllegalStateException("Unsupported value type[" + getType() + "] to resolve!"); diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java index cbdf723a..3c251a66 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java @@ -50,6 +50,7 @@ public class ClientResolveUtil { case BYTES: case TEXT: case JSON: + case XML: innerKvData.setValue(valueObj.toString()); break; case INT32: From d201c568de0295ad135b97ea803071562e00c2ce Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Thu, 8 Aug 2019 15:19:41 +0800 Subject: [PATCH 032/124] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/base/src/main/resources/log4j2.xml | 74 ------------------ .../src/main/resources/scripts/startup.sh | 2 +- .../src/main/resources/scripts/ledger-init.sh | 4 +- .../src/main/resources/scripts/startup.sh | 2 +- source/gateway/src/main/resources/log4j2.xml | 16 ++-- .../jd/blockchain/peer/PeerServerBooter.java | 8 +- .../jd/blockchain/peer/web/PeerTimeTasks.java | 9 ++- source/peer/src/main/resources/log4j2.xml | 16 ++-- .../sdk/samples/SDKDemo_Constant.java | 38 ++++----- .../samples/SDK_Threads_KvInsert_Demo.java | 56 +++++++++++++ ...deNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl | Bin 0 -> 4077 bytes ...deNf7W5SZ1Xh1KXoQcbPEdPDM21nUsEp8zFN_0.mdl | Bin 0 -> 4077 bytes ...deNgxFpbzqTH3ZouX5vPv7A9M7pGWUjGJAan_0.mdl | Bin 0 -> 4077 bytes ...deNhGKHXohv3Bme6YgqPkETVQwDetmNFcdPK_0.mdl | Bin 0 -> 4077 bytes ...deNhpKuGE7bsrCsARipmdhFD7AGFFVkvzT6e_0.mdl | Bin 0 -> 4077 bytes ...deNi3yqaJ5G3N2rGLZbXLXJ1XF6ajiyXbdkR_0.mdl | Bin 0 -> 4077 bytes ...deNigyUzWXAySKzQH8W74sjXcQykK6Ss1eEH_0.mdl | Bin 0 -> 7118 bytes ...deNjhemDjb3aSg3SpMRzqUrhFZDygUNsKA3o_0.mdl | Bin 0 -> 7118 bytes ...deNkJg78L7L1KYv6Cqicqc5SmbCD9Y4bkpez_0.mdl | Bin 0 -> 4077 bytes ...deNmcdMwTeQ73C6MsKbMxhFQ2XCh6bPz7p21_0.mdl | Bin 0 -> 4077 bytes ...deNmnQmbpZS2QSXnJ73LeGDJmwkhDhyFsVjJ_0.mdl | Bin 0 -> 7118 bytes ...deNmpZN6vXC9i6pyhPpRxmBf7iiaPj7bhvN2_0.mdl | Bin 0 -> 4077 bytes ...deNmzy486oNRSzhS2z7QRVvVswxCyaz65wys_0.mdl | Bin 0 -> 7118 bytes ...deNnDgXmyep22ZyabbdtdEzCjxFiM9nLDiby_0.mdl | Bin 0 -> 4077 bytes ...deNnYU8txmjeRhi9rgiZSwAQHW632Q3rQZue_0.mdl | Bin 0 -> 4077 bytes ...deNoENuwCi3bVbhmoFG4w5mggmPsgGtpZMtF_0.mdl | Bin 0 -> 4255 bytes ...deNqAMP5kjnciodhTY6Pnf8o5qbBvFkEs25Q_0.mdl | Bin 0 -> 4077 bytes ...deNqr3pRJGZQFTUQgSZ6onhi7FVFDHkUXwd5_0.mdl | Bin 0 -> 4077 bytes ...deNrFwJSVujRK7RBizL46WC5cUtudARjfHvT_0.mdl | Bin 0 -> 4077 bytes ...deNroewGwCBmwFMgxUrEY4WgMq4GA54N82r5_0.mdl | Bin 0 -> 4077 bytes ...deNrpEqQFALAghHSLpjt2Zdx3k6SBqTCVsGA_0.mdl | Bin 0 -> 4077 bytes ...deNsH2ZedzAohTe6xRRZQvKJt4geSFfhmaoX_0.mdl | Bin 0 -> 7118 bytes ...deNstg544ncLvc9D94ezXmPFYUq4QoPVkEYL_0.mdl | Bin 0 -> 4077 bytes ...deNsxrjTsagkS53m7AWWKwnpvJ1YUGBGM3mC_0.mdl | Bin 0 -> 7118 bytes ...deNtFf3HuzSNV2oTyrdHW3XPtmKvHUWpBpyr_0.mdl | Bin 0 -> 4077 bytes ...deNtM6R2N5wMgUQw1jDyUZ8gHbaVDFgyMAVN_0.mdl | Bin 0 -> 4255 bytes ...deNtY59RJyvx45bj6fjkDocBGB9Nc7Sf4ymf_0.mdl | Bin 0 -> 4077 bytes ...deNu29NacEbeEKCXpDAu1BcU1R54gYVKpJBX_0.mdl | Bin 0 -> 4077 bytes ...deNuRZEjx2UN1nmcmGwdx1AqFc3uFaHrKtjX_0.mdl | Bin 0 -> 7118 bytes ...deNv7SiL1nujKFuns1cGmWiaKD49jryjcYeH_0.mdl | Bin 0 -> 7118 bytes ...deNveXpq25eUxab6N21rJgg4cw7BXvKfm2LV_0.mdl | Bin 0 -> 4077 bytes ...deNvfeFwgRQxftBuUpekDCZJMLFwbWxXpzri_0.mdl | Bin 0 -> 7118 bytes ...deNvpJH7no46KQpnyFyNnR7fxT3hWsUrYdgz_0.mdl | Bin 0 -> 7118 bytes ...deNxXeWNfmRAXJX5xPSpgq6JArNrxsPDLgX3_0.mdl | Bin 0 -> 7118 bytes ...deNxd6yNub5qWhB49eBPGKnwNf5smuhSShDb_0.mdl | Bin 0 -> 4077 bytes ...deNxzTy6YegizyRKxA1EH87wYdHHBDMLqwSZ_0.mdl | Bin 0 -> 7167 bytes ...deNy5TGpQhi4pz8jNDX3wz3VABLPuGbcvLin_0.mdl | Bin 0 -> 7118 bytes ...deNz6SbUf9WZUFnkcZK3o2qYcTcLPvikMLJz_0.mdl | Bin 0 -> 4077 bytes ...deNzBjCSBy9uQDyYqgiUkpm4zpe1Fs9gEhXK_0.mdl | Bin 0 -> 4077 bytes ...deNzH3wcSNAJDX5RtaNKFxw6UQJMzdcRQUTp_0.mdl | Bin 0 -> 4077 bytes ...deNzJu2FYBNyZo9Ue757Chjj9vsxYFAnMHSS_0.mdl | Bin 0 -> 4077 bytes ...deP11pE4f8TWU5DpqJJVnojuxScK4yRceAiX_0.mdl | Bin 0 -> 4077 bytes ...deP1Vzbe3vbn8rjgoGN9FwRGyW6192PASjLS_0.mdl | Bin 0 -> 4077 bytes ...deP2WzejQqJdREjbbpjTsNno863XTvjCemvL_0.mdl | Bin 0 -> 7118 bytes ...deP2o8X3tnzLUV5kvmnT1rU6tL48Mn9yKnSp_0.mdl | Bin 0 -> 7118 bytes ...deP3pQGeyQhZJxsHhBu48kaNwYKow4HvdAcA_0.mdl | Bin 0 -> 4077 bytes ...deP3sKx6RrxZVGoYdJAGUrttNMNwWU3JC2NV_0.mdl | Bin 0 -> 7118 bytes .../boot/LedgerInitCommandBooter.java | 13 ++- .../initializer/LedgerBindingConfig.java | 48 +++++++++++- .../tools/initializer/LedgerInitCommand.java | 13 ++- .../initializer/LedgerInitProperties.java | 13 +++ .../tools/initializer/LogPrompter.java | 52 ++++++++++++ .../src/main/resources/application.properties | 8 +- .../src/main/resources/banner2.txt | 12 --- .../src/main/resources/banner3.txt | 12 --- .../src/main/resources/log4j2-init.xml | 62 +++++++++++++++ 66 files changed, 303 insertions(+), 155 deletions(-) delete mode 100644 source/base/src/main/resources/log4j2.xml create mode 100644 source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Threads_KvInsert_Demo.java create mode 100644 source/test/test-integration/runtime/LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNf7W5SZ1Xh1KXoQcbPEdPDM21nUsEp8zFN_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNgxFpbzqTH3ZouX5vPv7A9M7pGWUjGJAan_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNhGKHXohv3Bme6YgqPkETVQwDetmNFcdPK_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNhpKuGE7bsrCsARipmdhFD7AGFFVkvzT6e_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNi3yqaJ5G3N2rGLZbXLXJ1XF6ajiyXbdkR_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNigyUzWXAySKzQH8W74sjXcQykK6Ss1eEH_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNjhemDjb3aSg3SpMRzqUrhFZDygUNsKA3o_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNkJg78L7L1KYv6Cqicqc5SmbCD9Y4bkpez_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNmcdMwTeQ73C6MsKbMxhFQ2XCh6bPz7p21_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNmnQmbpZS2QSXnJ73LeGDJmwkhDhyFsVjJ_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNmpZN6vXC9i6pyhPpRxmBf7iiaPj7bhvN2_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNmzy486oNRSzhS2z7QRVvVswxCyaz65wys_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNnDgXmyep22ZyabbdtdEzCjxFiM9nLDiby_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNnYU8txmjeRhi9rgiZSwAQHW632Q3rQZue_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNoENuwCi3bVbhmoFG4w5mggmPsgGtpZMtF_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNqAMP5kjnciodhTY6Pnf8o5qbBvFkEs25Q_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNqr3pRJGZQFTUQgSZ6onhi7FVFDHkUXwd5_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNrFwJSVujRK7RBizL46WC5cUtudARjfHvT_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNroewGwCBmwFMgxUrEY4WgMq4GA54N82r5_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNrpEqQFALAghHSLpjt2Zdx3k6SBqTCVsGA_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNsH2ZedzAohTe6xRRZQvKJt4geSFfhmaoX_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNstg544ncLvc9D94ezXmPFYUq4QoPVkEYL_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNsxrjTsagkS53m7AWWKwnpvJ1YUGBGM3mC_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNtFf3HuzSNV2oTyrdHW3XPtmKvHUWpBpyr_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNtM6R2N5wMgUQw1jDyUZ8gHbaVDFgyMAVN_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNtY59RJyvx45bj6fjkDocBGB9Nc7Sf4ymf_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNu29NacEbeEKCXpDAu1BcU1R54gYVKpJBX_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNuRZEjx2UN1nmcmGwdx1AqFc3uFaHrKtjX_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNv7SiL1nujKFuns1cGmWiaKD49jryjcYeH_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNveXpq25eUxab6N21rJgg4cw7BXvKfm2LV_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNvfeFwgRQxftBuUpekDCZJMLFwbWxXpzri_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNvpJH7no46KQpnyFyNnR7fxT3hWsUrYdgz_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNxXeWNfmRAXJX5xPSpgq6JArNrxsPDLgX3_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNxd6yNub5qWhB49eBPGKnwNf5smuhSShDb_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNxzTy6YegizyRKxA1EH87wYdHHBDMLqwSZ_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNy5TGpQhi4pz8jNDX3wz3VABLPuGbcvLin_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNz6SbUf9WZUFnkcZK3o2qYcTcLPvikMLJz_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNzBjCSBy9uQDyYqgiUkpm4zpe1Fs9gEhXK_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNzH3wcSNAJDX5RtaNKFxw6UQJMzdcRQUTp_0.mdl create mode 100644 source/test/test-integration/runtime/LdeNzJu2FYBNyZo9Ue757Chjj9vsxYFAnMHSS_0.mdl create mode 100644 source/test/test-integration/runtime/LdeP11pE4f8TWU5DpqJJVnojuxScK4yRceAiX_0.mdl create mode 100644 source/test/test-integration/runtime/LdeP1Vzbe3vbn8rjgoGN9FwRGyW6192PASjLS_0.mdl create mode 100644 source/test/test-integration/runtime/LdeP2WzejQqJdREjbbpjTsNno863XTvjCemvL_0.mdl create mode 100644 source/test/test-integration/runtime/LdeP2o8X3tnzLUV5kvmnT1rU6tL48Mn9yKnSp_0.mdl create mode 100644 source/test/test-integration/runtime/LdeP3pQGeyQhZJxsHhBu48kaNwYKow4HvdAcA_0.mdl create mode 100644 source/test/test-integration/runtime/LdeP3sKx6RrxZVGoYdJAGUrttNMNwWU3JC2NV_0.mdl create mode 100644 source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LogPrompter.java delete mode 100644 source/tools/tools-initializer/src/main/resources/banner2.txt delete mode 100644 source/tools/tools-initializer/src/main/resources/banner3.txt create mode 100644 source/tools/tools-initializer/src/main/resources/log4j2-init.xml diff --git a/source/base/src/main/resources/log4j2.xml b/source/base/src/main/resources/log4j2.xml deleted file mode 100644 index 26f090d7..00000000 --- a/source/base/src/main/resources/log4j2.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/source/deployment/deployment-gateway/src/main/resources/scripts/startup.sh b/source/deployment/deployment-gateway/src/main/resources/scripts/startup.sh index db75a6ad..44a1a528 100644 --- a/source/deployment/deployment-gateway/src/main/resources/scripts/startup.sh +++ b/source/deployment/deployment-gateway/src/main/resources/scripts/startup.sh @@ -5,5 +5,5 @@ GATEWAY=$(ls $HOME/lib | grep deployment-gateway-) if [ ! -n "$GATEWAY" ]; then echo "GateWay Is Null !!!" else - nohup java -jar -server $HOME/lib/$GATEWAY -c $HOME/config/gateway.conf $* > gw.out 2>&1 & + nohup java -jar -server -Dgateway.log=$HOME $HOME/lib/$GATEWAY -c $HOME/config/gateway.conf $* >$HOME/bin/gw.out 2>&1 & fi \ No newline at end of file diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/ledger-init.sh b/source/deployment/deployment-peer/src/main/resources/scripts/ledger-init.sh index d0c3f5f2..660676e8 100644 --- a/source/deployment/deployment-peer/src/main/resources/scripts/ledger-init.sh +++ b/source/deployment/deployment-peer/src/main/resources/scripts/ledger-init.sh @@ -5,5 +5,5 @@ boot_file=$(ls $HOME/libs | grep tools-initializer-booter-) if [ ! -n "$boot_file" ]; then echo "tools-initializer-booter is null" else - java -jar $HOME/libs/$boot_file -l $HOME/config/init/local.conf -i $HOME/config/init/ledger.init $* -fi + java -jar -server -Dinit.log=$HOME $HOME/libs/$boot_file -l $HOME/config/init/local.conf -i $HOME/config/init/ledger.init $* +fi \ No newline at end of file diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh b/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh index 371b8d78..d0fdd3d5 100644 --- a/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh +++ b/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh @@ -5,5 +5,5 @@ PEER=$(ls $HOME/system | grep deployment-peer-) if [ ! -n "$PEER" ]; then echo "Peer Is Null !!!" else - nohup java -jar -server -Xmx2g -Xms2g $HOME/system/$PEER -home=$HOME -c $HOME/config/ledger-binding.conf -p 7080 $* & + nohup java -jar -server -Xmx2g -Xms2g -Dpeer.log=$HOME $HOME/system/$PEER -home=$HOME -c $HOME/config/ledger-binding.conf -p 7080 $* >$HOME/bin/peer.out 2>&1 & fi \ No newline at end of file diff --git a/source/gateway/src/main/resources/log4j2.xml b/source/gateway/src/main/resources/log4j2.xml index d81fe1dc..6b49c3e9 100644 --- a/source/gateway/src/main/resources/log4j2.xml +++ b/source/gateway/src/main/resources/log4j2.xml @@ -13,12 +13,12 @@ - + - + @@ -27,8 +27,8 @@ - + @@ -38,8 +38,8 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java b/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java index 8e7811a0..aba98521 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java @@ -58,8 +58,12 @@ public class PeerServerBooter { if (ledgerBindConfigFile == null) { ConsoleUtils.info("Load build-in default configuration ..."); ClassPathResource configResource = new ClassPathResource("ledger-binding.conf"); - InputStream in = configResource.getInputStream(); - ledgerBindingConfig = LedgerBindingConfig.resolve(in); + + try (InputStream in = configResource.getInputStream()) { + ledgerBindingConfig = LedgerBindingConfig.resolve(in); + } catch (Exception e) { + throw e; + } } else { ConsoleUtils.info("Load configuration,ledgerBindConfigFile position="+ledgerBindConfigFile); File file = new File(ledgerBindConfigFile); diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerTimeTasks.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerTimeTasks.java index 698fb584..dc5c77ec 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerTimeTasks.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/PeerTimeTasks.java @@ -107,14 +107,17 @@ public class PeerTimeTasks implements ApplicationContextAware { } private LedgerBindingConfig loadLedgerBindingConfig() throws Exception { - LedgerBindingConfig ledgerBindingConfig; + LedgerBindingConfig ledgerBindingConfig = null; ledgerBindConfigFile = PeerServerBooter.ledgerBindConfigFile; LOGGER.debug("Load LedgerBindConfigFile path = {}", ledgerBindConfigFile == null ? "Default" : ledgerBindConfigFile); if (ledgerBindConfigFile == null) { ClassPathResource configResource = new ClassPathResource("ledger-binding.conf"); - InputStream in = configResource.getInputStream(); - ledgerBindingConfig = LedgerBindingConfig.resolve(in); + try (InputStream in = configResource.getInputStream()) { + ledgerBindingConfig = LedgerBindingConfig.resolve(in); + } catch (Exception e) { + throw e; + } } else { File file = new File(ledgerBindConfigFile); ledgerBindingConfig = LedgerBindingConfig.resolve(file); diff --git a/source/peer/src/main/resources/log4j2.xml b/source/peer/src/main/resources/log4j2.xml index b3630520..3f7b9d7a 100644 --- a/source/peer/src/main/resources/log4j2.xml +++ b/source/peer/src/main/resources/log4j2.xml @@ -13,12 +13,12 @@ - + - + @@ -27,8 +27,8 @@ - + @@ -38,8 +38,8 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java index 3ee48e47..f0237cec 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java @@ -7,37 +7,37 @@ import java.io.File; public class SDKDemo_Constant { - public static final String GW_IPADDR = "127.0.0.1"; -// public static final String GW_IPADDR = "192.168.151.41"; - - public static final int GW_PORT = 11000; -// public static final int GW_PORT = 18081; - - public static final String[] PUB_KEYS = { - "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", - "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", - "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", - "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; - - public static final String[] PRIV_KEYS = { - "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", - "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", - "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", - "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; +// public static final String GW_IPADDR = "127.0.0.1"; + public static final String GW_IPADDR = "192.168.151.41"; +// public static final int GW_PORT = 11000; + public static final int GW_PORT = 18081; // public static final String[] PUB_KEYS = { -// "3snPdw7i7PXvEDgq96QyzcKhfWL4mgYspzKwvgXiuAidWb2rkRMgDY", +// "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", // "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", // "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", // "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; // // public static final String[] PRIV_KEYS = { -// "177gjsxj2ezADGthZ4tGqWeCAqRAwtNvesPjRnyKqCb1huU8LKZmJ3HGZNMPKWQJK3DP1B2", +// "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", // "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", // "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", // "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; + + public static final String[] PUB_KEYS = { + "3snPdw7i7PXvEDgq96QyzcKhfWL4mgYspzKwvgXiuAidWb2rkRMgDY", + "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", + "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", + "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; + + public static final String[] PRIV_KEYS = { + "177gjsxj2ezADGthZ4tGqWeCAqRAwtNvesPjRnyKqCb1huU8LKZmJ3HGZNMPKWQJK3DP1B2", + "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", + "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", + "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; + public static final String PASSWORD = "abc"; public static final byte[] readChainCodes(String contractZip) { diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Threads_KvInsert_Demo.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Threads_KvInsert_Demo.java new file mode 100644 index 00000000..c4924af3 --- /dev/null +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Threads_KvInsert_Demo.java @@ -0,0 +1,56 @@ +package com.jd.blockchain.sdk.samples; + +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.utils.codec.Base58Utils; + +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SDK_Threads_KvInsert_Demo extends SDK_Base_Demo { + + public static void main(String[] args) throws Exception { + new SDK_Threads_KvInsert_Demo().executeThreadsInsert(); + } + + public void executeThreadsInsert() throws Exception { + + final int MAX = 30; + + final String dataAddress = "LdeNqP4S88t1YjkGQaCGbX95ygD6hA2B6yjp6"; + + ExecutorService threadPool = Executors.newFixedThreadPool(50); + + final CountDownLatch latch = new CountDownLatch(MAX); + + for (int i = 0; i < MAX; i++) { + + threadPool.execute(() -> { + TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); + + String key = System.currentTimeMillis() + "-" + + System.nanoTime() + "-" + + new Random(Thread.currentThread().getId()).nextInt(1024); + + txTemp.dataAccount(dataAddress).setText(key,"value1",-1); + + // TX 准备就绪 + PreparedTransaction prepTx = txTemp.prepare(); + prepTx.sign(adminKey); + + // 提交交易; + TransactionResponse response = prepTx.commit(); + + System.out.printf("Key = %s, Result = %s \r\n", key, response.isSuccess()); + + latch.countDown(); + + }); + } + + latch.await(); + System.out.println("It is Over !!!"); + System.exit(0); + } +} diff --git a/source/test/test-integration/runtime/LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl b/source/test/test-integration/runtime/LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl new file mode 100644 index 0000000000000000000000000000000000000000..ddf9c66802c4fa96d6bad111a772ab2165168bfb GIT binary patch literal 4077 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td zw=zPF3<(QyfqzjWyRHV7gviUvsHt;-Wo5cRIInrgyk}kM-CHq8$XNY=j)R`Pk}G8s z&AIZy{__mhTGN6_=XAaU@F7&6*3jkGP8CK~t#`6()ZJt*dfZ8BDo|I>Gh9cZPBnq_3AWSU$>ia=S27gNh zHGXFOM*#j_1M^RfrzXEqBDqpBvU+B0X>4w20sD>qt-sYbeg<=}Gc!}IX-#Ym#obUev|LHTQy`i;@71aI@-+z?Sudx>R{iN0H z3}N=BP`d|zMnN87kIUG~(B3{@OUHq9nDSh9+?=u{`U_x=0#HgdE`^VrKp}+^%RiOm z7fTW2Q_NrC4GjiT-I};ibNTdNXRIj=2mt}lu%PcekC|(4i=I>jZM7a31Z~MH?^}pjZU9T7JtiWy zrJeg@{pi3=zCc+!QK78N(j6v2FsDV+(SrZK0;Qc4uuS&{-^0WU7n+%@?qq&Wj4KQJ z@WfmxbXAC9emtr>D}Rz?0zUzh$>ore^|2JxSvdCy0>)cgM7F-y5VZjb1~3cwq@`}-Tpj<&6wLG_nzV{Y?#CPeiCSn zY@A;mK!G{pGAsdi^r>beiH@e;>SMO%F43*J! zTE`6m1_dv;(NORP`_r;Gjq>fP%jMmK<7vx=Yc@g(Jve$O%7VPfLq{TKO7K30mGVLC z!(G9|EhV0l9IDJ{hbDX^PH$M$aPV2XE(n-!=h7J)a(jJC;qQ$JD>{c~(=4a6U6e6) z&R6*?zpYD=s;}N93sQ16c^HI=b5AkNwp@|f;1wT1JB5jq&jE46;08fVQT}Xd3*3D1 zb4GaSE}Jl|eiI<^h)!uW@}^|*ft%w1zjm*{Z6OM!`dGfm@AekFjDxDEPXdY5dxK&< zF|49@UZCIqOn8Go&EMMLnJ4s2?h9oi6wZ3gI~Bfti~@SdoF4j=(kVTYye3S!j)6RQ zBV;!lD(&4R-BM;Dwi=YAURGP{;h$PNo9@=Hx=or$>QR$BBkI96=jr)&c8TQa$BjIE z>sFeOtWurHpiOcLECD``)ENL*v6fex`FZM$*ja>(mP5l-T(4Loh>frVFp3?y-!#5qVT}7zHEER)8WHt;J8wZP8XTnO)9n?Qqk1HwO zj-(DHVE5}s#nnx*T{sVdFyJ=l0>E{VJMQRoGx45lu~9@XTL)&(Nu2x-6MV2||JacWk4x$GS_4;FHxorDS_qE=YDQ+fDkVUxo zRY-GUL664T_lgAg2II_hG;)N6WkLi6-q_3Gc9ULMcg5H;#Z05-%nb0Y7+PxwhMEhl znTN2hEAAic-YdeByl3hv!;bzqJ1>tZ`qZU(;mL}=L;AG3TQWx=#le|J`e*Opk;Zo* z>Y_8+c@i`B`%uV>QV66>I|ts=)>)>ZGKmAFX!IL#Msw8ukRMF)_8lZCf)a?9a@_b#?+C@<^J{v z`(nlZ{YU?qCyuSB3~wZz)r@)v57?2o_^wJ9`KWFuB4{#s{C#c{ z(5MY;p-k|G=)HKL7(q_K18GO}7$**yp@fHyy`&wCYichliPT^0O*s>nBWLSSx#l6s zr)UtAVW8xvN?3ZYTIQ;=FFnec;GB)O0S}>nO40o4Rav2tJ7n)ybg#g8h%%4(S*XG> z>EPOX>!_w&IlW~P+DFwl?pSFg;(=l|Yw5xnlLxluNl&%?8*wmd%K1qAT*|&~*I0z8 z%Su7ww?_SC>cer&-c>{NNz5A=?L?OYIlJsxa!&L*e6(9U4SJ5i6i=F2Mu`fGX&}W6 z-8M^N-#nt$N;@|mc5Y5<&%QKkzJP0HIs2-2=mqHBYg2^qDY+Mi#=CE8l>x{3zVGlJ zM&i)J>t=*h8OBb4;@}+5c&Bf!s!@ZL_%s>yfREn7fT%)7)g%Z__$pwbf>lL@b`X@i z9X~&n2Bx*`BFlcIA`+(ae-D$;J%xT4Ow=P%P z;{{$l*M0p$XuR>rk;UrN2qg{ckd7UwvP0?s+Lbg`NChGzpIs&@E|9f~9hpA8BHwFRw*F4@ZS2JE>`A9zwa|<;v87lT-d%xPNca&)&Fe&?S5(dg)6BgMuLXx&Ctpd zjkk{uSYQC*_uoxbrf+JmdQ!zU=pI@s<4=9A_THTcWD^&muq5AEF-1Nk2^W{0>M7m> z^eGgz63jpevKY^(zet8;Nm92EL1uWmzumaiBi}sPvlB%hO3|_u*qY1@aIf`SZxbzf z6rQQCqPua7*Kt4(%Jmi2&q9xLs+b|#IJU45I3fh%qG7$wwCe3v;QR#9j7_BKVZrKf zk!dDgx$C=9!f!#*9ivtGf(cn87(6`l)Wotd#s)vxyL5?I~o0SftU9Q4KGz zU3`#fJS@i9Y+NovDkR*@DyaBO`c`GROj1|cUDiUdvM^^g4l-6vb&cp#cB)gt%Z7G8 zy($U?(Y321TJo!Y{nsXo_y5;q{b;a)!+Rk05=6~)-u=5;$TADaL23RqF1;=QS;F^y zemk{R*C3@I1J^p3eou%?8V&8KbVhn-?mlu*)G~v^SQUl0 zU9b7-NL(jvfqb;?ZU4rMW&3@9} z>%k-#v|u0W^;*s;sy}*MnLRBO<{k1c`58h0r+#CsZ#%iJPJ{OWx7yrO=BUitkTefG z8JrxGJNOOYEY_0l1-#%8N;}O+usaR~0iVF~zWJuBlwbfwcgaHDN6D(fce1T(JZVCY zlvWa_-S5Sj@vNJTQr-|bM}5Qv4*l+)boxaaXN|9yi|2kwLzwRaefTF`TCNwOs^RJY z4J^(`HtgL-O%>3nPw5juxrDV7wjD=))!H|w*)@$r{+32-1_knscfIY*IX$Y#rjTn9 z8N$RmzV*#V4H*du7xi};oAf)h_p z{Dp-WJvFVY_lW=2SlKvQ$%<#);%mB@hyG@j0a9N;GfYc-ot-F-ExjDdwDMo+MmlXc~qiB!vFH5etNl@wLp$zgp>F<+F!stgG>uubq9 zmkNwW9viB8ED506LfX2_fYI;`9u`Cf_)vbAf&b+{b3FrBhms_X$M)mWWMb?hDptAj z@yw~{D4AvcRxKL06tm?muptVO!gP&I$|#+!yI)m|K(sn{yXa=U$vuz=4Si4yvtF#D z*7+lEDwM2^FdNTNZWK6gi5(^8x@)~W|5pnxlEw2|O=x_?L30fh(Y6os#7S~L@@+P4 z^J*F$qv;ryvuRqYOtidXyg<88NobPSF%q^DCOHd+Rq2e|ov6jknsyiEj!DY1Yg*0q zjtqlAz~$aPe60?Qz;c2ZsE@gKO#6gu%J30o!2zmG$kT#+?AIEloon?L`ym!_n~#0M zsJMB&N}c?Hit~zr&jfO0HbwgbZ;ZvYkNW7dE{Cw5VG ztFo+JQ)NHQjSw^_8sI9ten3SD%cWUz9p=s7wcCPxat@UI3cBq(yawob5%R$I%x`wW z&|bs&Kp+if$M<+-jj^u~dBZRj>Lz`@v|9k9hOG*0)wId+=#5!x7w9aYm%{p|0-&P> zT>C^eMl*xhJ2w~J8Y<4yFYE;QL*Y|csk{?Z!t6rhq%@Y_iNwuEGTjbx9Fo|?o(WRF zo)(lL?sm``aYR0#qv_2z()q%nug2qz%V&pkw>a|&6%ePQ_<_tH5$0(8X$DFChQ=T%2vroqnEIcv%9A;*G zw1(re!slFy-;!FR*MeS}(v11Z2!J-@&c|_h^ptnIOaHOOYDxv(9NB`p`{3ymlGvG> zrEQ^-E5XE5EzII{Cx#02n*J1L!F;Eibq1(t{EK5A+pxgHe4-l^`FGQsM^cy)Hv!e! z@qpGtQXkw^S*6HXn(roHiF&5)fjju0>A>Izy6Z+!OTzPo7* z3Zq2x8b4bt^3>-9eqiS;X9-ES5Fu@>BGX$*1tujTBNv}-_C#AR^F;bBd8!_Fm(p6L zS1g*I^bO+xoXIHtu#Sv+Lc35}lPUomTHZF9L|P58~v>hDw(exnk? zUf+^8(tdtOS|=e)+-E@y@p)^!Z)?u`5=AH|e-PA}q3`vwQJSsH!IRg=fLO@1g_c{= zti6Eq!=;Kjwl{U6IF^-Pzp$< z5Ym75Fn10EEXW#AIO{9%z!VE##b=sWB-sa$#l^5E4a($&FhtNu@mVv(RfH)%YMrMt(r8!p<_H`pZ3_52QAmF#vcSKYUW1e6qFgTm8}ll<~W{1o1_EvQ-h^2ea3T~@FNKe#tClFcw#_Vtm%Neoj0&jHHy{A(pfoT#=m>uq0 zu7bx)19R|3k(d%Vpmm^YSnY56Q$Dr3Tf*odm8 zrxf3}yUOXD6CEoC=*1PH9eoa=WUcDq;FK9hvLgk?ny_-5uWqaL6=&>uEH+(J zP+Oaa4ck`0-|S$>#-l$`A?s!EvK@*fT6=jjm-F{L_?E|zp|rrRdv9Zf6fh3{Yy^{5Lc*qeb7H18>7`Xf zwpuhuP(7>7Tm9pOnZv6QOg(sln9}#~u80>Vvi3GQ-pQ{YR1W2-|jq}w^f0OqIQeegU(uQ--YmY_1| z4GyhmcMGdPaNud<{=Bt^GBC*e@pyG1921yex;Wl%d&5OncJvIJk$FF!6$Z$umAvQJ zL)ka2x^Po8bkZZa-Qhr<(X-&O{C1Qa(LI#$7 zv0F}K8_WUfQsemw90D$~fjHT^J9|n6dP@g^z4(Xrcvy$qgtH3V?cAK(ipAjWo}mtf zK2Up`Qa7YquCsGafrG705v0h43|fpLLyoI%@~=V{%5Kv8qY-;kPEc=waM}uSD}#_x z$WZ^86Gx~Zga~Ov^78o?*ZKo?#gSj9moGW;pOOJ*LA?G2yT+JbANbd<{gQ_MA(sPV zAUc*Gr1X`mzalPa=_|yYKM{YB)c*{+q^Yk!;49F-@xlL$zof0N@Gt%r|C10#5W!c5 zeyyNO8vDvn1;Wr@5&KDM|1+53C-#pl&HX3%??~={Hg`#L|B%br0}$2!i!c9a?&ln@ zk=~c~uW0YFb;2zk800sEgkO=ep^uM*OU>S8uPI-td zw=zPF3<(QyfqzjWyRHV7gviUvsHt;-Wo5cRIInrgyk}kM-CHq8$XNY=j)R`Pk}G8s z&AIZy{__mhTGN6_=XAaU@F7&6*3jkGP8CK~t#`6()ZJt*dfZ8BDo|I>Gh9cZPBnq_3AWSU$>ia=S27gNh zHGXFOM*#j_1M^RfrzXEqBDqpBvU+B0X>4w20sD>qt-sYbeg<=}Gc!}IX-#Ym#obUev|LHTQy`i;@71aI@-+z?Sudx>R{iN0H z3}N=BP`d|zMnN87kIUG~(B3{@OUHq9nDSh9+?=u{`U_x=0#HgdE`^VrKp}+^%RiOm z7fTW2Q_NrC4GjiT-I};ibNTdNXRIj=2mt}lu%PcekC|(4i=I>jZM7a31Z~MH?^}pjZU9T7JtiWy zrJeg@{pi3=zCc+!QK78N(j6v2FsDV+(SrZK0;Qc4uuS&{-^0WU7n+%@?qq&Wj4KQJ z@WfmxbXAC9emtr>D}Rz?0zUzh$>ore^|2JxSvdCy0>)cgM7F-y5VZjb1~3cwq@`}-Tpj<&6wLG_nzV{Y?#CPeiCSn zY@A;mK!G{pGAsdi^r>beiH@e;>SMO%F43*J! zTE`6m1_dv;(NORP`_r;Gjq>fP%jMmK<7vx=Yc@g(Jve$O%7VPfLq{TKO7K30mGVLC z!(G9|EhV0l9IDJ{hbDX^PH$M$aPV2XE(n-!=h7J)a(jJC;qQ$JD>{c~(=4a6U6e6) z&R6*?zpYD=s;}N93sQ16c^HI=b5AkNwp@|f;1wT1JB5jq&jE46;08fVQT}Xd3*3D1 zb4GaSE}Jl|eiI<^h)!uW@}^|*ft%w1zjm*{Z6OM!`dGfm@AekFjDxDEPXdY5dxK&< zF|49@UZCIqOn8Go&EMMLnJ4s2?h9oi6wZ3gI~Bfti~@SdoF4j=(kVTYye3S!j)6RQ zBV;!lD(&4R-BM;Dwi=YAURGP{;h$PNo9@=Hx=or$>QR$BBkI96=jr)&c8TQa$BjIE z>sFeOtWurHpiOcLECD``)ENL*v6fex`FZM$*ja>(mP5l-T(4Loh>frVFp3?y-!#5qVT}7zHEER)8WHt;J8wZP8XTnO)9n?Qqk1HwO zj-(DHVE5}s#nnx*T{sVdFyJ=l0>E{VJMQRoGx45lu~9@XTL)&(Nu2x-6MV2||JacWk4x$GS_4;FHxorDS_qE=YDQ+fDkVUxo zRY-GUL664T_lgAg2II_hG;)N6WkLi6-q_3Gc9ULMcg5H;#Z05-%nb0Y7+PxwhMEhl znTN2hEAAic-YdeByl3hv!;bzqJ1>tZ`qZU(;mL}=L;AG3TQWx=#le|J`e*Opk;Zo* z>Y_8+c@i`B`%uV>QV66>I|ts=)>)>ZGKmAFX!IL#Msw8ukRMF)_8lZCf)a?9a@_b#?+C@<^J{v z`(nlZ{YU?qCyuSB3~wZz)r@)v57?2o_^wJ9`KWFuB4{#s{C#c{ z(5MY;p-k|G=)HKL7(q_K18GO}7$**yp@fHyy`&wCYichliPT^0O*s>nBWLSSx#l6s zr)UtAVW8xvN?3ZYTIQ;=FFnec;GB)O0S}>nO40o4Rav2tJ7n)ybg#g8h%%4(S*XG> z>EPOX>!_w&IlW~P+DFwl?pSFg;(=l|Yw5xnlLxluNl&%?8*wmd%K1qAT*|&~*I0z8 z%Su7ww?_SC>cer&-c>{NNz5A=?L?OYIlJsxa!&L*e6(9U4SJ5i6i=F2Mu`fGX&}W6 z-8M^N-#nt$N;@|mc5Y5<&%QKkzJP0HIs2-2=mqHBYg2^qDY+Mi#=CE8l>x{3zVGlJ zM&i)J>t=*h8OBb4;@}+5c&Bf!s!@ZL_%s>yfREn7fT%)7)g%Z__$pwbf>lL@b`X@i z9X~&n2Bx*`BFlcIA`+(ae-D$;J%xT4Ow=P%P z;{{$l*M0p$XuR>rk;UrN2qg{ckd7UwvP0?s+Lbg`NChGzpIs&@E|9f~9hpA8BHwFRw*F4@ZS2JE>`A9zwa|<;v87lT-d%xPNca&)&Fe&?S5(dg)6BgMuLXx&Ctpd zjkk{uSYQC*_uoxbrf+JmdQ!zU=pI@s<4=9A_THTcWD^&muq5AEF-1Nk2^W{0>M7m> z^eGgz63jpevKY^(zet8;Nm92EL1uWmzumaiBi}sPvlB%hO3|_u*qY1@aIf`SZxbzf z6rQQCqPua7*Kt4(%Jmi2&q9xLs+b|#IJU45I3fh%qG7$wwCe3v;QR#9j7_BKVZrKf zk!dDgx$C=9!f!#*9ivtGf(cn87(6`l)Wotd#s)vxyL5?I~o0SftU9Q4KGz zU3`#fJS@i9Y+NovDkR*@DyaBO`c`GROj1|cUDiUdvM^^g4l-6vb&cp#cB)gt%Z7G8 zy($U?(Y321TJo!Y{nsXo_y5;q{b;a)!+Rk05=6~)-u=5;$TADaL23RqF1;=QS;F^y zemk{R*C3@I1J^p3eou%?8V&8KbVhn-?mlu*)G~v^SQUl0 zU9b7-NL(jvfqb;?ZU4rMW&3@9} z>%k-#v|u0W^;*s;sy}*MnLRBO<{k1c`58h0r+#CsZ#%iJPJ{OWx7yrO=BUitkTefG z8JrxGJNOOYEY_0l1-#%8N;}O+usaR~0iVF~zWJuBlwbfwcgaHDN6D(fce1T(JZVCY zlvWa_-S5Sj@vNJTQr-|bM}5Qv4*l+)boxaaXN|9yi|2kwLzwRaefTF`TCNwOs^RJY z4J^(`HtgL-O%>3nPw5juxrDV7wjD=))!H|w*)@$r{+32-1_knscfIY*IX$Y#rjTn9 z8N$RmzV*#V4H*du7xi};oAf)h_p z{Dp-WJvFVY_lW=2SlKvQ$%<#);%mB@hyG@j0a9N;GfYc-ot-F-ExjDdwDMo+MmlXc~qiB!vFH5etNl@wLp$zgp>F<+F!stgG>uubq9 zmkNwW9viB8ED506LfX2_fYI;`9u`Cf_)vbAf&b+{b3FrBhms_X$M)mWWMb?hDptAj z@yw~{D4AvcRxKL06tm?muptVO!gP&I$|#+!yI)m|K(sn{yXa=U$vuz=4Si4yvtF#D z*7+lEDwM2^FdNTNZWK6gi5(^8x@)~W|5pnxlEw2|O=x_?L30fh(Y6os#7S~L@@+P4 z^J*F$qv;ryvuRqYOtidXyg<88NobPSF%q^DCOHd+Rq2e|ov6jknsyiEj!DY1Yg*0q zjtqlAz~$aPe60?Qz;c2ZsE@gKO#6gu%J30o!2zmG$kT#+?AIEloon?L`ym!_n~#0M zsJMB&N}c?Hit~zr&jfO0HbwgbZ;ZvYkNW7dE{Cw5VG ztFo+JQ)NHQjSw^_8sI9ten3SD%cWUz9p=s7wcCPxat@UI3cBq(yawob5%R$I%x`wW z&|bs&Kp+if$M<+-jj^u~dBZRj>Lz`@v|9k9hOG*0)wId+=#5!x7w9aYm%{p|0-&P> zT>C^eMl*xhJ2w~J8Y<4yFYE;QL*Y|csk{?Z!t6rhq%@Y_iNwuEGTjbx9Fo|?o(WRF zo)(lL?sm``aYR0#qv_2z()q%nug2qz%V&pkw>a|&6%ePQ_<_tH5$0(8X$DFChQ=T%2vroqnEIcv%9A;*G zw1(re!slFy-;!FR*MeS}(v11Z2!J-@&c|_h^ptnIOaHOOYDxv(9NB`p`{3ymlGvG> zrEQ^-E5XE5EzII{Cx#02n*J1L!F;Eibq1(t{EK5A+pxgHe4-l^`FGQsM^cy)Hv!e! z@qpGtQXkw^S*6HXn(roHiF&5)fjju0>A>Izy6Z+!OTzPo7* z3Zq2x8b4bt^3>-9eqiS;X9-ES5Fu@>BGX$*1tujTBNv}-_C#AR^F;bBd8!_Fm(p6L zS1g*I^bO+xoXIHtu#Sv+Lc35}lPUomTHZF9L|P58~v>hDw(exnk? zUf+^8(tdtOS|=e)+-E@y@p)^!Z)?u`5=AH|e-PA}q3`vwQJSsH!IRg=fLO@1g_c{= zti6Eq!=;Kjwl{U6IF^-Pzp$< z5Ym75Fn10EEXW#AIO{9%z!VE##b=sWB-sa$#l^5E4a($&FhtNu@mVv(RfH)%YMrMt(r8!p<_H`pZ3_52QAmF#vcSKYUW1e6qFgTm8}ll<~W{1o1_EvQ-h^2ea3T~@FNKe#tClFcw#_Vtm%Neoj0&jHHy{A(pfoT#=m>uq0 zu7bx)19R|3k(d%Vpmm^YSnY56Q$Dr3Tf*odm8 zrxf3}yUOXD6CEoC=*1PH9eoa=WUcDq;FK9hvLgk?ny_-5uWqaL6=&>uEH+(J zP+Oaa4ck`0-|S$>#-l$`A?s!EvK@*fT6=jjm-F{L_?E|zp|rrRdv9Zf6fh3{Yy^{5Lc*qeb7H18>7`Xf zwpuhuP(7>7Tm9pOnZv6QOg(sln9}#~u80>Vvi3GQ-pQ{YR1W2-|jq}w^f0OqIQeegU(uQ--YmY_1| z4GyhmcMGdPaNud<{=Bt^GBC*e@pyG1921yex;Wl%d&5OncJvIJk$FF!6$Z$umAvQJ zL)ka2x^Po8bkZZa-Qhr<(X-&O{C1Qa(LI#$7 zv0F}K8_WUfQsemw90D$~fjHT^J9|n6dP@g^z4(Xrcvy$qgtH3V?cAK(ipAjWo}mtf zK2Up`Qa7YquCsGafrG705v0h43|fpLLyoI%@~=V{%5Kv8qY-;kPEc=waM}uSD}#_x z$WZ^86Gx~Zga~Ov^78o?*ZKo?#gSj9moGW;pOOJ*LA?G2yT+JbANbd<{gQ_MA(sPV zAUc*Gr1X`mzalPa=_|yYKM{YB)c*{+q^Yk!;49F-@xlL$zof0N@Gt%r|C10#5W!c5 zeyyNO8vDvn1;Wr@5&KDM|1+53C-#pl&HX3%??~={Hg`#L|B%br0}$2!i!c9a?&ln@ zk=~c~uW0YFb;2zk800sEgkO=ep^uM*OU>S8uPI-td zw=zPF3<(QyfqzjWyRHV7gviUvsHt;-Wo5cRIInrgyk}kM-CHq8$XNY=j)R`Pk}G8s z&AIZy{__mhTGN6_=XAaU@F7&6*3jkGP8CK~t#`6()ZJt*dfZ8BDo|I>Gh9cZPBnq_3AWSU$>ia=S27gNh zHGXFOM*#j_1M^RfrzXEqBDqpBvU+B0X>4w20sD>qt-sYbeg<=}Gc!}IX-#Ym#obUev|LHTQy`i;@71aI@-+z?Sudx>R{iN0H z3}N=BP`d|zMnN87kIUG~(B3{@OUHq9nDSh9+?=u{`U_x=0#HgdE`^VrKp}+^%RiOm z7fTW2Q_NrC4GjiT-I};ibNTdNXRIj=2mt}lu%PcekC|(4i=I>jZM7a31Z~MH?^}pjZU9T7JtiWy zrJeg@{pi3=zCc+!QK78N(j6v2FsDV+(SrZK0;Qc4uuS&{-^0WU7n+%@?qq&Wj4KQJ z@WfmxbXAC9emtr>D}Rz?0zUzh$>ore^|2JxSvdCy0>)cgM7F-y5VZjb1~3cwq@`}-Tpj<&6wLG_nzV{Y?#CPeiCSn zY@A;mK!G{pGAsdi^r>beiH@e;>SMO%F43*J! zTE`6m1_dv;(NORP`_r;Gjq>fP%jMmK<7vx=Yc@g(Jve$O%7VPfLq{TKO7K30mGVLC z!(G9|EhV0l9IDJ{hbDX^PH$M$aPV2XE(n-!=h7J)a(jJC;qQ$JD>{c~(=4a6U6e6) z&R6*?zpYD=s;}N93sQ16c^HI=b5AkNwp@|f;1wT1JB5jq&jE46;08fVQT}Xd3*3D1 zb4GaSE}Jl|eiI<^h)!uW@}^|*ft%w1zjm*{Z6OM!`dGfm@AekFjDxDEPXdY5dxK&< zF|49@UZCIqOn8Go&EMMLnJ4s2?h9oi6wZ3gI~Bfti~@SdoF4j=(kVTYye3S!j)6RQ zBV;!lD(&4R-BM;Dwi=YAURGP{;h$PNo9@=Hx=or$>QR$BBkI96=jr)&c8TQa$BjIE z>sFeOtWurHpiOcLECD``)ENL*v6fex`FZM$*ja>(mP5l-T(4Loh>frVFp3?y-!#5qVT}7zHEER)8WHt;J8wZP8XTnO)9n?Qqk1HwO zj-(DHVE5}s#nnx*T{sVdFyJ=l0>E{VJMQRoGx45lu~9@XTL)&(Nu2x-6MV2||JacWk4x$GS_4;FHxorDS_qE=YDQ+fDkVUxo zRY-GUL664T_lgAg2II_hG;)N6WkLi6-q_3Gc9ULMcg5H;#Z05-%nb0Y7+PxwhMEhl znTN2hEAAic-YdeByl3hv!;bzqJ1>tZ`qZU(;mL}=L;AG3TQWx=#le|J`e*Opk;Zo* z>Y_8+c@i`B`%uV>QV66>I|ts=)>)>ZGKmAFX!IL#Msw8ukRMF)_8lZCf)a?9a@_b#?+C@<^J{v z`(nlZ{YU?qCyuSB3~wZz)r@)v57?2o_^wJ9`KWFuB4{#s{C#c{ z(5MY;p-k|G=)HKL7(q_K18GO}7$**yp@fHyy`&wCYichliPT^0O*s>nBWLSSx#l6s zr)UtAVW8xvN?3ZYTIQ;=FFnec;GB)O0S}>nO40o4Rav2tJ7n)ybg#g8h%%4(S*XG> z>EPOX>!_w&IlW~P+DFwl?pSFg;(=l|Yw5xnlLxluNl&%?8*wmd%K1qAT*|&~*I0z8 z%Su7ww?_SC>cer&-c>{NNz5A=?L?OYIlJsxa!&L*e6(9U4SJ5i6i=F2Mu`fGX&}W6 z-8M^N-#nt$N;@|mc5Y5<&%QKkzJP0HIs2-2=mqHBYg2^qDY+Mi#=CE8l>x{3zVGlJ zM&i)J>t=*h8OBb4;@}+5c&Bf!s!@ZL_%s>yfREn7fT%)7)g%Z__$pwbf>lL@b`X@i z9X~&n2Bx*`BFlcIA`+(ae-D$;J%xT4Ow=P%P z;{{$l*M0p$XuR>rk;UrN2qg{ckd7UwvP0?s+Lbg`NChGzpIs&@E|9f~9hpA8BHwFRw*F4@ZS2JE>`A9zwa|<;v87lT-d%xPNca&)&Fe&?S5(dg)6BgMuLXx&Ctpd zjkk{uSYQC*_uoxbrf+JmdQ!zU=pI@s<4=9A_THTcWD^&muq5AEF-1Nk2^W{0>M7m> z^eGgz63jpevKY^(zet8;Nm92EL1uWmzumaiBi}sPvlB%hO3|_u*qY1@aIf`SZxbzf z6rQQCqPua7*Kt4(%Jmi2&q9xLs+b|#IJU45I3fh%qG7$wwCe3v;QR#9j7_BKVZrKf zk!dDgx$C=9!f!#*9ivtGf(cn87(6`l)Wotd#s)vxyL5?I~o0SftU9Q4KGz zU3`#fJS@i9Y+NovDkR*@DyaBO`c`GROj1|cUDiUdvM^^g4l-6vb&cp#cB)gt%Z7G8 zy($U?(Y321TJo!Y{nsXo_y5;q{b;a)!+Rk05=6~)-u=5;$TADaL23RqF1;=QS;F^y zemk{R*C3@I1J^p3eou%?8V&8KbVhn-?mlu*)G~v^SQUl0 zU9b7-NL(jvfqb;?ZU4rMW&3@9} z>%k-#v|u0W^;*s;sy}*MnLRBO<{k1c`58h0r+#CsZ#%iJPJ{OWx7yrO=BUitkTefG z8JrxGJNOOYEY_0l1-#%8N;}O+usaR~0iVF~zWJuBlwbfwcgaHDN6D(fce1T(JZVCY zlvWa_-S5Sj@vNJTQr-|bM}5Qv4*l+)boxaaXN|9yi|2kwLzwRaefTF`TCNwOs^RJY z4J^(`HtgL-O%>3nPw5juxrDV7wjD=))!H|w*)@$r{+32-1_knscfIY*IX$Y#rjTn9 z8N$RmzV*#V4H*du7xi};oAf)h_p z{Dp-WJvFVY_lW=2SlKvQ$%<#);%mB@hyG@j0a9N;GfYc-ot-F-ExjDdwDMo+MmlXc~qiB!vFH5etNl@wLp$zgp>F<+F!stgG>uubq9 zmkNwW9viB8ED506LfX2_fYI;`9u`Cf_)vbAf&b+{b3FrBhms_X$M)mWWMb?hDptAj z@yw~{D4AvcRxKL06tm?muptVO!gP&I$|#+!yI)m|K(sn{yXa=U$vuz=4Si4yvtF#D z*7+lEDwM2^FdNTNZWK6gi5(^8x@)~W|5pnxlEw2|O=x_?L30fh(Y6os#7S~L@@+P4 z^J*F$qv;ryvuRqYOtidXyg<88NobPSF%q^DCOHd+Rq2e|ov6jknsyiEj!DY1Yg*0q zjtqlAz~$aPe60?Qz;c2ZsE@gKO#6gu%J30o!2zmG$kT#+?AIEloon?L`ym!_n~#0M zsJMB&N}c?Hit~zr&jfO0HbwgbZ;ZvYkNW7dE{Cw5VG ztFo+JQ)NHQjSw^_8sI9ten3SD%cWUz9p=s7wcCPxat@UI3cBq(yawob5%R$I%x`wW z&|bs&Kp+if$M<+-jj^u~dBZRj>Lz`@v|9k9hOG*0)wId+=#5!x7w9aYm%{p|0-&P> zT>C^eMl*xhJ2w~J8Y<4yFYE;QL*Y|csk{?Z!t6rhq%@Y_iNwuEGTjbx9Fo|?o(WRF zo)(lL?sm``aYR0#qv_2z()q%nug2qz%V&pkw>a|&6%ePQ_<_tH5$0(8X$DFChQ=T%2vroqnEIcv%9A;*G zw1(re!slFy-;!FR*MeS}(v11Z2!J-@&c|_h^ptnIOaHOOYDxv(9NB`p`{3ymlGvG> zrEQ^-E5XE5EzII{Cx#02n*J1L!F;Eibq1(t{EK5A+pxgHe4-l^`FGQsM^cy)Hv!e! z@qpGtQXkw^S*6HXn(roHiF&5)fjju0>A>Izy6Z+!OTzPo7* z3Zq2x8b4bt^3>-9eqiS;X9-ES5Fu@>BGX$*1tujTBNv}-_C#AR^F;bBd8!_Fm(p6L zS1g*I^bO+xoXIHtu#Sv+Lc35}lPUomTHZF9L|P58~v>hDw(exnk? zUf+^8(tdtOS|=e)+-E@y@p)^!Z)?u`5=AH|e-PA}q3`vwQJSsH!IRg=fLO@1g_c{= zti6Eq!=;Kjwl{U6IF^-Pzp$< z5Ym75Fn10EEXW#AIO{9%z!VE##b=sWB-sa$#l^5E4a($&FhtNu@mVv(RfH)%YMrMt(r8!p<_H`pZ3_52QAmF#vcSKYUW1e6qFgTm8}ll<~W{1o1_EvQ-h^2ea3T~@FNKe#tClFcw#_Vtm%Neoj0&jHHy{A(pfoT#=m>uq0 zu7bx)19R|3k(d%Vpmm^YSnY56Q$Dr3Tf*odm8 zrxf3}yUOXD6CEoC=*1PH9eoa=WUcDq;FK9hvLgk?ny_-5uWqaL6=&>uEH+(J zP+Oaa4ck`0-|S$>#-l$`A?s!EvK@*fT6=jjm-F{L_?E|zp|rrRdv9Zf6fh3{Yy^{5Lc*qeb7H18>7`Xf zwpuhuP(7>7Tm9pOnZv6QOg(sln9}#~u80>Vvi3GQ-pQ{YR1W2-|jq}w^f0OqIQeegU(uQ--YmY_1| z4GyhmcMGdPaNud<{=Bt^GBC*e@pyG1921yex;Wl%d&5OncJvIJk$FF!6$Z$umAvQJ zL)ka2x^Po8bkZZa-Qhr<(X-&O{C1Qa(LI#$7 zv0F}K8_WUfQsemw90D$~fjHT^J9|n6dP@g^z4(Xrcvy$qgtH3V?cAK(ipAjWo}mtf zK2Up`Qa7YquCsGafrG705v0h43|fpLLyoI%@~=V{%5Kv8qY-;kPEc=waM}uSD}#_x z$WZ^86Gx~Zga~Ov^78o?*ZKo?#gSj9moGW;pOOJ*LA?G2yT+JbANbd<{gQ_MA(sPV zAUc*Gr1X`mzalPa=_|yYKM{YB)c*{+q^Yk!;49F-@xlL$zof0N@Gt%r|C10#5W!c5 zeyyNO8vDvn1;Wr@5&KDM|1+53C-#pl&HX3%??~={Hg`#L|B%br0}$2!i!c9a?&ln@ zk=~c~uW0YFb;2zk800sEgkO=ep^uM*O`b6O*M#64A9qgpu3gD#T4BW8zw} zW*uCG+>x$IA?ZPTXsB`|rp4sr!>6y^ zz=+bb(vmSW(U&y?)VOFEflKWRHLjg%C@m91eLYJnC`wOxZ|8E=}=b9kX@i`*&YhXgAZFA;FA>8(1Mc;CdZ!Q-j;g zf$A5*fq-9K{4gKG|F#DBlZLnJ50p5zm7LLjE!S{aBH>g@{sDJKS9|vvW=H#mL zDFaX!v{OJps@Z@My*!(G-jwga5-DHy{DolJwqIk&DKW5H zjp{@WkH=TEYmld!S}Bw)*uvWK2RgFAIrhK<(+VJy*tp@hhnn?df+4Nf5-Ypi&pzr` zb;QUs&!+zNV%l8AT=Eb5dbU{Ufc)ENOi}j!Wdo*>GkM|_B(GaV@x_k87#4^LhZi#2 zsoTLSOBBji_A;tO>BJ$Qi`Y}x2i1>{p$zpl?59i{ZmgziQN_IBv z7aoX8d2PZYrX+B+_U_e^F^RgGlg6Ag!>kYHN=xfyk*r=tIjp?<)jUlkZt5Ml>3t*L z5e#u)*Smf|{FlVrCn4g4yOhG~G3?I7;rg3ld-BvlL$uTiXBb|T=BK5h5v1F!@NZ{b z{^zr92&~{{=5JVI0tR&1&bM3Ner`PiM`ooMJVlLn;KOr*6lF_f$Vtgx`bgJ{!B){clNCp5X{sm6wB!(9cTmSC|F`N-OV zLEPK4$$6f=b@3N{dwV0VoG7wgyH)iEZ*G2fZsBA?=1Nq&P|ERWY`jhW;Q4!sE4*pZ zw9gG#8ZDw~E6hhxPVkUEIhJuD9l7j7@`#XvTHf0Xbrie8vl#=XkNo4U-I>ygdW|wM zF+t(x{>$;=I&0DoiS`9JMvZ)BZwky}tV0G*T;zTDrr3qs*h7g>;&(g{ZdVX+dElvN z&x6c_#1lfoXT&0T)cjPU-6{mc%Q1FJbuGcUxF>gtkBk~WJeH_lH7P@{44lnG??`rdmW)}xnj(dU$=&w!#BXM-CF=(GR(VkR(E;) zNbW>*NOF~Hr&!=7KV~?k&)PobV}T< zPL4JE%JR;>Cp14dpXUv9WnZ@|MPv+1BCGMT(ZWDJR;fq_RT-7wejK$7td zk*0YLl24Ww$SW>+i8+4U;R!=WLnCK0@IGN)yYJ4F>($%yS%Y^CE-*i^!*=bfY=iPG zbX~%6&s~kGW?`@&EI78TDEvN+Nfq?opS9Z&9r4_)Uu%k@Fj~c`WjEa{IyH8B_Z(-C z)2g7`365}|WDN-Rz)iy>3({PA=stUBwr1Lc)D8NDNgEPtOm1Wbr?LEpxK8;af!(5^ zZ58{GmxM$qDhh~-^C?1s%2%Xk8XcgvVu(GRYkDLj_I8CrlZf@GxHz0dfP5y$;{8re zh>kg|4_~fjxl4I{f7OahQ#`v)`ml{|!uZXYJlUivT`8T2!JIeNrXmF2Pam5N0}QjZiEYVD@owwk+=qTya#$ zv&x!i$J=#U-V&=lEGR9Z2g|{Mt%zm^cdx_! zAkQ0!GB^3ri)8l#(LU6&`XYD8?CUpUAxrC%spMUM^pL9rl3Qq!g%xWgC00TBwgxd` zX09#Wn$=l79(WW?m#Oa>jRIRlEFJhItgE(h?}sUC=7SP!x;O28rbF+V@~sZr2=epR6EP;z`I8A3^jKGPRA|N9`R^BTpt`0R?S^L^d?96h4c$O zcR2X^@rlDKIJ-8!UOv(pe_TQG>1SnwAO%X7$qT3OKmAl8Jn)d|#_Qz0M^5ULkS0-u z_4a}!-AH!WQ-c=nU*DalV4orK$dHN9EQ{{)a+I;@&^TDpx09)pT;@2KL4Yp|rNv8C zcXPBfn5N4`^ckP#G+m+_?opS}4cAGvn?D-(-c~ZDS0bLV0(Y>6=anXz25mDN&p6A- z86C{qD^qHCFg=k{O)5$EhIw4Wc)4;|$uv%0g&Pu+t|X86ECk@w>1Ui@$(srw<}dv{ zG=|N5gU#aCRrb5GR(NUyL|zM{bZh`M3ud%vq3?A7I;INa&Kmxbt7!h`8pitjVcq<3 zUTy)Fflwoqv?0nIk#B{SMi{pvj?bf{k!_Z8f5CcTW0FW2$@bQc62;Dv9#|*KasV^q z@;vLXk!*{s%)D_is=cH4nNb(ArP;LIq#e=PI$|U(*=&q7hSDO(Qn_d|D(Yhi461qc z_8-H#_GoXd7vMA+xXqBXbX@e`C;fm5AZ#=ck*`w`}t6ZCAfUP?`5OLi`irBM<*Rh|0vbLC9^;zbNnb@leKY0jmEC zU%ocC72^(GPqn|zF?X_0Dzo2`oCqvnpSrfdO6^GcmORdagp+>RuAE~)D2QcPD2r&KEX|^{P VGbF<-2jkdhV{VrY;CX%GeJ5`%xZ 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 bindings = new LinkedHashMap<>(); @@ -89,6 +94,7 @@ public class LedgerBindingConfig { for (int i = 0; i < hashs.length; i++) { writeLine(builder, "#第 %s 个账本[%s]的配置;", i + 1, hashs[i].toBase58()); BindingConfig binding = getLedger(hashs[i]); + writeLedger(builder, hashs[i], binding); writeParticipant(builder, hashs[i], binding); writeDB(builder, hashs[i], binding); writeLine(builder); @@ -113,11 +119,14 @@ public class LedgerBindingConfig { // 参与方配置; String partiAddressKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_ADDRESS); String partiPkPathKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_PK_PATH); + String partiNameKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_NAME); String partiPKKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_PK); String partiPwdKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_PASSWORD); writeLine(builder, "#账本的当前共识参与方的节点地址 Address;"); writeLine(builder, "%s=%s", partiAddressKey, stringOf(binding.getParticipant().getAddress())); + writeLine(builder, "#账本的当前共识参与方的节点名称 NodeName;"); + writeLine(builder, "%s=%s", partiNameKey, stringOf(binding.getParticipant().getName())); writeLine(builder, "#账本的当前共识参与方的私钥文件的保存路径;"); writeLine(builder, "%s=%s", partiPkPathKey, stringOf(binding.getParticipant().getPkPath())); writeLine(builder, "#账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性;"); @@ -140,6 +149,16 @@ public class LedgerBindingConfig { writeLine(builder); } + private void writeLedger(StringBuilder builder, HashDigest ledgerHash, BindingConfig binding) { + String ledgerPrefix = String.join(ATTR_SEPERATOR, BINDING_PREFIX, ledgerHash.toBase58()); + // 账本相关信息配置; + String ledgerNameKey = String.join(ATTR_SEPERATOR, ledgerPrefix, LEDGER_NAME); + + writeLine(builder, "#账本的名称;"); + writeLine(builder, "%s=%s", ledgerNameKey, stringOf(binding.getLedgerName())); + writeLine(builder); + } + private static String stringOf(Object obj) { if (obj == null) { return ""; @@ -219,11 +238,12 @@ public class LedgerBindingConfig { // 参与方配置; String partiAddrKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_ADDRESS); String partiPkPathKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_PK_PATH); + String partiNameKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_NAME); String partiPKKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_PK); String partiPwdKey = String.join(ATTR_SEPERATOR, ledgerPrefix, PARTI_PASSWORD); - String strPartiAddr = getProperty(props, partiAddrKey, true); - binding.participant.address = strPartiAddr; + binding.participant.address = getProperty(props, partiAddrKey, true); + binding.participant.name = getProperty(props, partiNameKey, true); binding.participant.pkPath = getProperty(props, partiPkPathKey, false); binding.participant.pk = getProperty(props, partiPKKey, false); binding.participant.password = getProperty(props, partiPwdKey, false); @@ -247,6 +267,10 @@ public class LedgerBindingConfig { String.format("No db connection config of participant of ledger binding[%s]!", ledgerHash)); } + // 设置账本名称 + String ledgerNameKey = String.join(ATTR_SEPERATOR, ledgerPrefix, LEDGER_NAME); + binding.ledgerName = getProperty(props, ledgerNameKey, true); + return binding; } @@ -296,6 +320,9 @@ public class LedgerBindingConfig { public static class BindingConfig { + private String ledgerName; + + // 账本名字 private ParticipantBindingConfig participant = new ParticipantBindingConfig(); private DBConnectionConfig dbConnection = new DBConnectionConfig(); @@ -308,18 +335,35 @@ public class LedgerBindingConfig { return dbConnection; } + public void setLedgerName(String ledgerName) { + this.ledgerName = ledgerName; + } + + public String getLedgerName() { + return ledgerName; + } } public static class ParticipantBindingConfig { private String address; + private String name; + private String pkPath; private String pk; private String password; + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public String getAddress() { return address; } diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index 8077b6dc..4617076a 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -2,6 +2,8 @@ package com.jd.blockchain.tools.initializer; import java.io.File; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -52,6 +54,8 @@ public class LedgerInitCommand { private static final Prompter ANSWER_PROMPTER = new PresetAnswerPrompter("Y"); + private static final Prompter LOG_PROMPTER = new LogPrompter(); + /** * 入口; * @@ -65,7 +69,7 @@ public class LedgerInitCommand { try { if (argSet.hasOption(MONITOR_OPT)) { - prompter = ANSWER_PROMPTER; + prompter = LOG_PROMPTER; } ArgEntry localArg = argSet.getArg(LOCAL_ARG); @@ -178,7 +182,14 @@ public class LedgerInitCommand { // generate binding config; BindingConfig bindingConf = new BindingConfig(); + + // 设置账本名称 + bindingConf.setLedgerName(ledgerInitProperties.getLedgerName()); + bindingConf.getParticipant().setAddress(ledgerInitProperties.getConsensusParticipant(currId).getAddress()); + // 设置参与方名称 + bindingConf.getParticipant().setName(ledgerInitProperties.getConsensusParticipant(currId).getName()); + String encodedPrivKey = KeyGenCommand.encodePrivKey(privKey, base58Pwd); bindingConf.getParticipant().setPk(encodedPrivKey); bindingConf.getParticipant().setPassword(base58Pwd); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index fa29ba71..9030eb77 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -25,6 +25,9 @@ public class LedgerInitProperties { // 账本种子; public static final String LEDGER_SEED = "ledger.seed"; + // 账本名称 + public static final String LEDGER_NAME = "ledger.name"; + // 声明的账本建立时间; public static final String CREATED_TIME = "created-time"; // 创建时间的格式; @@ -61,6 +64,8 @@ public class LedgerInitProperties { private byte[] ledgerSeed; + private String ledgerName; + private List consensusParticipants = new ArrayList<>(); private String consensusProvider; @@ -75,6 +80,10 @@ public class LedgerInitProperties { return ledgerSeed.clone(); } + public String getLedgerName() { + return ledgerName; + } + public long getCreatedTime() { return createdTime; } @@ -159,6 +168,10 @@ public class LedgerInitProperties { byte[] ledgerSeed = HexUtils.decode(hexLedgerSeed); LedgerInitProperties initProps = new LedgerInitProperties(ledgerSeed); + // 账本名称 + String ledgerName = PropertiesUtils.getRequiredProperty(props, LEDGER_NAME); + initProps.ledgerName = ledgerName; + // 创建时间; String strCreatedTime = PropertiesUtils.getRequiredProperty(props, CREATED_TIME); try { diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LogPrompter.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LogPrompter.java new file mode 100644 index 00000000..ccc6a850 --- /dev/null +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LogPrompter.java @@ -0,0 +1,52 @@ +package com.jd.blockchain.tools.initializer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogPrompter implements Prompter { + + private static final Logger LOGGER = LoggerFactory.getLogger(LogPrompter.class); + + private static final String ANSWER_DEFAULT = "Yes"; + + private boolean debug = true; + + @Override + public void info(String format, Object... args) { + LOGGER.info(format, args); + } + + @Override + public void error(String format, Object... args) { + LOGGER.error(format, args); + } + + @Override + public void error(Exception error, String format, Object... args) { + if (debug) { + error.printStackTrace(); + LOGGER.error(error.toString()); + } + } + + @Override + public String confirm(String format, Object... args) { + return confirm("", format, args); + } + + @Override + public String confirm(String tag, String format, Object... args) { + String msg = String.format(format, args); + LOGGER.info(msg); + return ANSWER_DEFAULT; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + +} \ No newline at end of file diff --git a/source/tools/tools-initializer/src/main/resources/application.properties b/source/tools/tools-initializer/src/main/resources/application.properties index 2090d209..16cdc6bf 100644 --- a/source/tools/tools-initializer/src/main/resources/application.properties +++ b/source/tools/tools-initializer/src/main/resources/application.properties @@ -1,17 +1,13 @@ server.address=127.0.0.1 server.port=8900 -#server.ssl.key-store=classpath:mykeys.jks -#server.ssl.key-store-password=abc123 -#server.ssl.key-password=abc123 - server.tomcat.accesslog.enabled=true debug=false +logging.config=classpath:log4j2-init.xml #logging.file=logs/peer.log logging.level.com.jd.blockchain=DEBUG logging.level.org.org.springframework=DEBUG -spring.mvc.favicon.enabled=false - +spring.mvc.favicon.enabled=false \ No newline at end of file diff --git a/source/tools/tools-initializer/src/main/resources/banner2.txt b/source/tools/tools-initializer/src/main/resources/banner2.txt deleted file mode 100644 index f1c960f7..00000000 --- a/source/tools/tools-initializer/src/main/resources/banner2.txt +++ /dev/null @@ -1,12 +0,0 @@ - - 888888 8888888b. 888888b. 888 888 .d8888b. 888 d8b - "88b 888 "Y88b 888 "88b 888 888 d88P Y88b 888 Y8P - 888 888 888 888 .88P 888 888 888 888 888 - 888 888 888 8888888K. 888 .d88b. .d8888b 888 888 888 88888b. 8888b. 888 88888b. - 888 888 888 888 "Y88b 888 d88""88b d88P" 888 .88P 888 888 "88b "88b 888 888 "88b - 888 888 888 888 888 888 888 888 888 888888K 888 888 888 888 .d888888 888 888 888 - 88P 888 .d88P 888 d88P 888 Y88..88P Y88b. 888 "88b Y88b d88P 888 888 888 888 888 888 888 - 888 8888888P" 8888888P" 888 "Y88P" "Y8888P 888 888 "Y8888P" 888 888 "Y888888 888 888 888 - .d88P - .d88P" -888P" diff --git a/source/tools/tools-initializer/src/main/resources/banner3.txt b/source/tools/tools-initializer/src/main/resources/banner3.txt deleted file mode 100644 index a8e93d02..00000000 --- a/source/tools/tools-initializer/src/main/resources/banner3.txt +++ /dev/null @@ -1,12 +0,0 @@ - - $$$$$\ $$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ - \__$$ |$$ __$$\ $$ __$$\ $$ | $$ | $$ __$$\ $$ | \__| - $$ |$$ | $$ | $$ | $$ |$$ | $$$$$$\ $$$$$$$\ $$ | $$\ $$ / \__|$$$$$$$\ $$$$$$\ $$\ $$$$$$$\ - $$ |$$ | $$ | $$$$$$$\ |$$ |$$ __$$\ $$ _____|$$ | $$ | $$ | $$ __$$\ \____$$\ $$ |$$ __$$\ -$$\ $$ |$$ | $$ | $$ __$$\ $$ |$$ / $$ |$$ / $$$$$$ / $$ | $$ | $$ | $$$$$$$ |$$ |$$ | $$ | -$$ | $$ |$$ | $$ | $$ | $$ |$$ |$$ | $$ |$$ | $$ _$$< $$ | $$\ $$ | $$ |$$ __$$ |$$ |$$ | $$ | -\$$$$$$ |$$$$$$$ | $$$$$$$ |$$ |\$$$$$$ |\$$$$$$$\ $$ | \$$\ \$$$$$$ |$$ | $$ |\$$$$$$$ |$$ |$$ | $$ | - \______/ \_______/ \_______/ \__| \______/ \_______|\__| \__| \______/ \__| \__| \_______|\__|\__| \__| - - - diff --git a/source/tools/tools-initializer/src/main/resources/log4j2-init.xml b/source/tools/tools-initializer/src/main/resources/log4j2-init.xml new file mode 100644 index 00000000..6ef49baf --- /dev/null +++ b/source/tools/tools-initializer/src/main/resources/log4j2-init.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f36ef575d8b28a1fdf3dc9d837fa909fcd95a6d1 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Thu, 8 Aug 2019 15:21:00 +0800 Subject: [PATCH 033/124] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E5=90=88=E7=BA=A6=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl | Bin 4077 -> 0 bytes .../LdeNf7W5SZ1Xh1KXoQcbPEdPDM21nUsEp8zFN_0.mdl | Bin 4077 -> 0 bytes .../LdeNgxFpbzqTH3ZouX5vPv7A9M7pGWUjGJAan_0.mdl | Bin 4077 -> 0 bytes .../LdeNhGKHXohv3Bme6YgqPkETVQwDetmNFcdPK_0.mdl | Bin 4077 -> 0 bytes .../LdeNhpKuGE7bsrCsARipmdhFD7AGFFVkvzT6e_0.mdl | Bin 4077 -> 0 bytes .../LdeNi3yqaJ5G3N2rGLZbXLXJ1XF6ajiyXbdkR_0.mdl | Bin 4077 -> 0 bytes .../LdeNigyUzWXAySKzQH8W74sjXcQykK6Ss1eEH_0.mdl | Bin 7118 -> 0 bytes .../LdeNjhemDjb3aSg3SpMRzqUrhFZDygUNsKA3o_0.mdl | Bin 7118 -> 0 bytes .../LdeNkJg78L7L1KYv6Cqicqc5SmbCD9Y4bkpez_0.mdl | Bin 4077 -> 0 bytes .../LdeNmcdMwTeQ73C6MsKbMxhFQ2XCh6bPz7p21_0.mdl | Bin 4077 -> 0 bytes .../LdeNmnQmbpZS2QSXnJ73LeGDJmwkhDhyFsVjJ_0.mdl | Bin 7118 -> 0 bytes .../LdeNmpZN6vXC9i6pyhPpRxmBf7iiaPj7bhvN2_0.mdl | Bin 4077 -> 0 bytes .../LdeNmzy486oNRSzhS2z7QRVvVswxCyaz65wys_0.mdl | Bin 7118 -> 0 bytes .../LdeNnDgXmyep22ZyabbdtdEzCjxFiM9nLDiby_0.mdl | Bin 4077 -> 0 bytes .../LdeNnYU8txmjeRhi9rgiZSwAQHW632Q3rQZue_0.mdl | Bin 4077 -> 0 bytes .../LdeNoENuwCi3bVbhmoFG4w5mggmPsgGtpZMtF_0.mdl | Bin 4255 -> 0 bytes .../LdeNqAMP5kjnciodhTY6Pnf8o5qbBvFkEs25Q_0.mdl | Bin 4077 -> 0 bytes .../LdeNqr3pRJGZQFTUQgSZ6onhi7FVFDHkUXwd5_0.mdl | Bin 4077 -> 0 bytes .../LdeNrFwJSVujRK7RBizL46WC5cUtudARjfHvT_0.mdl | Bin 4077 -> 0 bytes .../LdeNroewGwCBmwFMgxUrEY4WgMq4GA54N82r5_0.mdl | Bin 4077 -> 0 bytes .../LdeNrpEqQFALAghHSLpjt2Zdx3k6SBqTCVsGA_0.mdl | Bin 4077 -> 0 bytes .../LdeNsH2ZedzAohTe6xRRZQvKJt4geSFfhmaoX_0.mdl | Bin 7118 -> 0 bytes .../LdeNstg544ncLvc9D94ezXmPFYUq4QoPVkEYL_0.mdl | Bin 4077 -> 0 bytes .../LdeNsxrjTsagkS53m7AWWKwnpvJ1YUGBGM3mC_0.mdl | Bin 7118 -> 0 bytes .../LdeNtFf3HuzSNV2oTyrdHW3XPtmKvHUWpBpyr_0.mdl | Bin 4077 -> 0 bytes .../LdeNtM6R2N5wMgUQw1jDyUZ8gHbaVDFgyMAVN_0.mdl | Bin 4255 -> 0 bytes .../LdeNtY59RJyvx45bj6fjkDocBGB9Nc7Sf4ymf_0.mdl | Bin 4077 -> 0 bytes .../LdeNu29NacEbeEKCXpDAu1BcU1R54gYVKpJBX_0.mdl | Bin 4077 -> 0 bytes .../LdeNuRZEjx2UN1nmcmGwdx1AqFc3uFaHrKtjX_0.mdl | Bin 7118 -> 0 bytes .../LdeNv7SiL1nujKFuns1cGmWiaKD49jryjcYeH_0.mdl | Bin 7118 -> 0 bytes .../LdeNveXpq25eUxab6N21rJgg4cw7BXvKfm2LV_0.mdl | Bin 4077 -> 0 bytes .../LdeNvfeFwgRQxftBuUpekDCZJMLFwbWxXpzri_0.mdl | Bin 7118 -> 0 bytes .../LdeNvpJH7no46KQpnyFyNnR7fxT3hWsUrYdgz_0.mdl | Bin 7118 -> 0 bytes .../LdeNxXeWNfmRAXJX5xPSpgq6JArNrxsPDLgX3_0.mdl | Bin 7118 -> 0 bytes .../LdeNxd6yNub5qWhB49eBPGKnwNf5smuhSShDb_0.mdl | Bin 4077 -> 0 bytes .../LdeNxzTy6YegizyRKxA1EH87wYdHHBDMLqwSZ_0.mdl | Bin 7167 -> 0 bytes .../LdeNy5TGpQhi4pz8jNDX3wz3VABLPuGbcvLin_0.mdl | Bin 7118 -> 0 bytes .../LdeNz6SbUf9WZUFnkcZK3o2qYcTcLPvikMLJz_0.mdl | Bin 4077 -> 0 bytes .../LdeNzBjCSBy9uQDyYqgiUkpm4zpe1Fs9gEhXK_0.mdl | Bin 4077 -> 0 bytes .../LdeNzH3wcSNAJDX5RtaNKFxw6UQJMzdcRQUTp_0.mdl | Bin 4077 -> 0 bytes .../LdeNzJu2FYBNyZo9Ue757Chjj9vsxYFAnMHSS_0.mdl | Bin 4077 -> 0 bytes .../LdeP11pE4f8TWU5DpqJJVnojuxScK4yRceAiX_0.mdl | Bin 4077 -> 0 bytes .../LdeP1Vzbe3vbn8rjgoGN9FwRGyW6192PASjLS_0.mdl | Bin 4077 -> 0 bytes .../LdeP2WzejQqJdREjbbpjTsNno863XTvjCemvL_0.mdl | Bin 7118 -> 0 bytes .../LdeP2o8X3tnzLUV5kvmnT1rU6tL48Mn9yKnSp_0.mdl | Bin 7118 -> 0 bytes .../LdeP3pQGeyQhZJxsHhBu48kaNwYKow4HvdAcA_0.mdl | Bin 4077 -> 0 bytes .../LdeP3sKx6RrxZVGoYdJAGUrttNMNwWU3JC2NV_0.mdl | Bin 7118 -> 0 bytes 47 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 source/test/test-integration/runtime/LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNf7W5SZ1Xh1KXoQcbPEdPDM21nUsEp8zFN_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNgxFpbzqTH3ZouX5vPv7A9M7pGWUjGJAan_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNhGKHXohv3Bme6YgqPkETVQwDetmNFcdPK_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNhpKuGE7bsrCsARipmdhFD7AGFFVkvzT6e_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNi3yqaJ5G3N2rGLZbXLXJ1XF6ajiyXbdkR_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNigyUzWXAySKzQH8W74sjXcQykK6Ss1eEH_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNjhemDjb3aSg3SpMRzqUrhFZDygUNsKA3o_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNkJg78L7L1KYv6Cqicqc5SmbCD9Y4bkpez_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNmcdMwTeQ73C6MsKbMxhFQ2XCh6bPz7p21_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNmnQmbpZS2QSXnJ73LeGDJmwkhDhyFsVjJ_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNmpZN6vXC9i6pyhPpRxmBf7iiaPj7bhvN2_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNmzy486oNRSzhS2z7QRVvVswxCyaz65wys_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNnDgXmyep22ZyabbdtdEzCjxFiM9nLDiby_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNnYU8txmjeRhi9rgiZSwAQHW632Q3rQZue_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNoENuwCi3bVbhmoFG4w5mggmPsgGtpZMtF_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNqAMP5kjnciodhTY6Pnf8o5qbBvFkEs25Q_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNqr3pRJGZQFTUQgSZ6onhi7FVFDHkUXwd5_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNrFwJSVujRK7RBizL46WC5cUtudARjfHvT_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNroewGwCBmwFMgxUrEY4WgMq4GA54N82r5_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNrpEqQFALAghHSLpjt2Zdx3k6SBqTCVsGA_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNsH2ZedzAohTe6xRRZQvKJt4geSFfhmaoX_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNstg544ncLvc9D94ezXmPFYUq4QoPVkEYL_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNsxrjTsagkS53m7AWWKwnpvJ1YUGBGM3mC_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNtFf3HuzSNV2oTyrdHW3XPtmKvHUWpBpyr_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNtM6R2N5wMgUQw1jDyUZ8gHbaVDFgyMAVN_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNtY59RJyvx45bj6fjkDocBGB9Nc7Sf4ymf_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNu29NacEbeEKCXpDAu1BcU1R54gYVKpJBX_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNuRZEjx2UN1nmcmGwdx1AqFc3uFaHrKtjX_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNv7SiL1nujKFuns1cGmWiaKD49jryjcYeH_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNveXpq25eUxab6N21rJgg4cw7BXvKfm2LV_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNvfeFwgRQxftBuUpekDCZJMLFwbWxXpzri_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNvpJH7no46KQpnyFyNnR7fxT3hWsUrYdgz_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNxXeWNfmRAXJX5xPSpgq6JArNrxsPDLgX3_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNxd6yNub5qWhB49eBPGKnwNf5smuhSShDb_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNxzTy6YegizyRKxA1EH87wYdHHBDMLqwSZ_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNy5TGpQhi4pz8jNDX3wz3VABLPuGbcvLin_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNz6SbUf9WZUFnkcZK3o2qYcTcLPvikMLJz_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNzBjCSBy9uQDyYqgiUkpm4zpe1Fs9gEhXK_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNzH3wcSNAJDX5RtaNKFxw6UQJMzdcRQUTp_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeNzJu2FYBNyZo9Ue757Chjj9vsxYFAnMHSS_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeP11pE4f8TWU5DpqJJVnojuxScK4yRceAiX_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeP1Vzbe3vbn8rjgoGN9FwRGyW6192PASjLS_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeP2WzejQqJdREjbbpjTsNno863XTvjCemvL_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeP2o8X3tnzLUV5kvmnT1rU6tL48Mn9yKnSp_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeP3pQGeyQhZJxsHhBu48kaNwYKow4HvdAcA_0.mdl delete mode 100644 source/test/test-integration/runtime/LdeP3sKx6RrxZVGoYdJAGUrttNMNwWU3JC2NV_0.mdl diff --git a/source/test/test-integration/runtime/LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl b/source/test/test-integration/runtime/LdeNezHHwrWNFYWF5n6RrbdyCoSPWZUreDhvv_0.mdl deleted file mode 100644 index ddf9c66802c4fa96d6bad111a772ab2165168bfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4077 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} zW*uCG+>x$IA?ZPTXsB`|rp4sr!>6y^ zz=+bb(vmSW(U&y?)VOFEflKWRHLjg%C@m91eLYJnC`wOxZ|8E=}=b9kX@i`*&YhXgAZFA;FA>8(1Mc;CdZ!Q-j;g zf$A5*fq-9K{4gKG|F#DBlZLnJ50p5zm7LLjE!S{aBH>g@{sDJKS9|vvW=H#mL zDFaX!v{OJps@Z@My*!(G-jwga5-DHy{DolJwqIk&DKW5H zjp{@WkH=TEYmld!S}Bw)*uvWK2RgFAIrhK<(+VJy*tp@hhnn?df+4Nf5-Ypi&pzr` zb;QUs&!+zNV%l8AT=Eb5dbU{Ufc)ENOi}j!Wdo*>GkM|_B(GaV@x_k87#4^LhZi#2 zsoTLSOBBji_A;tO>BJ$Qi`Y}x2i1>{p$zpl?59i{ZmgziQN_IBv z7aoX8d2PZYrX+B+_U_e^F^RgGlg6Ag!>kYHN=xfyk*r=tIjp?<)jUlkZt5Ml>3t*L z5e#u)*Smf|{FlVrCn4g4yOhG~G3?I7;rg3ld-BvlL$uTiXBb|T=BK5h5v1F!@NZ{b z{^zr92&~{{=5JVI0tR&1&bM3Ner`PiM`ooMJVlLn;KOr*6lF_f$Vtgx`bgJ{!B){clNCp5X{sm6wB!(9cTmSC|F`N-OV zLEPK4$$6f=b@3N{dwV0VoG7wgyH)iEZ*G2fZsBA?=1Nq&P|ERWY`jhW;Q4!sE4*pZ zw9gG#8ZDw~E6hhxPVkUEIhJuD9l7j7@`#XvTHf0Xbrie8vl#=XkNo4U-I>ygdW|wM zF+t(x{>$;=I&0DoiS`9JMvZ)BZwky}tV0G*T;zTDrr3qs*h7g>;&(g{ZdVX+dElvN z&x6c_#1lfoXT&0T)cjPU-6{mc%Q1FJbuGcUxF>gtkBk~WJeH_lH7P@{44lnG??`rdmW)}xnj(dU$=&w!#BXM-CF=(GR(VkR(E;) zNbW>*NOF~Hr&!=7KV~?k&)PobV}T< zPL4JE%JR;>Cp14dpXUv9WnZ@|MPv+1BCGMT(ZWDJR;fq_RT-7wejK$7td zk*0YLl24Ww$SW>+i8+4U;R!=WLnCK0@IGN)yYJ4F>($%yS%Y^CE-*i^!*=bfY=iPG zbX~%6&s~kGW?`@&EI78TDEvN+Nfq?opS9Z&9r4_)Uu%k@Fj~c`WjEa{IyH8B_Z(-C z)2g7`365}|WDN-Rz)iy>3({PA=stUBwr1Lc)D8NDNgEPtOm1Wbr?LEpxK8;af!(5^ zZ58{GmxM$qDhh~-^C?1s%2%Xk8XcgvVu(GRYkDLj_I8CrlZf@GxHz0dfP5y$;{8re zh>kg|4_~fjxl4I{f7OahQ#`v)`ml{|!uZXYJlUivT`8T2!JIeNrXmF2Pam5N0}QjZiEYVD@owwk+=qTya#$ zv&x!i$J=#U-V&=lEGR9Z2g|{Mt%zm^cdx_! zAkQ0!GB^3ri)8l#(LU6&`XYD8?CUpUAxrC%spMUM^pL9rl3Qq!g%xWgC00TBwgxd` zX09#Wn$=l79(WW?m#Oa>jRIRlEFJhItgE(h?}sUC=7SP!x;O28rbF+V@~sZr2=epR6EP;z`I8A3^jKGPRA|N9`R^BTpt`0R?S^L^d?96h4c$O zcR2X^@rlDKIJ-8!UOv(pe_TQG>1SnwAO%X7$qT3OKmAl8Jn)d|#_Qz0M^5ULkS0-u z_4a}!-AH!WQ-c=nU*DalV4orK$dHN9EQ{{)a+I;@&^TDpx09)pT;@2KL4Yp|rNv8C zcXPBfn5N4`^ckP#G+m+_?opS}4cAGvn?D-(-c~ZDS0bLV0(Y>6=anXz25mDN&p6A- z86C{qD^qHCFg=k{O)5$EhIw4Wc)4;|$uv%0g&Pu+t|X86ECk@w>1Ui@$(srw<}dv{ zG=|N5gU#aCRrb5GR(NUyL|zM{bZh`M3ud%vq3?A7I;INa&Kmxbt7!h`8pitjVcq<3 zUTy)Fflwoqv?0nIk#B{SMi{pvj?bf{k!_Z8f5CcTW0FW2$@bQc62;Dv9#|*KasV^q z@;vLXk!*{s%)D_is=cH4nNb(ArP;LIq#e=PI$|U(*=&q7hSDO(Qn_d|D(Yhi461qc z_8-H#_GoXd7vMA+xXqBXbX@e`C;fm5AZ#=ck*`w`}t6ZCAfUP?`5OLi`irBM<*Rh|0vbLC9^;zbNnb@leKY0jmEC zU%ocC72^(GPqn|zF?X_0Dzo2`oCqvnpSrfdO6^GcmORdagp+>RuAE~)D2QcPD2r&KEX|^{P VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} zW*uCG+>x$IA?ZPTXsB`|rp4sr!>6y^ zz=+bb(vmSW(U&y?)VOFEflKWRHLjg%C@m91eLYJnC`wOxZ|8E=}=b9kX@i`*&YhXgAZFA;FA>8(1Mc;CdZ!Q-j;g zf$A5*fq-9K{4gKG|F#DBlZLnJ50p5zm7LLjE!S{aBH>g@{sDJKS9|vvW=H#mL zDFaX!v{OJps@Z@My*!(G-jwga5-DHy{DolJwqIk&DKW5H zjp{@WkH=TEYmld!S}Bw)*uvWK2RgFAIrhK<(+VJy*tp@hhnn?df+4Nf5-Ypi&pzr` zb;QUs&!+zNV%l8AT=Eb5dbU{Ufc)ENOi}j!Wdo*>GkM|_B(GaV@x_k87#4^LhZi#2 zsoTLSOBBji_A;tO>BJ$Qi`Y}x2i1>{p$zpl?59i{ZmgziQN_IBv z7aoX8d2PZYrX+B+_U_e^F^RgGlg6Ag!>kYHN=xfyk*r=tIjp?<)jUlkZt5Ml>3t*L z5e#u)*Smf|{FlVrCn4g4yOhG~G3?I7;rg3ld-BvlL$uTiXBb|T=BK5h5v1F!@NZ{b z{^zr92&~{{=5JVI0tR&1&bM3Ner`PiM`ooMJVlLn;KOr*6lF_f$Vtgx`bgJ{!B){clNCp5X{sm6wB!(9cTmSC|F`N-OV zLEPK4$$6f=b@3N{dwV0VoG7wgyH)iEZ*G2fZsBA?=1Nq&P|ERWY`jhW;Q4!sE4*pZ zw9gG#8ZDw~E6hhxPVkUEIhJuD9l7j7@`#XvTHf0Xbrie8vl#=XkNo4U-I>ygdW|wM zF+t(x{>$;=I&0DoiS`9JMvZ)BZwky}tV0G*T;zTDrr3qs*h7g>;&(g{ZdVX+dElvN z&x6c_#1lfoXT&0T)cjPU-6{mc%Q1FJbuGcUxF>gtkBk~WJeH_lH7P@{44lnG??`rdmW)}xnj(dU$=&w!#BXM-CF=(GR(VkR(E;) zNbW>*NOF~Hr&!=7KV~?k&)PobV}T< zPL4JE%JR;>Cp14dpXUv9WnZ@|MPv+1BCGMT(ZWDJR;fq_RT-7wejK$7td zk*0YLl24Ww$SW>+i8+4U;R!=WLnCK0@IGN)yYJ4F>($%yS%Y^CE-*i^!*=bfY=iPG zbX~%6&s~kGW?`@&EI78TDEvN+Nfq?opS9Z&9r4_)Uu%k@Fj~c`WjEa{IyH8B_Z(-C z)2g7`365}|WDN-Rz)iy>3({PA=stUBwr1Lc)D8NDNgEPtOm1Wbr?LEpxK8;af!(5^ zZ58{GmxM$qDhh~-^C?1s%2%Xk8XcgvVu(GRYkDLj_I8CrlZf@GxHz0dfP5y$;{8re zh>kg|4_~fjxl4I{f7OahQ#`v)`ml{|!uZXYJlUivT`8T2!JIeNrXmF2Pam5N0}QjZiEYVD@owwk+=qTya#$ zv&x!i$J=#U-V&=lEGR9Z2g|{Mt%zm^cdx_! zAkQ0!GB^3ri)8l#(LU6&`XYD8?CUpUAxrC%spMUM^pL9rl3Qq!g%xWgC00TBwgxd` zX09#Wn$=l79(WW?m#Oa>jRIRlEFJhItgE(h?}sUC=7SP!x;O28rbF+V@~sZr2=epR6EP;z`I8A3^jKGPRA|N9`R^BTpt`0R?S^L^d?96h4c$O zcR2X^@rlDKIJ-8!UOv(pe_TQG>1SnwAO%X7$qT3OKmAl8Jn)d|#_Qz0M^5ULkS0-u z_4a}!-AH!WQ-c=nU*DalV4orK$dHN9EQ{{)a+I;@&^TDpx09)pT;@2KL4Yp|rNv8C zcXPBfn5N4`^ckP#G+m+_?opS}4cAGvn?D-(-c~ZDS0bLV0(Y>6=anXz25mDN&p6A- z86C{qD^qHCFg=k{O)5$EhIw4Wc)4;|$uv%0g&Pu+t|X86ECk@w>1Ui@$(srw<}dv{ zG=|N5gU#aCRrb5GR(NUyL|zM{bZh`M3ud%vq3?A7I;INa&Kmxbt7!h`8pitjVcq<3 zUTy)Fflwoqv?0nIk#B{SMi{pvj?bf{k!_Z8f5CcTW0FW2$@bQc62;Dv9#|*KasV^q z@;vLXk!*{s%)D_is=cH4nNb(ArP;LIq#e=PI$|U(*=&q7hSDO(Qn_d|D(Yhi461qc z_8-H#_GoXd7vMA+xXqBXbX@e`C;fm5AZ#=ck*`w`}t6ZCAfUP?`5OLi`irBM<*Rh|0vbLC9^;zbNnb@leKY0jmEC zU%ocC72^(GPqn|zF?X_0Dzo2`oCqvnpSrfdO6^GcmORdagp+>RuAE~)D2QcPD2r&KEX|^{P VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 VGbF<-2jkdhV{VrY;CX%GeJ5`%xZ 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<`b6O*M#64A9qgpu3gD#T4BW8zw} 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 VGbF<U>S8uPI-td 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 VGbF<U>S8uPI-td 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 Date: Thu, 8 Aug 2019 16:28:04 +0800 Subject: [PATCH 034/124] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96ump=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/manager/ump-booter/pom.xml | 60 ++ .../java/com/jd/blockchain/ump/UmpBooter.java | 138 +++ .../jd/blockchain/ump/UmpConfiguration.java | 12 + .../src/main/resources/application.properties | 5 + .../src/main/resources/assembly.xml | 27 + .../ump-booter/src/main/resources/banner.txt | 13 + .../src/main/resources/config.properties | 8 + .../src/main/resources/log4j2-jump.xml | 46 + .../src/main/resources/scripts/jump-start.sh | 9 + .../src/main/resources/scripts/jump-stop.sh | 16 + source/manager/ump-model/pom.xml | 81 ++ .../jd/blockchain/ump/dao/DBConnection.java | 16 + .../ump/dao/DBConnectionProvider.java | 58 ++ .../ump/dao/MemoryDBConnection.java | 47 + .../blockchain/ump/dao/RocksDBConnection.java | 145 +++ .../com/jd/blockchain/ump/dao/UmpDao.java | 4 + .../jd/blockchain/ump/dao/UmpDaoHandler.java | 115 +++ .../jd/blockchain/ump/model/MasterAddr.java | 47 + .../jd/blockchain/ump/model/PartiNode.java | 88 ++ .../ump/model/PeerSharedConfigs.java | 217 ++++ .../jd/blockchain/ump/model/UmpConstant.java | 104 ++ .../com/jd/blockchain/ump/model/UmpQueue.java | 50 + .../ump/model/config/ConsensusConfig.java | 24 + .../ump/model/config/LedgerConfig.java | 46 + .../model/config/LedgerIdentification.java | 92 ++ .../ump/model/config/LedgerInitConfig.java | 141 +++ .../ump/model/config/MasterConfig.java | 89 ++ .../ump/model/config/PeerLocalConfig.java | 160 +++ .../ump/model/config/PeerSharedConfig.java | 86 ++ .../ump/model/config/PeerSharedConfigVv.java | 180 ++++ .../ump/model/state/InstallProcess.java | 21 + .../ump/model/state/InstallSchedule.java | 54 + .../ump/model/state/LedgerBindingConf.java | 33 + .../ump/model/state/LedgerInited.java | 101 ++ .../ump/model/state/LedgerMasterInstall.java | 156 +++ .../ump/model/state/LedgerPeerInited.java | 42 + .../ump/model/state/LedgerPeerInstall.java | 117 +++ .../ump/model/state/PeerInstallSchedule.java | 32 + .../ump/model/state/PeerInstallSchedules.java | 61 ++ .../ump/model/state/PeerStartupSchedules.java | 40 + .../ump/model/state/ScheduleState.java | 43 + .../ump/model/state/StartupState.java | 48 + .../ump/model/user/UserKeyBuilder.java | 34 + .../blockchain/ump/model/user/UserKeys.java | 68 ++ .../blockchain/ump/model/user/UserKeysVv.java | 69 ++ .../blockchain/ump/model/web/ErrorCode.java | 20 + .../blockchain/ump/model/web/WebResponse.java | 97 ++ source/manager/ump-service/pom.xml | 114 +++ .../blockchain/ump/service/LedgerService.java | 31 + .../ump/service/LedgerServiceHandler.java | 310 ++++++ .../jd/blockchain/ump/service/UmpService.java | 36 + .../ump/service/UmpServiceHandler.java | 925 ++++++++++++++++++ .../ump/service/UmpSimulateService.java | 17 + .../service/UmpSimulateServiceHandler.java | 134 +++ .../ump/service/UmpStateService.java | 62 ++ .../ump/service/UmpStateServiceHandler.java | 880 +++++++++++++++++ .../blockchain/ump/service/UtilService.java | 15 + .../ump/service/UtilServiceHandler.java | 80 ++ .../service/consensus/ConsensusProvider.java | 21 + .../service/consensus/ConsensusService.java | 10 + .../consensus/ConsensusServiceHandler.java | 79 ++ .../providers/BftsmartConsensusProvider.java | 162 +++ .../consensus/providers/BftsmartConstant.java | 17 + .../providers/MsgQueueConsensusProvider.java | 41 + .../jd/blockchain/ump/util/Base58Utils.java | 153 +++ .../jd/blockchain/ump/util/CommandUtils.java | 133 +++ .../blockchain/ump/util/HttpClientPool.java | 289 ++++++ .../ump/util/HttpJsonClientUtils.java | 61 ++ source/manager/ump-web/pom.xml | 83 ++ .../ump/controller/UmpDBController.java | 22 + .../ump/controller/UmpKeyController.java | 69 ++ .../ump/controller/UmpMasterController.java | 68 ++ .../ump/controller/UmpPeerController.java | 147 +++ .../controller/UmpPeerSimulateController.java | 64 ++ .../ump/web/ControllerConfigurer.java | 29 + .../ump/web/ExceptionResponseAdvice.java | 38 + .../ump/web/JsonResponseAdvice.java | 51 + .../ump/web/LogPrintInterceptor.java | 32 + 78 files changed, 7333 insertions(+) create mode 100644 source/manager/ump-booter/pom.xml create mode 100644 source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java create mode 100644 source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpConfiguration.java create mode 100644 source/manager/ump-booter/src/main/resources/application.properties create mode 100644 source/manager/ump-booter/src/main/resources/assembly.xml create mode 100644 source/manager/ump-booter/src/main/resources/banner.txt create mode 100644 source/manager/ump-booter/src/main/resources/config.properties create mode 100644 source/manager/ump-booter/src/main/resources/log4j2-jump.xml create mode 100644 source/manager/ump-booter/src/main/resources/scripts/jump-start.sh create mode 100644 source/manager/ump-booter/src/main/resources/scripts/jump-stop.sh create mode 100644 source/manager/ump-model/pom.xml create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnection.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnectionProvider.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/MemoryDBConnection.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/RocksDBConnection.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDao.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDaoHandler.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/MasterAddr.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PartiNode.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PeerSharedConfigs.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpConstant.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpQueue.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/ConsensusConfig.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerConfig.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerIdentification.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerInitConfig.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/MasterConfig.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerLocalConfig.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfig.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfigVv.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallProcess.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallSchedule.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerBindingConf.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerInited.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerMasterInstall.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInited.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInstall.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedule.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedules.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerStartupSchedules.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/ScheduleState.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/StartupState.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeyBuilder.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeys.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeysVv.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/ErrorCode.java create mode 100644 source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/WebResponse.java create mode 100644 source/manager/ump-service/pom.xml create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerService.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerServiceHandler.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpService.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpServiceHandler.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateService.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateServiceHandler.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateService.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateServiceHandler.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilService.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilServiceHandler.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusProvider.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusService.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusServiceHandler.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConsensusProvider.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConstant.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/MsgQueueConsensusProvider.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/Base58Utils.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/CommandUtils.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpClientPool.java create mode 100644 source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpJsonClientUtils.java create mode 100644 source/manager/ump-web/pom.xml create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpDBController.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerController.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerSimulateController.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ControllerConfigurer.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ExceptionResponseAdvice.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/JsonResponseAdvice.java create mode 100644 source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/LogPrintInterceptor.java diff --git a/source/manager/ump-booter/pom.xml b/source/manager/ump-booter/pom.xml new file mode 100644 index 00000000..63461b0f --- /dev/null +++ b/source/manager/ump-booter/pom.xml @@ -0,0 +1,60 @@ + + + + + manager + com.jd.blockchain + 1.1.0-SNAPSHOT + + 4.0.0 + + ump-booter + + ump-booter + + + UTF-8 + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-devtools + true + + + junit + junit + test + + + diff --git a/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java b/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java new file mode 100644 index 00000000..97656b03 --- /dev/null +++ b/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java @@ -0,0 +1,138 @@ +package com.jd.blockchain.ump; + +import org.springframework.boot.SpringApplication; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + + +public class UmpBooter { + + private static final String ARG_PORT = "-p"; + + private static final String ARG_HOST = "-h"; + + private static final String CONFIG = "BOOT-INF" + File.separator + "classes" + File.separator + "config.properties"; + + private static final String CONFIG_PROP_HOST = "server.host"; + + private static final String CONFIG_PROP_HOST_DEFAULT = "0.0.0.0"; + + private static final String CONFIG_PROP_PORT = "server.port"; + + private static final String CONFIG_PROP_PORT_DEFAULT = "8080"; + + private static final String CONFIG_PROP_DB_URL = "db.url"; + + private static final String CONFIG_PROP_DB_URL_DEFAULT = "rocksdb://#project#/jumpdb"; + + public static void main(String[] args) { + try { + startServer(server(args)); + System.out.println("Server Start SUCCESS !!!"); + } catch (Exception e) { + e.printStackTrace(); + System.err.printf("Server Start FAIL -> %s, Exit JVM !!!", e.toString()); + // 正常退出 + System.exit(0); + } + } + + private static void startServer(Server server) { + + System.out.printf("server.address = %s, server.port = %s, db.url = %s \r\n", + server.host, server.port, server.dbUrl); + + List argList = new ArrayList<>(); + argList.add(String.format("--server.address=%s", server.host)); + argList.add(String.format("--server.port=%s", server.port)); + argList.add(String.format("--db.url=%s", server.dbUrl)); + + String[] args = argList.toArray(new String[argList.size()]); + + // 启动服务器; + SpringApplication.run(UmpConfiguration.class, args); + } + + private static Server server(String[] args) { + Server defaultServer = serverFromConfig(); + if (args == null || args.length == 0) { + return defaultServer; + } + String host = null; + + int port = 0; + + // 读取参数列表 + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.equals(ARG_HOST)) { + host = args[i + 1]; + } else if (arg.equals(ARG_PORT)) { + port = Integer.parseInt(args[i + 1]); + } + } + + // 参数列表中的数据不完整,则剩余部分数据从配置文件中获取 + if (host == null) { + host = defaultServer.host; + } + if (port == 0) { + port = defaultServer.port; + } + + return new Server(host, port, defaultServer.dbUrl); + } + + private static Server serverFromConfig() { + try { + InputStream inputStream = UmpBooter.class.getResourceAsStream(File.separator + CONFIG); + if (inputStream == null) { + System.err.println("InputStream is NULL !!!"); + } + Properties props = new Properties(); + props.load(inputStream); + String host = props.getProperty(CONFIG_PROP_HOST, CONFIG_PROP_HOST_DEFAULT); + int port = Integer.parseInt( + props.getProperty(CONFIG_PROP_PORT, CONFIG_PROP_PORT_DEFAULT)); + String dbUrl = props.getProperty(CONFIG_PROP_DB_URL, CONFIG_PROP_DB_URL_DEFAULT); + return new Server(host, port, dbUrl); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static class Server { + + private String host; + + private int port; + + private String dbUrl; + + public Server(String host, int port, String dbUrl) { + this.host = host; + this.port = port; + this.dbUrl = dbUrl; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + } +} diff --git a/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpConfiguration.java b/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpConfiguration.java new file mode 100644 index 00000000..f1e86f12 --- /dev/null +++ b/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpConfiguration.java @@ -0,0 +1,12 @@ +package com.jd.blockchain.ump; + + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@EnableConfigurationProperties +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class} + ) +public class UmpConfiguration { +} diff --git a/source/manager/ump-booter/src/main/resources/application.properties b/source/manager/ump-booter/src/main/resources/application.properties new file mode 100644 index 00000000..b5ef6943 --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/application.properties @@ -0,0 +1,5 @@ +server.tomcat.uri-encoding=utf-8 + +spring.mvc.favicon.enabled=false + +logging.config=classpath:log4j2-jump.xml \ No newline at end of file diff --git a/source/manager/ump-booter/src/main/resources/assembly.xml b/source/manager/ump-booter/src/main/resources/assembly.xml new file mode 100644 index 00000000..8fd114ba --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/assembly.xml @@ -0,0 +1,27 @@ + + + ${project.version} + + zip + + false + + + src/main/resources/scripts + bin + + + + + false + true + ext + + com.jd.blockchain:ump-booter + + + + \ No newline at end of file diff --git a/source/manager/ump-booter/src/main/resources/banner.txt b/source/manager/ump-booter/src/main/resources/banner.txt new file mode 100644 index 00000000..c39618bd --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/banner.txt @@ -0,0 +1,13 @@ + + ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ▄ +▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ ▐░░░░░░░░░░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░▌ ▐░▌ + ▀▀▀▀▀█░█▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░▌▐░█▀▀▀▀▀▀▀█░▌ ▀▀▀▀█░█▀▀▀▀ ▐░▌░▌ ▐░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░░░░░░░░░░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ + ▄▄▄▄▄█░▌ ▐░█▄▄▄▄▄▄▄█░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌▐░▌ ▐░▌ ▄▄▄▄█░█▄▄▄▄ ▐░▌ ▐░▐░▌ +▐░░░░░░░▌ ▐░░░░░░░░░░▌ ▐░░░░░░░░░░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░░▌ + ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀▀ + diff --git a/source/manager/ump-booter/src/main/resources/config.properties b/source/manager/ump-booter/src/main/resources/config.properties new file mode 100644 index 00000000..deaf015e --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/config.properties @@ -0,0 +1,8 @@ +# Tomcat启动的HOST,默认为0.0.0.0 +server.host=0.0.0.0 + +# Tomcat启动监听端口号 +server.port=8080 + +# 本地数据库存储位置 +db.url=rocksdb://#project#/jumpdb \ No newline at end of file diff --git a/source/manager/ump-booter/src/main/resources/log4j2-jump.xml b/source/manager/ump-booter/src/main/resources/log4j2-jump.xml new file mode 100644 index 00000000..6b19f527 --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/log4j2-jump.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/manager/ump-booter/src/main/resources/scripts/jump-start.sh b/source/manager/ump-booter/src/main/resources/scripts/jump-start.sh new file mode 100644 index 00000000..a31a5b28 --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/scripts/jump-start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +HOME=$(cd `dirname $0`;cd ../; pwd) +UMP=$(ls $HOME/ext | grep ump-booter-) +if [ ! -n "UMP" ]; then + echo "Unified Management Platform Is Null !!!" +else + nohup java -jar -server -Djump.log=$HOME $HOME/ext/$UMP $* >$HOME/bin/jump.out 2>&1 & +fi \ No newline at end of file diff --git a/source/manager/ump-booter/src/main/resources/scripts/jump-stop.sh b/source/manager/ump-booter/src/main/resources/scripts/jump-stop.sh new file mode 100644 index 00000000..b7155c88 --- /dev/null +++ b/source/manager/ump-booter/src/main/resources/scripts/jump-stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +#启动Home路径 +BOOT_HOME=$(cd `dirname $0`;cd ../; pwd) + +#获取进程PID +PID=`ps -ef | grep $BOOT_HOME/ext/ump-booter | grep -v grep | awk '{print $2}'` + +#通过Kill命令将进程杀死 +if [ -z "$PID" ]; then + echo "Unable to find UMP PID. stop aborted." +else + echo "Start to kill PID = $PID ..." + kill -9 $PID + echo "Unified Management Platform has been stopped ..." +fi \ No newline at end of file diff --git a/source/manager/ump-model/pom.xml b/source/manager/ump-model/pom.xml new file mode 100644 index 00000000..67436cc0 --- /dev/null +++ b/source/manager/ump-model/pom.xml @@ -0,0 +1,81 @@ + + + + + manager + com.jd.blockchain + 1.1.0-SNAPSHOT + + 4.0.0 + + ump-model + + ump-model + + + UTF-8 + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-devtools + true + + + + com.alibaba + fastjson + + + + org.reflections + reflections + + + + org.rocksdb + rocksdbjni + + + + commons-io + commons-io + + + + junit + junit + test + + + diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnection.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnection.java new file mode 100644 index 00000000..eea65d3f --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnection.java @@ -0,0 +1,16 @@ +package com.jd.blockchain.ump.dao; + +public interface DBConnection { + + String dbSchema(); + + DBConnection initDbUrl(String dbUrl); + + void put(String key, String value); + + void put(String key, Object value, Class type); + + String get(String key); + + boolean exist(String dbUrl); +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnectionProvider.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnectionProvider.java new file mode 100644 index 00000000..d029a411 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/DBConnectionProvider.java @@ -0,0 +1,58 @@ +package com.jd.blockchain.ump.dao; + +import org.reflections.Reflections; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class DBConnectionProvider { + + private static final Map dbConnections = new ConcurrentHashMap<>(); + + static { + init(); + } + + public static DBConnection dbConnection(String dbUrl) { + String dbSchema = dbSchema(dbUrl); + if (!dbConnections.containsKey(dbSchema)) { + throw new IllegalStateException( + String.format("Can not find DBConnection by {%s} !", dbUrl)); + } + + DBConnection dbConnection = dbConnections.get(dbSchema); + return dbConnection.initDbUrl(dbUrl); + } + + + private static String dbSchema(String dbUrl) { + // rocksdb:///home/xxx -> rocksdb + return dbUrl.split("://")[0]; + + } + + private static void init() { + // 初始化所有实现类 + Reflections reflections = new Reflections("com.jd.blockchain.ump.dao"); + + Set> dbConnectionSet = + reflections.getSubTypesOf(DBConnection.class); + + for (Class clazz : dbConnectionSet) { + + if (!clazz.isInterface() && !clazz.equals(UmpDaoHandler.class)) { + try { + // 根据class生成对象 + DBConnection dbConnection = clazz.newInstance(); + String dbSchema = dbConnection.dbSchema(); + if (dbSchema != null && dbSchema.length() > 0) { + dbConnections.put(dbSchema, dbConnection); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/MemoryDBConnection.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/MemoryDBConnection.java new file mode 100644 index 00000000..ac9e5755 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/MemoryDBConnection.java @@ -0,0 +1,47 @@ +package com.jd.blockchain.ump.dao; + +import com.alibaba.fastjson.JSON; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class MemoryDBConnection implements DBConnection { + + private static final String MEMORY_SCHEMA = "memory"; + + private final Map memory = new ConcurrentHashMap<>(); + + @Override + public String dbSchema() { + return MEMORY_SCHEMA; + } + + @Override + public DBConnection initDbUrl(String dbUrl) { + return this; + } + + @Override + public void put(String key, String value) { + memory.put(key, value); + } + + @Override + public void put(String key, Object value, Class type) { + String json = JSON.toJSONString(value); + put(key, json); + } + + @Override + public String get(String key) { + return memory.get(key); + } + + @Override + public boolean exist(String dbUrl) { + if (dbUrl.startsWith(MEMORY_SCHEMA)) { + return true; + } + return false; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/RocksDBConnection.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/RocksDBConnection.java new file mode 100644 index 00000000..ed32c519 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/RocksDBConnection.java @@ -0,0 +1,145 @@ +package com.jd.blockchain.ump.dao; + + +import com.alibaba.fastjson.JSON; +import org.apache.commons.io.FileUtils; +import org.rocksdb.*; +import org.rocksdb.util.SizeUnit; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class RocksDBConnection implements DBConnection { + + public static final String SCHEMA = "rocksdb"; + + public static final String PROTOCOL_SPLIT = "://"; + + public static final String ROCKSDB_PROTOCOL = SCHEMA + PROTOCOL_SPLIT; + + static { + RocksDB.loadLibrary(); + } + + private RocksDB rocksDB; + + @Override + public String dbSchema() { + return SCHEMA; + } + + @Override + public DBConnection initDbUrl(String dbUrl) { + if (!dbUrl.startsWith(dbSchema())) { + throw new IllegalStateException(String.format("Unsupport DBConnection by URL {%s} !!!", dbUrl)); + } + String dbSavePath = dbUrl.split(PROTOCOL_SPLIT)[1]; + initDBConnection(dbSavePath); + return this; + } + + @Override + public void put(String key, String value) { + if (this.rocksDB == null) { + throw new IllegalStateException("Rocksdb is NULL, Please initDbUrl first !!!"); + } + try { + this.rocksDB.put(key.getBytes(UTF_8), value.getBytes(UTF_8)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public void put(String key, Object value, Class type) { + // 使用JSON序列化 + String json = JSON.toJSONString(value); + put(key, json); + } + + @Override + public String get(String key) { + try { + byte[] value = this.rocksDB.get(key.getBytes(UTF_8)); + if (value != null && value.length > 0) { + return new String(value, UTF_8); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return null; + } + + @Override + public boolean exist(String dbUrl) { + // 首先该dbUrl是Rocksdb + if (dbUrl.startsWith(ROCKSDB_PROTOCOL)) { + // 判断File是否存在,并且是文件夹 + File dbPath = new File(dbUrl.substring(ROCKSDB_PROTOCOL.length())); + if (dbPath.exists() && dbPath.isDirectory()) { + return true; + } + } + return false; + } + + private void initDBConnection(String dbUrl) { + try { + File dbPath = new File(dbUrl); + File dbParentPath = dbPath.getParentFile(); + if (!dbParentPath.exists()) { + FileUtils.forceMkdir(dbParentPath); + } + this.rocksDB = RocksDB.open(initOptions(), dbUrl); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + private Options initOptions() { + final Filter bloomFilter = new BloomFilter(32); + final BlockBasedTableConfig tableOptions = new BlockBasedTableConfig() + .setFilter(bloomFilter) + .setBlockSize(4 * SizeUnit.KB) + .setBlockSizeDeviation(10) + .setBlockCacheSize(64 * SizeUnit.GB) + .setNoBlockCache(false) + .setCacheIndexAndFilterBlocks(true) + .setBlockRestartInterval(16) + ; + final List compressionLevels = new ArrayList<>(); + compressionLevels.add(CompressionType.NO_COMPRESSION); // 0-1 + compressionLevels.add(CompressionType.SNAPPY_COMPRESSION); // 1-2 + compressionLevels.add(CompressionType.SNAPPY_COMPRESSION); // 2-3 + compressionLevels.add(CompressionType.SNAPPY_COMPRESSION); // 3-4 + compressionLevels.add(CompressionType.SNAPPY_COMPRESSION); // 4-5 + compressionLevels.add(CompressionType.SNAPPY_COMPRESSION); // 5-6 + compressionLevels.add(CompressionType.SNAPPY_COMPRESSION); // 6-7 + + Options options = new Options() + .setAllowConcurrentMemtableWrite(true) + .setEnableWriteThreadAdaptiveYield(true) + .setCreateIfMissing(true) + .setMaxWriteBufferNumber(3) + .setTableFormatConfig(tableOptions) + .setMaxBackgroundCompactions(10) + .setMaxBackgroundFlushes(4) + .setBloomLocality(10) + .setMinWriteBufferNumberToMerge(4) + .setCompressionPerLevel(compressionLevels) + .setNumLevels(7) + .setCompressionType(CompressionType.SNAPPY_COMPRESSION) + .setCompactionStyle(CompactionStyle.UNIVERSAL) + .setMemTableConfig(new SkipListMemTableConfig()) + ; + return options; + } + + public static void main(String[] args) { + String path = "rocksdb:///zhangsan/lisi"; + System.out.println(path.substring(ROCKSDB_PROTOCOL.length())); + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDao.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDao.java new file mode 100644 index 00000000..1da5d48b --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDao.java @@ -0,0 +1,4 @@ +package com.jd.blockchain.ump.dao; + +public interface UmpDao { +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDaoHandler.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDaoHandler.java new file mode 100644 index 00000000..62cce01d --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/dao/UmpDaoHandler.java @@ -0,0 +1,115 @@ +package com.jd.blockchain.ump.dao; + +import com.jd.blockchain.ump.model.UmpConstant; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Repository; + +import java.io.File; + +@Repository +public class UmpDaoHandler implements UmpDao, CommandLineRunner, DBConnection { + + public final String PROJECT_FLAG = "#project#"; + + private static final String PROTOCOL_FILE = "file:"; + + private static final String INNER_FILE_SEPARATOR = "!"; + + private static final String PROTOCOL_SEPARATOR = "://"; + + private DBConnection dbConnection; + + @Override + public void run(String... args) { + + String dbUrl = RocksDBConnection.SCHEMA + PROTOCOL_SEPARATOR + + PROJECT_FLAG + File.separator + UmpConstant.DB_NAME; + + if (args != null && args.length > 0) { + for (String arg : args) { + if (arg.startsWith("--db.url")) { + dbUrl = arg.split("=")[1]; + } + } + } + + dbConnection = DBConnectionProvider.dbConnection(realPath(dbUrl)); + + initProjectPath(); + } + + private void initProjectPath() { + UmpConstant.PROJECT_PATH = projectPath(); + System.out.printf("Init Project Path = %s \r\n", UmpConstant.PROJECT_PATH); + } + + @Override + public String dbSchema() { + return null; + } + + @Override + public DBConnection initDbUrl(String dbUrl) { + return dbConnection; + } + + @Override + public void put(String key, String value) { + dbConnection.put(key, value); + } + + @Override + public void put(String key, Object value, Class type) { + dbConnection.put(key, value, type); + } + + @Override + public String get(String key) { + return dbConnection.get(key); + } + + @Override + public boolean exist(String dbUrl) { + try { + return dbConnection.exist(dbUrl); + } catch (Exception e) { + // 不关心异常 + System.err.println(e); + return false; + } + } + + private String realPath(String dbUrl) { + if (dbUrl.contains(PROJECT_FLAG)) { + // 获取当前jar包路径 + try { + String projectPath = projectPath(); + return dbUrl.replaceAll(PROJECT_FLAG, projectPath); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + return dbUrl; + } + + private String projectPath() { + File jarDirectory = new File(jarRootPath()); + return jarDirectory.getParentFile().getParentFile().getPath(); + } + + private String jarRootPath() { + // 获取Jar包所在路径 + String jarRootPath = UmpDaoHandler.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + + // 处理打包到SpringBoot后路径问题:file: + if (jarRootPath.startsWith(PROTOCOL_FILE)) { + jarRootPath = jarRootPath.substring(PROTOCOL_FILE.length()); + } + // 处理打包到SpringBoot后内部分隔符问题:! + if (jarRootPath.contains(INNER_FILE_SEPARATOR)) { + jarRootPath = jarRootPath.substring(0, jarRootPath.indexOf(INNER_FILE_SEPARATOR)); + } + + return jarRootPath; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/MasterAddr.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/MasterAddr.java new file mode 100644 index 00000000..700cef0b --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/MasterAddr.java @@ -0,0 +1,47 @@ +package com.jd.blockchain.ump.model; + +public class MasterAddr { + + private String ipAddr; + + private int port; + + public MasterAddr() { + } + + public MasterAddr(String ipAddr, int port) { + this.ipAddr = ipAddr; + this.port = port; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public static MasterAddr newInstance(String ipAddr, int port) { + return new MasterAddr(ipAddr, port); + } + + public String toHttpUrl() { + return "http://" + ipAddr + ":" + port; + } + + public boolean legal() { + if (this.ipAddr == null || this.ipAddr.length() == 0 || this.port == 0) { + return false; + } + return true; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PartiNode.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PartiNode.java new file mode 100644 index 00000000..9281d408 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PartiNode.java @@ -0,0 +1,88 @@ +package com.jd.blockchain.ump.model; + +import java.util.ArrayList; +import java.util.List; + +public class PartiNode { + + private int id; + + private String name; + + private String pubKey; + + private String initHost; + + private int initPort; + + private boolean isSecure; + + public List toConfigChars() { + + List configCharList = new ArrayList<>(); + + configCharList.add(formatConfig(UmpConstant.PARTINODE_NAME_FORMAT, name)); + + configCharList.add(formatConfig(UmpConstant.PARTINODE_PUBKEY_FORMAT, pubKey)); + + configCharList.add(formatConfig(UmpConstant.PARTINODE_INIT_HOST_FORMAT, initHost)); + + configCharList.add(formatConfig(UmpConstant.PARTINODE_INIT_PORT_FORMAT, initPort)); + + configCharList.add(formatConfig(UmpConstant.PARTINODE_INIT_SECURE_FORMAT, isSecure)); + + return configCharList; + } + + private String formatConfig(String formatter, Object value) { + return String.format(formatter, id) + "=" + value; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + public String getInitHost() { + return initHost; + } + + public void setInitHost(String initHost) { + this.initHost = initHost; + } + + public int getInitPort() { + return initPort; + } + + public void setInitPort(int initPort) { + this.initPort = initPort; + } + + public boolean isSecure() { + return isSecure; + } + + public void setSecure(boolean secure) { + isSecure = secure; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PeerSharedConfigs.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PeerSharedConfigs.java new file mode 100644 index 00000000..63251967 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/PeerSharedConfigs.java @@ -0,0 +1,217 @@ +package com.jd.blockchain.ump.model; + +import com.jd.blockchain.ump.model.config.LedgerInitConfig; +import com.jd.blockchain.ump.model.config.MasterConfig; +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.config.PeerSharedConfig; +import com.jd.blockchain.ump.model.state.LedgerMasterInstall; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class PeerSharedConfigs { + + /** + * 默认的一次邀请码最长等待时间,单位分钟,默认30分钟 + */ + private static final int MAX_WAIT_MINUTE = 30; + + private CountDownLatch latch = null; + + private Lock lock = new ReentrantLock(); + + private Lock waitLock = new ReentrantLock(); + + private Condition sizeCondition = waitLock.newCondition(); + + private List sharedConfigs = new ArrayList<>(); + + private int waitNodeSize; + + private String consensusProvider; + + private String sharedKey; + + private String ledgerName; + + private LedgerInitConfig ledgerInitConfig; + + public synchronized PeerSharedConfigs addConfig(PeerLocalConfig sharedConfig) { + + // 判断内容是否存在重复 + for (PeerSharedConfig innerSharedConfig : sharedConfigs) { + if (innerSharedConfig.getName().equals(sharedConfig.getName()) + || innerSharedConfig.getPubKey().equals(sharedConfig.getPubKey())) { + return null; + } + } + + if (sharedConfig.getMasterConfig().isMaster()) { + initDataByMaster(sharedConfig); + } + + sharedConfigs.add(sharedConfig); + + if (latch != null) { + // 不管是Master还是普通用户都需要-1 + latch.countDown(); + } + return this; + } + + /** + * 由Master节点传入的信息对数据进行初始化 + * + * @param sharedConfig + */ + private void initDataByMaster(PeerLocalConfig sharedConfig) { + + MasterConfig masterConfig = sharedConfig.getMasterConfig(); + + // master需要对数据进行组织 + if (latch == null) { + latch = new CountDownLatch(masterConfig.getNodeSize() - sharedConfigs.size()); + } + if (consensusProvider == null) { + consensusProvider = sharedConfig.getConsensusProvider(); + } + if (sharedKey == null) { + sharedKey = sharedConfig.getSharedKey(); + } + if (ledgerName == null) { + ledgerName = masterConfig.getLedgerName(); + } + waitNodeSize = masterConfig.getNodeSize(); + } + + /** + * 线程等待 + * 一直处于等待状态(30分钟),直到有线程调用single方法 + * + */ + public void await() { + waitLock.lock(); + try { + sizeCondition.await(MAX_WAIT_MINUTE, TimeUnit.MINUTES); + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + waitLock.unlock(); + } + } + + /** + * 通知其他线程等待状态结束 + * + */ + public void single() { + waitLock.lock(); + try { + sizeCondition.signalAll(); + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + waitLock.unlock(); + } + } + + /** + * Master线程调用,等待数据满足后通知其他线程 + * + */ + public void waitAndNotify() { + if (this.latch == null) { + throw new IllegalStateException("Please init MasterConfig first !!!"); + } + try { + latch.await(MAX_WAIT_MINUTE, TimeUnit.MINUTES); + single(); // 通知其他线程释放 + } catch (Exception e) { + if (sharedConfigs.size() >= waitNodeSize) { + // 成功 + single(); + } + } + } + + public synchronized LedgerInitConfig ledgerInitConfig(String seed, String createTime) { + if (ledgerInitConfig != null) { + return ledgerInitConfig; + } + + // 处理该ledgerInitConfig + ledgerInitConfig = new LedgerInitConfig(seed, ledgerName, createTime, consensusProvider, waitNodeSize); + + // 添加参与方 + for (int i = 0; i < sharedConfigs.size(); i++) { + PeerLocalConfig sharedConfig = sharedConfigs.get(i); + ledgerInitConfig.addPartiNode(sharedConfig.toPartiNode(i)); + } + + return ledgerInitConfig; + } + + public String getConsensusProvider() { + return consensusProvider; + } + + public void setConsensusProvider(String consensusProvider) { + this.consensusProvider = consensusProvider; + } + + public String getSharedKey() { + return sharedKey; + } + + public void setSharedKey(String sharedKey) { + this.sharedKey = sharedKey; + } + + public Lock getLock() { + return lock; + } + + public String getLedgerName() { + return ledgerName; + } + + public void setLedgerName(String ledgerName) { + this.ledgerName = ledgerName; + } + + public List getSharedConfigs() { + return sharedConfigs; + } + + public void setSharedConfigs(List sharedConfigs) { + this.sharedConfigs = sharedConfigs; + } + + public LedgerInitConfig getLedgerInitConfig() { + return ledgerInitConfig; + } + + public void setLedgerInitConfig(LedgerInitConfig ledgerInitConfig) { + this.ledgerInitConfig = ledgerInitConfig; + } + + public LedgerMasterInstall toLedgerMasterInstall() { + + // String ledgerKey, String sharedKey, int totalNodeSize + LedgerMasterInstall masterInstall = new LedgerMasterInstall( + ledgerInitConfig.ledgerKey(), sharedConfigs.size()) + .initCreateTime(ledgerInitConfig.getCreateTime()); + + for (PeerLocalConfig sharedConfig : sharedConfigs) { + + masterInstall.add(sharedConfig.toPeerInstall()); + } + + return masterInstall; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpConstant.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpConstant.java new file mode 100644 index 00000000..5f383e02 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpConstant.java @@ -0,0 +1,104 @@ +package com.jd.blockchain.ump.model; + +import java.io.File; + +public class UmpConstant { + + public static String PROJECT_PATH = ""; + + public static final String DB_NAME = "jumpdb"; + + public static final String URL_SEPARATOR = "/"; + + public static final String URL_MASTER = "/master"; + + public static final String URL_PEER = "/peer"; + + public static final String PRIVATE_KEY_SUFFIX = ".priv"; + + public static final String PUBLIC_KEY_SUFFIX = ".priv"; + + public static final String PWD_SUFFIX = ".pwd"; + + public static final String REQUEST_SHARED_URL = URL_MASTER + URL_SEPARATOR + "share"; + + public static final String REQUEST_STATE_URL = URL_MASTER + URL_SEPARATOR + "receive"; + + public static final String PARTINODE_COUNT = "cons_parti.count"; + + public static final String PARTINODE_FORMAT = "cons_parti.%s"; + + public static final String PARTINODE_NAME_FORMAT = PARTINODE_FORMAT + ".name"; + + public static final String PARTINODE_PUBKEY_FORMAT = PARTINODE_FORMAT + ".pubkey"; + + public static final String PARTINODE_INIT_FORMAT = PARTINODE_FORMAT + ".initializer"; + + public static final String PARTINODE_INIT_HOST_FORMAT = PARTINODE_INIT_FORMAT + ".host"; + + public static final String PARTINODE_INIT_PORT_FORMAT = PARTINODE_INIT_FORMAT + ".port"; + + public static final String PARTINODE_INIT_SECURE_FORMAT = PARTINODE_INIT_FORMAT + ".secure"; + + public static final String LEDGER_PREFIX = "ledger"; + + public static final String LEDGER_SEED_PREFIX = LEDGER_PREFIX + ".seed"; + + public static final String LEDGER_NAME_PREFIX = LEDGER_PREFIX + ".name"; + + public static final String CREATE_TIME_PREFIX = "created-time"; + + public static final String CONSENSUS_PREFIX = "consensus"; + + public static final String CONSENSUS_PROVIDER_PREFIX = CONSENSUS_PREFIX + ".service-provider"; + + public static final String CONSENSUS_CONF_PREFIX = CONSENSUS_PREFIX + ".conf"; + + public static final String CRYPTO_PREFIX = "crypto"; + + public static final String CRYPTO_PROVIDERS_PREFIX = CRYPTO_PREFIX + ".service-providers"; + + public static final String LOCAL_PREFIX = "local"; + + public static final String LOCAL_PARTI_PREFIX = LOCAL_PREFIX + ".parti"; + + public static final String LOCAL_PARTI_ID_PREFIX = LOCAL_PARTI_PREFIX + ".id"; + + public static final String LOCAL_PARTI_PUBKEY_PREFIX = LOCAL_PARTI_PREFIX + ".pubkey"; + + public static final String LOCAL_PARTI_PRIVKEY_PREFIX = LOCAL_PARTI_PREFIX + ".privkey"; + + public static final String LOCAL_PARTI_PWD_PREFIX = LOCAL_PARTI_PREFIX + ".pwd"; + + public static final String LEDGER_BINDING_OUT_PREFIX = LEDGER_PREFIX + ".binding.out"; + + public static final String LEDGER_DB_URI_PREFIX = LEDGER_PREFIX + ".db.uri"; + + public static final String LEDGER_DB_PWD_PREFIX = LEDGER_PREFIX + ".db.pwd"; + + public static final String CMD_LEDGER_INIT = "/bin/bash %s -monitor"; + + public static final String CMD_START_UP_FORMAT = "/bin/bash %s"; + + public static final String PATH_BIN = File.separator + "bin"; + + public static final String PATH_LEDGER_INIT_BIN = PATH_BIN + File.separator + "ledger-init.sh"; + + public static final String PATH_STARTUP_BIN = PATH_BIN + File.separator + "startup.sh"; + + public static final String PATH_LIBS = File.separator + "libs"; + + public static final String PATH_SYSTEM = File.separator + "system"; + + public static final String PATH_CONFIG = File.separator + "config"; + + public static final String PATH_CONFIG_KEYS = PATH_CONFIG + File.separator + "keys"; + + public static final String PATH_LEDGER_BINDING_CONFIG = PATH_CONFIG + File.separator + "ledger-binding.conf"; + + public static final String PATH_CONFIG_INIT = PATH_CONFIG + File.separator + "init"; + + public static final String PATH_LOCAL_CONFIG = PATH_CONFIG_INIT + File.separator + "local.conf"; + + public static final String PATH_LEDGER_INIT_CONFIG = PATH_CONFIG_INIT + File.separator + "ledger.init"; +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpQueue.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpQueue.java new file mode 100644 index 00000000..509fb4a5 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/UmpQueue.java @@ -0,0 +1,50 @@ +package com.jd.blockchain.ump.model; + +import com.jd.blockchain.ump.model.state.InstallSchedule; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class UmpQueue { + + private final BlockingQueue QUEUE_INSTALL_SCHEDULE = new LinkedBlockingQueue<>(); + + public void put(InstallSchedule installSchedule, MasterAddr masterAddr) throws InterruptedException { + QUEUE_INSTALL_SCHEDULE.put(new InstallScheduleRequest(installSchedule, masterAddr)); + } + + public InstallScheduleRequest take() throws InterruptedException { + return QUEUE_INSTALL_SCHEDULE.take(); + } + + public static class InstallScheduleRequest { + + private InstallSchedule installSchedule; + + private MasterAddr masterAddr; + + public InstallScheduleRequest() { + } + + public InstallScheduleRequest(InstallSchedule installSchedule, MasterAddr masterAddr) { + this.installSchedule = installSchedule; + this.masterAddr = masterAddr; + } + + public InstallSchedule getInstallSchedule() { + return installSchedule; + } + + public void setInstallSchedule(InstallSchedule installSchedule) { + this.installSchedule = installSchedule; + } + + public MasterAddr getMasterAddr() { + return masterAddr; + } + + public void setMasterAddr(MasterAddr masterAddr) { + this.masterAddr = masterAddr; + } + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/ConsensusConfig.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/ConsensusConfig.java new file mode 100644 index 00000000..e6963c2f --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/ConsensusConfig.java @@ -0,0 +1,24 @@ +package com.jd.blockchain.ump.model.config; + +public class ConsensusConfig { + + private String confPath; + + private byte[] content; + + public String getConfPath() { + return confPath; + } + + public void setConfPath(String confPath) { + this.confPath = confPath; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerConfig.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerConfig.java new file mode 100644 index 00000000..2b435f93 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerConfig.java @@ -0,0 +1,46 @@ +package com.jd.blockchain.ump.model.config; + +public class LedgerConfig { + + private LedgerInitConfig initConfig; + + /** + * 共识文件配置信息,Base58格式 + */ + private String consensusConfig; + + + public LedgerConfig() { + } + + /** + * 包装一下,使用JSON处理过程 + * + * @param ledgerConfig + */ + public LedgerConfig(LedgerConfig ledgerConfig) { + this.consensusConfig = ledgerConfig.getConsensusConfig(); + + } + + public LedgerConfig(LedgerInitConfig initConfig, String consensusConfig) { + this.initConfig = initConfig; + this.consensusConfig = consensusConfig; + } + + public LedgerInitConfig getInitConfig() { + return initConfig; + } + + public void setInitConfig(LedgerInitConfig initConfig) { + this.initConfig = initConfig; + } + + public String getConsensusConfig() { + return consensusConfig; + } + + public void setConsensusConfig(String consensusConfig) { + this.consensusConfig = consensusConfig; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerIdentification.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerIdentification.java new file mode 100644 index 00000000..1b724dc7 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerIdentification.java @@ -0,0 +1,92 @@ +package com.jd.blockchain.ump.model.config; + +import com.jd.blockchain.ump.model.MasterAddr; + +public class LedgerIdentification { + + private String ledgerKey; + + private String ledgerAndNodeKey; + + private MasterAddr masterAddr; + + private int nodeId; + + private PeerLocalConfig localConfig; + + private LedgerInitConfig initConfig; + + public LedgerIdentification() { + } + + public LedgerIdentification(int nodeId, PeerLocalConfig localConfig, MasterAddr masterAddr, String ledgerAndNodeKey, LedgerInitConfig initConfig) { + this.nodeId = nodeId; + this.localConfig = localConfig; + this.masterAddr = masterAddr; + this.ledgerKey = initConfig.ledgerKey(); + this.ledgerAndNodeKey = ledgerAndNodeKey; + this.initConfig = initConfig; + init(); + } + + private void init() { + // 初始化部分配置信息 + MasterConfig masterConfig = localConfig.getMasterConfig(); + // 设置账本名称 + if (masterConfig.getLedgerName() == null || masterConfig.getLedgerName().length() == 0) { + masterConfig.setLedgerName(initConfig.getName()); + } + // 设置NodeSize + if (masterConfig.getNodeSize() == 0) { + masterConfig.setNodeSize(initConfig.getNodeSize()); + } + } + + public String getLedgerKey() { + return ledgerKey; + } + + public void setLedgerKey(String ledgerKey) { + this.ledgerKey = ledgerKey; + } + + public String getLedgerAndNodeKey() { + return ledgerAndNodeKey; + } + + public void setLedgerAndNodeKey(String ledgerAndNodeKey) { + this.ledgerAndNodeKey = ledgerAndNodeKey; + } + + public MasterAddr getMasterAddr() { + return masterAddr; + } + + public void setMasterAddr(MasterAddr masterAddr) { + this.masterAddr = masterAddr; + } + + public int getNodeId() { + return nodeId; + } + + public void setNodeId(int nodeId) { + this.nodeId = nodeId; + } + + public PeerLocalConfig getLocalConfig() { + return localConfig; + } + + public void setLocalConfig(PeerLocalConfig localConfig) { + this.localConfig = localConfig; + } + + public LedgerInitConfig getInitConfig() { + return initConfig; + } + + public void setInitConfig(LedgerInitConfig initConfig) { + this.initConfig = initConfig; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerInitConfig.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerInitConfig.java new file mode 100644 index 00000000..f31d84fe --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/LedgerInitConfig.java @@ -0,0 +1,141 @@ +package com.jd.blockchain.ump.model.config; + +import com.jd.blockchain.ump.model.PartiNode; +import com.jd.blockchain.ump.model.UmpConstant; + +import java.util.ArrayList; +import java.util.List; + +public class LedgerInitConfig { + + private String seed; + + private String name; + + private String createTime; + + private String consensusProvider; + + private int nodeSize; + + private String cryptoProviders = + "com.jd.blockchain.crypto.service.classic.ClassicCryptoService, " + + "com.jd.blockchain.crypto.service.sm.SMCryptoService"; + + + private List partiNodes = new ArrayList<>(); + + public LedgerInitConfig() { + } + + public LedgerInitConfig(String seed, String name, String createTime, String consensusProvider, int nodeSize) { + this.seed = seed; + this.name = name; + this.createTime = createTime; + this.consensusProvider = consensusProvider; + this.nodeSize = nodeSize; + } + + public List toConfigChars(String consensusConf) { + + List configChars = new ArrayList<>(); + + configChars.add(toConfigChars(UmpConstant.LEDGER_SEED_PREFIX, seed)); + + configChars.add(toConfigChars(UmpConstant.LEDGER_NAME_PREFIX, name)); + + configChars.add(toConfigChars(UmpConstant.CREATE_TIME_PREFIX, createTime)); + + configChars.add(toConfigChars(UmpConstant.CONSENSUS_PROVIDER_PREFIX, consensusProvider)); + + configChars.add(toConfigChars(UmpConstant.CONSENSUS_CONF_PREFIX, consensusConf)); + + configChars.add(toConfigChars(UmpConstant.CRYPTO_PROVIDERS_PREFIX, cryptoProviders)); + + configChars.add(toConfigChars(UmpConstant.PARTINODE_COUNT, partiNodes.size())); + + for (PartiNode partiNode : partiNodes) { + configChars.addAll(partiNode.toConfigChars()); + } + + return configChars; + } + + public String ledgerKey() { + return seed + "-" + name; + } + + public int nodeId(String pubKey) { + for (int i = 0; i < partiNodes.size(); i++) { + PartiNode partiNode = partiNodes.get(i); + if (partiNode.getPubKey().equals(pubKey)) { + return i; + } + } + throw new IllegalStateException(String.format("Can not find PubKey = %s !", pubKey)); + } + + private String toConfigChars(String prefix, Object value) { + return prefix + "=" + value; + } + + public String getSeed() { + return seed; + } + + public void setSeed(String seed) { + this.seed = seed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getConsensusProvider() { + return consensusProvider; + } + + public void setConsensusProvider(String consensusProvider) { + this.consensusProvider = consensusProvider; + } + + public int getNodeSize() { + return nodeSize; + } + + public void setNodeSize(int nodeSize) { + this.nodeSize = nodeSize; + } + + public String getCryptoProviders() { + return cryptoProviders; + } + + public void setCryptoProviders(String cryptoProviders) { + this.cryptoProviders = cryptoProviders; + } + + public List getPartiNodes() { + return partiNodes; + } + + public void setPartiNodes(List partiNodes) { + this.partiNodes = partiNodes; + } + + public void addPartiNode(PartiNode partiNode) { + this.partiNodes.add(partiNode); + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/MasterConfig.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/MasterConfig.java new file mode 100644 index 00000000..f3807773 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/MasterConfig.java @@ -0,0 +1,89 @@ +package com.jd.blockchain.ump.model.config; + +import com.jd.blockchain.ump.model.MasterAddr; + +public class MasterConfig { + + private String masterAddr; + + private int masterPort; + + private String ledgerName; + + private int nodeSize; + + private boolean isMaster = false; + + public MasterConfig() { + } + + public String getMasterAddr() { + return masterAddr; + } + + public void setMasterAddr(String masterAddr) { + this.masterAddr = masterAddr; + } + + public int getMasterPort() { + return masterPort; + } + + public void setMasterPort(int masterPort) { + this.masterPort = masterPort; + } + + public String getLedgerName() { + return ledgerName; + } + + public void setLedgerName(String ledgerName) { + this.ledgerName = ledgerName; + } + + public int getNodeSize() { + return nodeSize; + } + + public void setNodeSize(int nodeSize) { + this.nodeSize = nodeSize; + } + + public boolean isMaster() { + return isMaster; + } + + public void setMaster(boolean master) { + isMaster = master; + } + + public MasterConfig buildIsMaster(boolean isMaster) { + setMaster(isMaster); + return this; + } + + public MasterConfig buildNodeSize(int nodeSize) { + setNodeSize(nodeSize); + return this; + } + + public MasterConfig buildLedgerName(String ledgerName) { + setLedgerName(ledgerName); + return this; + } + + public MasterConfig buildMasterAddr(String masterAddr) { + setMasterAddr(masterAddr); + return this; + } + + public MasterConfig buildMasterPort(int masterPort) { + setMasterPort(masterPort); + return this; + } + + + public MasterAddr toMasterAddr() { + return MasterAddr.newInstance(masterAddr, masterPort); + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerLocalConfig.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerLocalConfig.java new file mode 100644 index 00000000..21ba3220 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerLocalConfig.java @@ -0,0 +1,160 @@ +package com.jd.blockchain.ump.model.config; + + +import com.jd.blockchain.ump.model.MasterAddr; +import com.jd.blockchain.ump.model.PartiNode; +import com.jd.blockchain.ump.model.UmpConstant; +import com.jd.blockchain.ump.model.state.LedgerMasterInstall; +import com.jd.blockchain.ump.model.state.LedgerPeerInstall; + +import java.io.File; + +/** + * Peer本地配置信息 + */ +public class PeerLocalConfig extends PeerSharedConfig { + + private String peerPath; + + private String consensusConf = "bftsmart.config"; // 默认为bftsmart配置 + + private String privKey; + + private String encodePwd; + + private String dbName; + + private MasterConfig masterConfig; + + public String bindingOutPath() { + return peerPath + UmpConstant.PATH_CONFIG; + } + + public String localConfPath() { + return peerPath + UmpConstant.PATH_LOCAL_CONFIG; + } + + public String ledgerInitConfPath() { + return peerPath + UmpConstant.PATH_LEDGER_INIT_CONFIG; + } + + public String consensusConfPath() { + return peerPath + UmpConstant.PATH_CONFIG_INIT + File.separator + consensusConf; + } + + public String libsDirectory() { + return peerPath + UmpConstant.PATH_LIBS; + } + + public String getPeerPath() { + return peerPath; + } + + public void setPeerPath(String peerPath) { + this.peerPath = peerPath; + } + + public String getConsensusConf() { + return consensusConf; + } + + public void setConsensusConf(String consensusConf) { + this.consensusConf = consensusConf; + } + + public String getPrivKey() { + return privKey; + } + + public void setPrivKey(String privKey) { + this.privKey = privKey; + } + + public String getEncodePwd() { + return encodePwd; + } + + public void setEncodePwd(String encodePwd) { + this.encodePwd = encodePwd; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public MasterConfig getMasterConfig() { + return masterConfig; + } + + public void setMasterConfig(MasterConfig masterConfig) { + this.masterConfig = masterConfig; + } + + public synchronized PartiNode toPartiNode(int nodeId) { + if (this.partiNode != null) { + return partiNode; + } + partiNode = new PartiNode(); + partiNode.setId(nodeId); + partiNode.setName(name); + partiNode.setInitHost(initAddr); + partiNode.setInitPort(initPort); + partiNode.setPubKey(pubKey); + partiNode.setSecure(false); + return partiNode; + } + + public LedgerPeerInstall toLedgerPeerInstall(int totalNodeSize) { + return new LedgerPeerInstall(name, sharedKey, peerPath, totalNodeSize); + } + + public LedgerMasterInstall.PeerInstall toPeerInstall() { + return new LedgerMasterInstall.PeerInstall(name, pubKey, initAddr, initPort, consensusNode, consensusProvider); + } + + public void verify() { + + // 主要校验dbName地址是否存在 + String dbPath = peerPath + File.separator + dbName; + File dbDir = new File(dbPath); + if (dbDir.exists()) { + throw new IllegalStateException(String.format("DB name = %s, path = %s is exist !!!", dbName, dbPath)); + } + + // 其他配置信息是否正确 + if (masterConfig == null) { + // Master不能为空 + throw new IllegalStateException("Master Config can not be NULL !!!"); + } + if (masterConfig.isMaster()) { + // 账本名字及NodeSize不能为空 + if (masterConfig.getLedgerName() == null || masterConfig.getLedgerName().length() == 0) { + throw new IllegalStateException("Master 's LedgerName can not be empty !!!"); + } + if (masterConfig.getNodeSize() == 0) { + throw new IllegalStateException("Master 's NodeSize can not be Zero !!!"); + } + } else { + // 普通Peer需要检查Master的IP地址及端口 + if (masterConfig.getMasterAddr() == null || masterConfig.getMasterAddr().length() == 0) { + throw new IllegalStateException("Master 's IP Address can not be empty !!!"); + } + + if (masterConfig.getMasterPort() == 0) { + throw new IllegalStateException("Master 's Port must be Set !!!"); + } + } + } + + public boolean master() { + return masterConfig.isMaster(); + } + + public MasterAddr masterAddr() { + return masterConfig.toMasterAddr(); + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfig.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfig.java new file mode 100644 index 00000000..cf5d71ef --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfig.java @@ -0,0 +1,86 @@ +package com.jd.blockchain.ump.model.config; + +import com.jd.blockchain.ump.model.PartiNode; + +public class PeerSharedConfig { + + public static final String DB_ROCKSDB_SUFFIX = "rocksdb_"; + + protected String sharedKey; + + protected String name; + + protected String initAddr; + + protected String pubKey; + + protected int initPort; + + protected String consensusNode; + + protected String consensusProvider = "com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider"; + + protected PartiNode partiNode; + + public String getSharedKey() { + return sharedKey; + } + + public void setSharedKey(String sharedKey) { + this.sharedKey = sharedKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getInitAddr() { + return initAddr; + } + + public void setInitAddr(String initAddr) { + this.initAddr = initAddr; + } + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + public int getInitPort() { + return initPort; + } + + public void setInitPort(int initPort) { + this.initPort = initPort; + } + + public String addr() { + return initAddr + "-" + initPort; + } + + public String getConsensusNode() { + return consensusNode; + } + + public void setConsensusNode(String consensusNode) { + this.consensusNode = consensusNode; + } + + public String getConsensusProvider() { + return consensusProvider; + } + + public void setConsensusProvider(String consensusProvider) { + this.consensusProvider = consensusProvider; + } + + +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfigVv.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfigVv.java new file mode 100644 index 00000000..6e536b32 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/config/PeerSharedConfigVv.java @@ -0,0 +1,180 @@ +package com.jd.blockchain.ump.model.config; + + +import com.jd.blockchain.ump.model.UmpConstant; +import com.jd.blockchain.ump.model.user.UserKeys; + +/** + * + */ +public class PeerSharedConfigVv { + + private String sharedKey; + + private String name; + + private int userId; + + private String pubKey; + + private String initAddr; + + private int initPort; + + private String consensusNode; + + private String peerPath = UmpConstant.PROJECT_PATH; + + private String dbName; + + private int nodeSize; + + private String masterAddr; + + private int masterPort; + + private String ledgerName; + + public String getSharedKey() { + return sharedKey; + } + + public void setSharedKey(String sharedKey) { + this.sharedKey = sharedKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public String getInitAddr() { + return initAddr; + } + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + public void setInitAddr(String initAddr) { + this.initAddr = initAddr; + } + + public int getInitPort() { + return initPort; + } + + public void setInitPort(int initPort) { + this.initPort = initPort; + } + + public String getConsensusNode() { + return consensusNode; + } + + public void setConsensusNode(String consensusNode) { + this.consensusNode = consensusNode; + } + + public String getPeerPath() { + return peerPath; + } + + public void setPeerPath(String peerPath) { + this.peerPath = peerPath; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public int getNodeSize() { + return nodeSize; + } + + public void setNodeSize(Integer nodeSize) { + this.nodeSize = nodeSize; + } + + public String getMasterAddr() { + return masterAddr; + } + + public void setMasterAddr(String masterAddr) { + this.masterAddr = masterAddr; + } + + public int getMasterPort() { + return masterPort; + } + + public void setMasterPort(Integer masterPort) { + this.masterPort = masterPort; + } + + public String getLedgerName() { + return ledgerName; + } + + public void setLedgerName(String ledgerName) { + this.ledgerName = ledgerName; + } + + public PeerLocalConfig toPeerLocalConfig(UserKeys userKeys) { + + PeerLocalConfig localConfig = new PeerLocalConfig(); + + localConfig.setSharedKey(sharedKey); + localConfig.setName(name); + localConfig.setInitAddr(initAddr); + localConfig.setInitPort(initPort); + localConfig.setConsensusNode(consensusNode); + localConfig.setPubKey(userKeys.getPubKey()); + localConfig.setPrivKey(userKeys.getPrivKey()); + localConfig.setEncodePwd(userKeys.getEncodePwd()); + localConfig.setPeerPath(peerPath); + localConfig.setDbName(dbName); + + MasterConfig masterConfig = new MasterConfig(); + + if (master()) { + masterConfig.buildIsMaster(true) + .buildLedgerName(ledgerName) + .buildNodeSize(nodeSize); + } else { + masterConfig.buildIsMaster(false) + .buildMasterAddr(masterAddr) + .buildMasterPort(masterPort); + } + + localConfig.setMasterConfig(masterConfig); + + return localConfig; + } + + private boolean master() { + if (masterAddr == null || masterAddr.length() == 0) { + return true; + } + return false; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallProcess.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallProcess.java new file mode 100644 index 00000000..a373f5be --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallProcess.java @@ -0,0 +1,21 @@ +package com.jd.blockchain.ump.model.state; + +public class InstallProcess { + + private String content; + + public InstallProcess() { + } + + public InstallProcess(String content) { + this.content = content; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallSchedule.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallSchedule.java new file mode 100644 index 00000000..66f26ebb --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/InstallSchedule.java @@ -0,0 +1,54 @@ +package com.jd.blockchain.ump.model.state; + +public class InstallSchedule { + + private String ledgerKey; + + private String ledgerAndNodeKey; + + private InstallProcess process; + + private ScheduleState state; + + public InstallSchedule() { + } + + public InstallSchedule(String ledgerKey, String ledgerAndNodeKey, InstallProcess process, ScheduleState state) { + this.ledgerKey = ledgerKey; + this.ledgerAndNodeKey = ledgerAndNodeKey; + this.process = process; + this.state = state; + } + + public String getLedgerKey() { + return ledgerKey; + } + + public void setLedgerKey(String ledgerKey) { + this.ledgerKey = ledgerKey; + } + + public String getLedgerAndNodeKey() { + return ledgerAndNodeKey; + } + + public void setLedgerAndNodeKey(String ledgerAndNodeKey) { + this.ledgerAndNodeKey = ledgerAndNodeKey; + } + + public InstallProcess getProcess() { + return process; + } + + public void setProcess(InstallProcess process) { + this.process = process; + } + + public ScheduleState getState() { + return state; + } + + public void setState(ScheduleState state) { + this.state = state; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerBindingConf.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerBindingConf.java new file mode 100644 index 00000000..7843dade --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerBindingConf.java @@ -0,0 +1,33 @@ +package com.jd.blockchain.ump.model.state; + +import java.util.Set; + +public class LedgerBindingConf { + + private Set ledgerHashs; + + private long lastTime; + + public LedgerBindingConf() { + } + + public LedgerBindingConf(long lastTime) { + this.lastTime = lastTime; + } + + public Set getLedgerHashs() { + return ledgerHashs; + } + + public void setLedgerHashs(Set ledgerHashs) { + this.ledgerHashs = ledgerHashs; + } + + public long getLastTime() { + return lastTime; + } + + public void setLastTime(long lastTime) { + this.lastTime = lastTime; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerInited.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerInited.java new file mode 100644 index 00000000..64445781 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerInited.java @@ -0,0 +1,101 @@ +package com.jd.blockchain.ump.model.state; + +public class LedgerInited { + + private String ledgerHash; + + private String ledgerName; + + private String partiName; + + private String partiAddress; + + private String dbUri; + + private StartupState startupState = StartupState.UNKNOWN; + + public LedgerInited() { + } + + public LedgerInited(String ledgerHash) { + this.ledgerHash = ledgerHash; + } + + public String getLedgerHash() { + return ledgerHash; + } + + public void setLedgerHash(String ledgerHash) { + this.ledgerHash = ledgerHash; + } + + public String getLedgerName() { + return ledgerName; + } + + public void setLedgerName(String ledgerName) { + this.ledgerName = ledgerName; + } + + public String getPartiName() { + return partiName; + } + + public void setPartiName(String partiName) { + this.partiName = partiName; + } + + public String getPartiAddress() { + return partiAddress; + } + + public void setPartiAddress(String partiAddress) { + this.partiAddress = partiAddress; + } + + public String getDbUri() { + return dbUri; + } + + public void setDbUri(String dbUri) { + this.dbUri = dbUri; + } + + public StartupState getStartupState() { + return startupState; + } + + public void setStartupState(StartupState startupState) { + this.startupState = startupState; + } + + public LedgerInited buildLedgerHash(String ledgerHash) { + setLedgerHash(ledgerHash); + return this; + } + + public LedgerInited buildLedgerName(String ledgerName) { + setLedgerName(ledgerName); + return this; + } + + public LedgerInited buildPartiName(String partiName) { + setPartiName(partiName); + return this; + } + + public LedgerInited buildPartiAddress(String partiAddress) { + setPartiAddress(partiAddress); + return this; + } + + public LedgerInited buildDbUri(String dbUri) { + setDbUri(dbUri); + return this; + } + + public LedgerInited buildStartupState(StartupState startupState) { + setStartupState(startupState); + return this; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerMasterInstall.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerMasterInstall.java new file mode 100644 index 00000000..76ca3d41 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerMasterInstall.java @@ -0,0 +1,156 @@ +package com.jd.blockchain.ump.model.state; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class LedgerMasterInstall { + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + private String ledgerKey; + + private int totalNodeSize; + + private String createTime; + + private List peerInstalls = new ArrayList<>(); + + public LedgerMasterInstall() { + } + + public LedgerMasterInstall(String ledgerKey, int totalNodeSize) { + this.ledgerKey = ledgerKey; + this.totalNodeSize = totalNodeSize; + } + + public LedgerMasterInstall initCreateTime(String createTime) { + this.createTime = createTime; + return this; + } + + public LedgerMasterInstall initCreateTime(Date date) { + this.createTime = SDF.format(date); + return this; + } + + public LedgerMasterInstall add(PeerInstall peerInstall) { + peerInstalls.add(peerInstall); + return this; + } + + public LedgerMasterInstall add(String name, String pubKey, String ipAddr, int initPort, + String consensusNode, String consensusProvider) { + PeerInstall peerInstall = new PeerInstall( + name, pubKey, ipAddr, initPort, consensusNode, consensusProvider); + return add(peerInstall); + } + + public String getLedgerKey() { + return ledgerKey; + } + + public void setLedgerKey(String ledgerKey) { + this.ledgerKey = ledgerKey; + } + + public int getTotalNodeSize() { + return totalNodeSize; + } + + public void setTotalNodeSize(int totalNodeSize) { + this.totalNodeSize = totalNodeSize; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public List getPeerInstalls() { + return peerInstalls; + } + + public void setPeerInstalls(List peerInstalls) { + this.peerInstalls = peerInstalls; + } + + public static class PeerInstall { + + private String name; + + private String pubKey; + + private String ipAddr; + + private int initPort; + + private String consensusNode; + + private String consensusProvider; + + public PeerInstall() { + } + + public PeerInstall(String name, String pubKey, String ipAddr, int initPort, String consensusNode, String consensusProvider) { + this.name = name; + this.pubKey = pubKey; + this.ipAddr = ipAddr; + this.initPort = initPort; + this.consensusNode = consensusNode; + this.consensusProvider = consensusProvider; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public int getInitPort() { + return initPort; + } + + public void setInitPort(int initPort) { + this.initPort = initPort; + } + + public String getConsensusNode() { + return consensusNode; + } + + public void setConsensusNode(String consensusNode) { + this.consensusNode = consensusNode; + } + + public String getConsensusProvider() { + return consensusProvider; + } + + public void setConsensusProvider(String consensusProvider) { + this.consensusProvider = consensusProvider; + } + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInited.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInited.java new file mode 100644 index 00000000..7026aa57 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInited.java @@ -0,0 +1,42 @@ +package com.jd.blockchain.ump.model.state; + +public class LedgerPeerInited { + + private String ledgerHash; + + private LedgerPeerInstall peerInstall; + + private StartupState startupState; + + public LedgerPeerInited() { + } + + public LedgerPeerInited(String ledgerHash, LedgerPeerInstall peerInstall) { + this.ledgerHash = ledgerHash; + this.peerInstall = peerInstall; + } + + public String getLedgerHash() { + return ledgerHash; + } + + public void setLedgerHash(String ledgerHash) { + this.ledgerHash = ledgerHash; + } + + public LedgerPeerInstall getPeerInstall() { + return peerInstall; + } + + public void setPeerInstall(LedgerPeerInstall peerInstall) { + this.peerInstall = peerInstall; + } + + public StartupState getStartupState() { + return startupState; + } + + public void setStartupState(StartupState startupState) { + this.startupState = startupState; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInstall.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInstall.java new file mode 100644 index 00000000..932d621c --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/LedgerPeerInstall.java @@ -0,0 +1,117 @@ +package com.jd.blockchain.ump.model.state; + +import com.jd.blockchain.ump.model.MasterAddr; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class LedgerPeerInstall { + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + private String ledgerKey; + + private String ledgerAndNodeKey; + + private String nodeName; + + private String sharedKey; + + private String peerPath; + + private int totalNodeSize; + + private MasterAddr masterAddr; + + private String createTime; + + public LedgerPeerInstall() { + } + + public LedgerPeerInstall(String nodeName, String sharedKey, String peerPath, int totalNodeSize) { + this.nodeName = nodeName; + this.sharedKey = sharedKey; + this.peerPath = peerPath; + this.totalNodeSize = totalNodeSize; + } + + public LedgerPeerInstall initKey(String ledgerKey, String ledgerAndNodeKey) { + this.ledgerKey = ledgerKey; + this.ledgerAndNodeKey = ledgerAndNodeKey; + return this; + } + + public LedgerPeerInstall initMasterAddr(MasterAddr masterAddr) { + this.masterAddr = masterAddr; + return this; + } + + public LedgerPeerInstall initCreateTime(Date date) { + createTime = SDF.format(date); + return this; + } + + public String getLedgerKey() { + return ledgerKey; + } + + public void setLedgerKey(String ledgerKey) { + this.ledgerKey = ledgerKey; + } + + public String getLedgerAndNodeKey() { + return ledgerAndNodeKey; + } + + public void setLedgerAndNodeKey(String ledgerAndNodeKey) { + this.ledgerAndNodeKey = ledgerAndNodeKey; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public String getSharedKey() { + return sharedKey; + } + + public void setSharedKey(String sharedKey) { + this.sharedKey = sharedKey; + } + + public String getPeerPath() { + return peerPath; + } + + public void setPeerPath(String peerPath) { + this.peerPath = peerPath; + } + + public int getTotalNodeSize() { + return totalNodeSize; + } + + public void setTotalNodeSize(int totalNodeSize) { + this.totalNodeSize = totalNodeSize; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public MasterAddr getMasterAddr() { + return masterAddr; + } + + public void setMasterAddr(MasterAddr masterAddr) { + this.masterAddr = masterAddr; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedule.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedule.java new file mode 100644 index 00000000..dc85343a --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedule.java @@ -0,0 +1,32 @@ +package com.jd.blockchain.ump.model.state; + +public class PeerInstallSchedule { + + private InstallProcess process; + + private ScheduleState state; + + public PeerInstallSchedule() { + } + + public PeerInstallSchedule(InstallProcess process, ScheduleState state) { + this.process = process; + this.state = state; + } + + public InstallProcess getProcess() { + return process; + } + + public void setProcess(InstallProcess process) { + this.process = process; + } + + public ScheduleState getState() { + return state; + } + + public void setState(ScheduleState state) { + this.state = state; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedules.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedules.java new file mode 100644 index 00000000..d6aaa7b1 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerInstallSchedules.java @@ -0,0 +1,61 @@ +package com.jd.blockchain.ump.model.state; + +import com.jd.blockchain.ump.model.config.LedgerIdentification; + +import java.util.ArrayList; +import java.util.List; + +public class PeerInstallSchedules { + + private String ledgerHash; + + private LedgerIdentification identification; + + private List installSchedules = new ArrayList<>(); + + public PeerInstallSchedules() { + } + + public PeerInstallSchedules(LedgerIdentification identification) { + this.identification = identification; + } + + public PeerInstallSchedules(LedgerIdentification identification, String ledgerHash) { + this.identification = identification; + this.ledgerHash = ledgerHash; + } + + public PeerInstallSchedules addInstallSchedule(PeerInstallSchedule installSchedule) { + this.installSchedules.add(installSchedule); + return this; + } + + public PeerInstallSchedules initLedgerHash(String ledgerHash) { + setLedgerHash(ledgerHash); + return this; + } + + public String getLedgerHash() { + return ledgerHash; + } + + public void setLedgerHash(String ledgerHash) { + this.ledgerHash = ledgerHash; + } + + public LedgerIdentification getIdentification() { + return identification; + } + + public void setIdentification(LedgerIdentification identification) { + this.identification = identification; + } + + public List getInstallSchedules() { + return installSchedules; + } + + public void setInstallSchedules(List installSchedules) { + this.installSchedules = installSchedules; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerStartupSchedules.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerStartupSchedules.java new file mode 100644 index 00000000..ba9ac9da --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/PeerStartupSchedules.java @@ -0,0 +1,40 @@ +package com.jd.blockchain.ump.model.state; + +import java.util.ArrayList; +import java.util.List; + +public class PeerStartupSchedules { + + private String peerPath; + + private List installSchedules = new ArrayList<>(); + + public PeerStartupSchedules() { + } + + public PeerStartupSchedules(String peerPath) { + this.peerPath = peerPath; + } + + + public PeerStartupSchedules addInstallSchedule(PeerInstallSchedule installSchedule) { + this.installSchedules.add(installSchedule); + return this; + } + + public List getInstallSchedules() { + return installSchedules; + } + + public void setInstallSchedules(List installSchedules) { + this.installSchedules = installSchedules; + } + + public String getPeerPath() { + return peerPath; + } + + public void setPeerPath(String peerPath) { + this.peerPath = peerPath; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/ScheduleState.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/ScheduleState.java new file mode 100644 index 00000000..6307e498 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/ScheduleState.java @@ -0,0 +1,43 @@ +package com.jd.blockchain.ump.model.state; + +public enum ScheduleState { + + /** + * 加载内容,包括获取各种数据列表 + */ + LOAD, + LOAD_SUCCESS, // 加载成功 + LOAD_FAIL, // 加载失败 + + /** + * 将获取的数据写入文件 + * + */ + WRITE, + WRITE_SUCCESS, // 写入文件成功 + WRITE_FAIL, // 写入文件失败 + + /** + * Ledger_INIT:账本初始化过程 + * 主要是调用SHELL + * + */ + INIT, + INIT_SUCCESS, // 账本初始化成功 + INIT_FAIL, // 账本初始化失败 + + /** + * 无须启动PEER,等待PEER自动更新账本信息 + */ + NO_STARTUP, + + /** + * 启动Peer节点 + */ + STARTUP_START, + STARTUP_OVER, + STARTUP_SUCCESS, // Peer节点启动成功 + STARTUP_FAIL, // Peer节点启动失败 + ; + +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/StartupState.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/StartupState.java new file mode 100644 index 00000000..d605ccd8 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/state/StartupState.java @@ -0,0 +1,48 @@ +package com.jd.blockchain.ump.model.state; + +public enum StartupState { + + /** + * UNEXIST + * 不存在,描述该账本Hash曾经创建,但目前在LedgerBinding.conf文件中不存在 + * 此状态不支持任何其他操作 + */ + UNEXIST, + + /** + * UNLOAD + * 账本存在,但未加载 + * 此状态可以启动,不能停止 + */ + UNLOAD, + + /** + * LOADING + * 账本加载中,说明程序已经启动,但尚未加载该程序 + * 此状态不可以启动,不建议停止 + */ + LOADING, + + /** + * LOADED + * 账本已加载 + * 此状态不可以启动,后续可以支持停止操作 + */ + LOADED, + + /** + * UNKNOWN + * 未知,常见于命令检测执行错误或程序启动,但账本尚未加载完成 + * 此状态不支持任何其他操作 + */ + UNKNOWN, + + /** + * DB_UNEXIST + * 该账本对应的数据库不存在 + * 此状态不支持任何其他操作 + */ + DB_UNEXIST, + + ; +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeyBuilder.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeyBuilder.java new file mode 100644 index 00000000..e3967c01 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeyBuilder.java @@ -0,0 +1,34 @@ +package com.jd.blockchain.ump.model.user; + +public class UserKeyBuilder { + + private String name; + + private String seed; + + private String pwd; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSeed() { + return seed; + } + + public void setSeed(String seed) { + this.seed = seed; + } + + public String getPwd() { + return pwd; + } + + public void setPwd(String pwd) { + this.pwd = pwd; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeys.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeys.java new file mode 100644 index 00000000..121d3ed7 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeys.java @@ -0,0 +1,68 @@ +package com.jd.blockchain.ump.model.user; + +public class UserKeys { + + private int id; + + private String name; + + private String privKey; + + private String pubKey; + + private String encodePwd; + + public UserKeys() { + } + + public UserKeys(String name, String privKey, String pubKey, String encodePwd) { + this.name = name; + this.privKey = privKey; + this.pubKey = pubKey; + this.encodePwd = encodePwd; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPrivKey() { + return privKey; + } + + public void setPrivKey(String privKey) { + this.privKey = privKey; + } + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + public String getEncodePwd() { + return encodePwd; + } + + public void setEncodePwd(String encodePwd) { + this.encodePwd = encodePwd; + } + + public UserKeysVv toUserKeysVv() { + return new UserKeysVv(id, name, privKey, pubKey); + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeysVv.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeysVv.java new file mode 100644 index 00000000..90eaae9c --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/user/UserKeysVv.java @@ -0,0 +1,69 @@ +package com.jd.blockchain.ump.model.user; + +public class UserKeysVv { + + public static final int PRIVKEY_HEADER_LENGTH = 4; + + public static final int PRIVKEY_TAIL_LENGTH = 8; + + public static final String PRIVKEY_HIDE_CONTENT = "******"; + + private int id; + + private String name; + + private String privKey; + + private String pubKey; + + public UserKeysVv() { + } + + public UserKeysVv(int id, String name, String privKey, String pubKey) { + this.id = id; + this.name = name; + this.privKey = encodePrivKey(privKey); + this.pubKey = pubKey; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPrivKey() { + return privKey; + } + + public void setPrivKey(String privKey) { + this.privKey = privKey; + } + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + private String encodePrivKey(final String privKey) { + if (privKey != null && privKey.length() > (PRIVKEY_HEADER_LENGTH + PRIVKEY_TAIL_LENGTH)) { + return privKey.substring(0, PRIVKEY_HEADER_LENGTH) + + PRIVKEY_HIDE_CONTENT + + privKey.substring(privKey.length() - PRIVKEY_TAIL_LENGTH); + } + return privKey; + } +} diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/ErrorCode.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/ErrorCode.java new file mode 100644 index 00000000..ca023e05 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/ErrorCode.java @@ -0,0 +1,20 @@ +package com.jd.blockchain.ump.model.web; + +/** + * 错误代码; + */ +public enum ErrorCode { + + UNEXPECTED(5000), + ; + + private int value; + + ErrorCode(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} \ No newline at end of file diff --git a/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/WebResponse.java b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/WebResponse.java new file mode 100644 index 00000000..29890518 --- /dev/null +++ b/source/manager/ump-model/src/main/java/com/jd/blockchain/ump/model/web/WebResponse.java @@ -0,0 +1,97 @@ +package com.jd.blockchain.ump.model.web; + +public class WebResponse { + + private boolean success; + + private T data; + + private ErrorMessage error; + + private WebResponse(){ + + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public ErrorMessage getError() { + return error; + } + + public void setError(ErrorMessage error) { + this.error = error; + } + + public static WebResponse createSuccessResult(Object data){ + WebResponse responseResult = new WebResponse(); + responseResult.setSuccess(true); + responseResult.setData(data); + return responseResult; + } + + public static WebResponse createFailureResult(int code, String message){ + ErrorMessage errorMessage = new ErrorMessage(code, message); + return createFailureResult(errorMessage); + } + + public static WebResponse createFailureResult(ErrorMessage errorMessage){ + WebResponse responseResult = new WebResponse(); + responseResult.setSuccess(false); + responseResult.setError(errorMessage); + return responseResult; + } + + + + /** + * 错误消息实体 + * + * @author liuxrb + * + */ + public static class ErrorMessage { + + private int errorCode; + + private String errorMessage; + + public ErrorMessage() { + + } + + public ErrorMessage(int errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + public int getErrorCode() { + return errorCode; + } + + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + } +} diff --git a/source/manager/ump-service/pom.xml b/source/manager/ump-service/pom.xml new file mode 100644 index 00000000..99b5f2c4 --- /dev/null +++ b/source/manager/ump-service/pom.xml @@ -0,0 +1,114 @@ + + + + + manager + com.jd.blockchain + 1.1.0-SNAPSHOT + + 4.0.0 + + ump-service + + ump-service + + + UTF-8 + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-devtools + true + + + + com.jd.blockchain + crypto-classic + ${project.version} + + + + com.jd.blockchain + crypto-sm + ${project.version} + + + + com.jd.blockchain + tools-keygen + ${project.version} + + + + com.alibaba + fastjson + + + + commons-io + commons-io + + + + commons-codec + commons-codec + + + + com.jd.blockchain + ump-model + ${project.version} + + + + org.reflections + reflections + + + + org.apache.httpcomponents + httpclient + + + + com.sun + tools + 1.8 + system + ${project.basedir}/../ump-booter/libs/tools.jar + + + + junit + junit + test + + + diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerService.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerService.java new file mode 100644 index 00000000..77c11016 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerService.java @@ -0,0 +1,31 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.ump.model.state.LedgerBindingConf; +import com.jd.blockchain.ump.model.state.LedgerInited; + +import java.util.List; + +public interface LedgerService { + + String randomSeed(); + + String currentCreateTime(); + + String ledgerInitCommand(String peerPath); + + String peerStartCommand(String peerPath); + + LedgerBindingConf allLedgerHashs(String peerPath); + + LedgerBindingConf allLedgerHashs(long lastTime, String peerPath); + + List allLedgerIniteds(String peerPath); + + boolean dbExist(String peerPath, String ledgerHash); + + String peerVerifyKey(String peerPath); + + void save(String ledgerAndNodeKey, String ledgerHash); + + String readLedgerHash(String ledgerAndNodeKey); +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerServiceHandler.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerServiceHandler.java new file mode 100644 index 00000000..f78aef42 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/LedgerServiceHandler.java @@ -0,0 +1,310 @@ +package com.jd.blockchain.ump.service; + + +import com.jd.blockchain.ump.dao.DBConnection; +import com.jd.blockchain.ump.model.UmpConstant; +import com.jd.blockchain.ump.model.state.LedgerBindingConf; +import com.jd.blockchain.ump.model.state.LedgerInited; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.FileUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.*; + +@Service +public class LedgerServiceHandler implements LedgerService { + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); + + private static final String LEDGER_HASHS_FLAG = "ledger.bindings"; + + private static final String LEDGER_NAME_FORMAT = "binding.%s.name"; + + private static final String LEDGER_PARTI_ADDRESS_FORMAT = "binding.%s.parti.address"; + + private static final String LEDGER_PARTI_NAME_FORMAT = "binding.%s.parti.name"; + + private static final String LEDGER_DB_FORMAT = "binding.%s.db.uri"; + + private static final String FILE_PEER_FLAG = "deployment-peer"; + + private static final String JAR_SUFFIX = "jar"; + + private static final int SEED_BYTES_LENGTH = 32; + + private static final int NAME_BYTES_LENGTH = 8; + + private static final int SEED_PART_LENGTH = 8; + + private static final Random LEDGER_RANDOM = new Random(); + + @Autowired + private UmpStateService umpStateService; + + @Autowired + private DBConnection dbConnection; + + @Override + public String randomSeed() { + byte[] seedBytes = new byte[SEED_BYTES_LENGTH]; + + LEDGER_RANDOM.nextBytes(seedBytes); + + char[] seedChars = Hex.encodeHex(seedBytes); + + StringBuilder sBuilder = new StringBuilder(); + + for (int i = 0; i < seedChars.length; i++) { + if (i != 0 && i % SEED_PART_LENGTH == 0) { + sBuilder.append("-"); + } + sBuilder.append(seedChars[i]); + } + + return sBuilder.toString(); + } + + @Override + public String currentCreateTime() { + return SDF.format(new Date()); + } + + @Override + public String ledgerInitCommand(String peerPath) { + + return String.format(UmpConstant.CMD_LEDGER_INIT, + peerPath + UmpConstant.PATH_LEDGER_INIT_BIN); + } + + @Override + public String peerStartCommand(String peerPath) { + return String.format(UmpConstant.CMD_START_UP_FORMAT, + peerPath + UmpConstant.PATH_STARTUP_BIN); + } + + @Override + public LedgerBindingConf allLedgerHashs(String peerPath) { + + return allLedgerHashs(0L, peerPath); + } + + @Override + public LedgerBindingConf allLedgerHashs(long lastTime, String peerPath) { + + // 读取LedgerBingConf文件,假设该文件不存在则返回空值 + Set allLedgerHashs = new HashSet<>(); + + PropAndTime propAndTime = loadLedgerBindingConf(lastTime, peerPath); + + Properties props = propAndTime.getProp(); + + if (props != null) { + + String ledgerHashChars = props.getProperty(LEDGER_HASHS_FLAG); + + if (ledgerHashChars != null && ledgerHashChars.length() > 0) { + String[] ledgerHashArray = ledgerHashChars.split(","); + if (ledgerHashArray.length > 0) { + for (String ledgerHash : ledgerHashArray) { + allLedgerHashs.add(ledgerHash.trim()); + } + } + } + } + + LedgerBindingConf ledgerBindingConf = new LedgerBindingConf(propAndTime.getLastTime()); + + ledgerBindingConf.setLedgerHashs(allLedgerHashs); + + return ledgerBindingConf; + } + + @Override + public List allLedgerIniteds(String peerPath) { + + List ledgerIniteds = new ArrayList<>(); + + PropAndTime propAndTime = loadLedgerBindingConf(0L, peerPath); + + Properties props = propAndTime.getProp(); + + if (props != null) { + + String ledgerHashChars = props.getProperty(LEDGER_HASHS_FLAG); + + Set ledgerHashSet = new HashSet<>(); + + if (ledgerHashChars != null && ledgerHashChars.length() > 0) { + String[] ledgerHashArray = ledgerHashChars.split(","); + if (ledgerHashArray.length > 0) { + for (String ledgerHash : ledgerHashArray) { + ledgerHashSet.add(ledgerHash.trim()); + } + } + } + + // 根据Hash值,遍历Prop + for (String hash : ledgerHashSet) { + + LedgerInited ledgerInited = new LedgerInited(hash); + + String ledgerName = props.getProperty(String.format(LEDGER_NAME_FORMAT, hash)); + + String partiAddress = props.getProperty(String.format(LEDGER_PARTI_ADDRESS_FORMAT, hash)); + + String partiName = props.getProperty(String.format(LEDGER_PARTI_NAME_FORMAT, hash)); + + String dbUri = props.getProperty(String.format(LEDGER_DB_FORMAT, hash)); + + ledgerIniteds.add( + ledgerInited + .buildLedgerName(ledgerName) + .buildPartiAddress(partiAddress) + .buildPartiName(partiName) + .buildDbUri(dbUri)); + } + } + return ledgerIniteds; + } + + @Override + public synchronized boolean dbExist(String peerPath, String ledgerHash) { + // 检查该账本对应的数据库是否存在 + + PropAndTime propAndTime = loadLedgerBindingConf(0L, peerPath); + + // binding.j5faRYSqSqSRmSVgdmPsgq7Hzd1yP7yAGPWkTihekWms94.db.uri=rocksdb:///Users/shaozhuguang/Documents/ideaProjects/jdchain-patch/source/test/test-integration/rocks.db/rocksdb4.db + Properties props = propAndTime.getProp(); + + if (props != null) { + String dbKey = String.format(LEDGER_DB_FORMAT, ledgerHash); + + String dbUri = props.getProperty(dbKey); + + if (dbUri != null && dbUri.length() > 0) { + + return dbConnection.exist(dbUri); + } + } + + return false; + } + + @Override + public String peerVerifyKey(String peerPath) { + // 从libs中读取对应的Peer.jar的文件名称,配合全路径 + File libsDirectory = new File(peerPath + UmpConstant.PATH_SYSTEM); + + Collection jars = FileUtils.listFiles(libsDirectory, new String[]{JAR_SUFFIX}, false); + + String peerVerifyKey = null; + + if (!jars.isEmpty()) { + for (File jar : jars) { + String jarName = jar.getName(); + if (jarName.startsWith(FILE_PEER_FLAG)) { + peerVerifyKey = jar.getPath(); + break; + } + } + } + + return peerVerifyKey; + } + + @Override + public void save(String ledgerAndNodeKey, String ledgerHash) { + // 保存LedgerAndNodeKey与账本关系 + umpStateService.saveLedgerHash(ledgerAndNodeKey, ledgerHash); + } + + @Override + public String readLedgerHash(String ledgerAndNodeKey) { + + return umpStateService.readLedgerHash(ledgerAndNodeKey); + } + + private PropAndTime loadLedgerBindingConf(long lastTime, String peerPath) { + + File ledgerBindingConf = new File(peerPath + UmpConstant.PATH_LEDGER_BINDING_CONFIG); + + PropAndTime propAndTime = new PropAndTime(lastTime); + + // 说明被修改过 + if (ledgerBindingConf.exists() && ledgerBindingConf.lastModified() > lastTime) { + + propAndTime.lastTime = ledgerBindingConf.lastModified(); + + try (InputStream inputStream = new FileInputStream(ledgerBindingConf)) { + + Properties props = new Properties(); + + props.load(inputStream); + + propAndTime.prop = props; + + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + return propAndTime; + } + + private static class PropAndTime { + + private Properties prop; + + private long lastTime; + + public PropAndTime() { + } + + public PropAndTime(long lastTime) { + this.lastTime = lastTime; + } + + public Properties getProp() { + return prop; + } + + public void setProp(Properties prop) { + this.prop = prop; + } + + public long getLastTime() { + return lastTime; + } + + public void setLastTime(long lastTime) { + this.lastTime = lastTime; + } + } + +// private Properties loadLedgerBindingConf(String peerPath) { +// +// File ledgerBindingConf = new File(peerPath + UmpConstant.PATH_LEDGER_BINDING_CONFIG); +// +// if (ledgerBindingConf.exists()) { +// +// try (InputStream inputStream = new FileInputStream(ledgerBindingConf)) { +// +// Properties props = new Properties(); +// +// props.load(inputStream); +// +// return props; +// +// } catch (Exception e) { +// throw new IllegalStateException(e); +// } +// } +// +// return null; +// } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpService.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpService.java new file mode 100644 index 00000000..08e1ee4a --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpService.java @@ -0,0 +1,36 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.ump.model.MasterAddr; +import com.jd.blockchain.ump.model.PeerSharedConfigs; +import com.jd.blockchain.ump.model.config.*; +import com.jd.blockchain.ump.model.state.PeerInstallSchedules; +import com.jd.blockchain.ump.model.state.PeerStartupSchedules; + + +public interface UmpService { + + PeerSharedConfigs loadPeerSharedConfigs(PeerLocalConfig sharedConfig); + + LedgerConfig response(PeerSharedConfigs peerSharedConfigs, PeerLocalConfig localConfig); + + String save(MasterAddr masterAddr, LedgerConfig ledgerConfig, PeerLocalConfig localConfig); + + String ledgerAndNodeKey(LedgerConfig ledgerConfig, PeerSharedConfig sharedConfig); + + PeerInstallSchedules install(LedgerIdentification identification, PeerLocalConfig localConfig, String ledgerAndNodeKey); + + PeerInstallSchedules install(String ledgerAndNodeKey); + + PeerInstallSchedules init(String ledgerAndNodeKey); + + PeerInstallSchedules init(LedgerIdentification identification, PeerLocalConfig localConfig, String ledgerAndNodeKey); + +// PeerInstallSchedules startup(String ledgerAndNodeKey); + + PeerStartupSchedules startup(); + + boolean stop(String ledgerAndNodeKey); + + boolean stop(); + +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpServiceHandler.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpServiceHandler.java new file mode 100644 index 00000000..c17cc309 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpServiceHandler.java @@ -0,0 +1,925 @@ +package com.jd.blockchain.ump.service; + + +import com.jd.blockchain.ump.dao.DBConnection; +import com.jd.blockchain.ump.dao.RocksDBConnection; +import com.jd.blockchain.ump.model.MasterAddr; +import com.jd.blockchain.ump.model.PeerSharedConfigs; +import com.jd.blockchain.ump.model.UmpConstant; +import com.jd.blockchain.ump.model.config.*; +import com.jd.blockchain.ump.model.state.*; +import com.jd.blockchain.ump.service.consensus.ConsensusService; +import com.jd.blockchain.ump.util.Base58Utils; +import com.jd.blockchain.ump.util.CommandUtils; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; + +@Service +public class UmpServiceHandler implements UmpService { + + private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + private static final String SUCCESS = "SUCCESS"; + + private static final String ROCKSDB_PROTOCOL = RocksDBConnection.ROCKSDB_PROTOCOL; + + private static final int DB_SUFFIX_LENGTH = 4; + + private static final Random DB_RANDOM = new Random(); + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMddHHmmssSSS");//yyyy-MM-dd HH:mm:ss为目标的样式 + + private final Map ledgerConfigs = new ConcurrentHashMap<>(); + + private final Map masterConfigs = new ConcurrentHashMap<>(); + + private final Map peerShareds = new ConcurrentHashMap<>(); + + private final Map ledgerConfigMemory = new ConcurrentHashMap<>(); + + @Autowired + private ConsensusService consensusService; + + @Autowired + private LedgerService ledgerService; + + @Autowired + private DBConnection dbConnection; + + @Autowired + private UmpStateService umpStateService; + + @Override + public synchronized PeerSharedConfigs loadPeerSharedConfigs(PeerLocalConfig sharedConfig) { + + String sharedKey = sharedConfig.getSharedKey(); + + PeerSharedConfigs peerSharedConfigs = peerShareds.get(sharedKey); + + if (peerSharedConfigs == null) { + peerSharedConfigs = new PeerSharedConfigs(); + peerShareds.put(sharedKey, peerSharedConfigs); + } + + return peerSharedConfigs.addConfig(sharedConfig); + } + + @Override + public LedgerConfig response(PeerSharedConfigs sharedConfigs, PeerLocalConfig localConfig) { + try { + // 对于Master和Peer处理方式不同 + if (localConfig.getMasterConfig().isMaster()) { + + // Master节点需要等待完成后通知其他线程 + sharedConfigs.waitAndNotify(); + } else { + + // 等待Master节点通知 + sharedConfigs.await(); + } + + // 此处需要防止并发 + final String sharedKey = sharedConfigs.getSharedKey(); + + LedgerConfig savedLedgerConfig = ledgerConfigMemory.get(sharedKey); + + if (savedLedgerConfig != null) { + return savedLedgerConfig; + } + + // 获取当前对象锁(所有节点请求使用同一个对象) + final Lock lock = sharedConfigs.getLock(); + + lock.lock(); + + try { + // 执行到此表示获取到锁,此时需要判断是否有数据 + // Double Check !!! + savedLedgerConfig = ledgerConfigMemory.get(sharedKey); + + if (savedLedgerConfig != null) { + return savedLedgerConfig; + } + + // 校验 + verify(sharedConfigs); + + // 所有数据到达之后生成返回的应答 + LedgerInitConfig initConfig = sharedConfigs.ledgerInitConfig( + ledgerService.randomSeed(), ledgerService.currentCreateTime()); + + // 生成共识文件 + String consensusConfig = consensusService.initConsensusConf( + sharedConfigs.getConsensusProvider(), sharedConfigs.getSharedConfigs()); + + LedgerConfig ledgerConfig = new LedgerConfig(initConfig, consensusConfig); + + // 将本次LedgerKey信息写入数据库 + String ledgerKey = initConfig.ledgerKey(); + + dbConnection.put(ledgerKey, ledgerConfig, LedgerConfig.class); + + // 将节点的Key信息写入数据库 + umpStateService.save(ledgerKey, sharedConfigKeys(ledgerKey, sharedConfigs)); + + // 将本地生成数据的信息写入数据库 + LedgerMasterInstall masterInstall = sharedConfigs.toLedgerMasterInstall(); + + umpStateService.save(masterInstall); + + // 将数据放入内存 + ledgerConfigMemory.put(sharedKey, ledgerConfig); + + return ledgerConfig; + } finally { + lock.unlock(); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public String save(MasterAddr masterAddr, LedgerConfig ledgerConfig, PeerLocalConfig localConfig) { + + String ledgerAndNodeKey = ledgerAndNodeKey(ledgerConfig, localConfig); + + ledgerConfigs.put(ledgerAndNodeKey, ledgerConfig); + + // 保存本次需要发送的Master地址 + masterConfigs.put(ledgerAndNodeKey, localConfig.getMasterConfig()); + + // 保存所有的信息至本地 + umpStateService.save(ledgerAndNodeKey, localConfig); + + // 保存当前同步信息至数据库 + LedgerPeerInstall peerInstall = localConfig.toLedgerPeerInstall(ledgerConfig.getInitConfig().getNodeSize()); + + // init相关配置信息 + peerInstall + .initKey(ledgerConfig.getInitConfig().ledgerKey(), ledgerAndNodeKey) + .initCreateTime(new Date()) + .initMasterAddr(masterAddr); + + // 写入数据库 + umpStateService.save(peerInstall); + + return ledgerAndNodeKey; + } + + @Override + public String ledgerAndNodeKey(LedgerConfig ledgerConfig, PeerSharedConfig sharedConfig) { + + return ledgerAndNodeKey(ledgerConfig.getInitConfig().ledgerKey(), sharedConfig); + } + + @Override + public PeerInstallSchedules install(LedgerIdentification identification, PeerLocalConfig localConfig, String ledgerAndNodeKey) { + + // 初始化Peer节点数据 + PeerInstallSchedules installSchedules = init(identification, localConfig, ledgerAndNodeKey); + + // Peer节点启动 + peerStart(localConfig.getPeerPath(), installSchedules); + + return installSchedules; + } + + @Override + public PeerInstallSchedules install(String ledgerAndNodeKey) { + + PeerLocalConfig localConfig = umpStateService.readConfig(ledgerAndNodeKey); + + if (localConfig != null) { + + // 获取LedgerIdentification + LedgerIdentification identification = umpStateService.readIdentification(ledgerAndNodeKey); + + return install(identification, localConfig, ledgerAndNodeKey); + } + throw new IllegalStateException("Can not find LocalConfig from DataBase !!!"); + } + + @Override + public PeerInstallSchedules init(LedgerIdentification identification, PeerLocalConfig localConfig, String ledgerAndNodeKey) { + + PeerInstallSchedules installSchedules = new PeerInstallSchedules(identification); + + MasterAddr masterAddr = loadMaster(localConfig); + + LedgerConfig ledgerConfig = ledgerConfigs.get(ledgerAndNodeKey); + + if (ledgerConfig == null || ledgerConfig.getInitConfig() == null) { + saveInstallSchedule(installSchedules, masterAddr, "", ledgerAndNodeKey, + String.format("Ledger Key = [%s] can not find Ledger-Config !!!", ledgerAndNodeKey), + ScheduleState.LOAD_FAIL); + throw new IllegalStateException(String.format("Ledger Key = [%s] can not find Ledger-Config !!!", ledgerAndNodeKey)); + } + + LedgerInitConfig initConfig = ledgerConfig.getInitConfig(); + + String ledgerKey = initConfig.ledgerKey(); + + List localConfContents, ledgerInitContents; + + try { + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Find LedgerConfig from Memory for Key [%s] -> %s", ledgerAndNodeKey, SUCCESS), + ScheduleState.LOAD); + + // 首先获取当前节点的ID + int nodeId = initConfig.nodeId(localConfig.getPubKey()); + + // 生成local.conf文件内容 + localConfContents = localConfContents(localConfig, nodeId); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Init Local.Conf's Content -> %s", SUCCESS), + ScheduleState.LOAD); + + // 生成LedgerInit内容 + ledgerInitContents = initConfig.toConfigChars(localConfig.consensusConfPath()); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Init Ledger.Init's Content -> %s", SUCCESS), + ScheduleState.LOAD); + } catch (Exception e) { + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + "Load Config's Content !!!", + ScheduleState.LOAD_FAIL); + throw new IllegalStateException(e); + } + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + "Load Config's Content !!!", + ScheduleState.LOAD_SUCCESS); + + try { + // 将该文件内容写入Local.Conf + forceWrite(localConfContents, new File(localConfig.localConfPath())); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Write And Backup File local.conf -> %s", SUCCESS), + ScheduleState.WRITE); + + // 将文件内容写入Ledger-Init + forceWrite(ledgerInitContents, new File(localConfig.ledgerInitConfPath())); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Write And Backup File ledger.init -> %s", SUCCESS), + ScheduleState.WRITE); + + // 将共识内容写入文件,例如bftsmart.conf + String consensusFileName = writeConsensusContent(ledgerConfig.getConsensusConfig(), + new File(localConfig.consensusConfPath())); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Write And Backup Consensus File %s -> %s", consensusFileName, SUCCESS), + ScheduleState.WRITE); + + } catch (Exception e) { + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + "Write Config's Content to Config File !!!", + ScheduleState.WRITE_FAIL); + throw new IllegalStateException(e); + } + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + "Write Config's Content to Config File !!!", + ScheduleState.WRITE_SUCCESS); + + // 账本初始化 + String ledgerHash = ledgerInit(localConfig.getPeerPath(), installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey); + + // 设置账本Hash + installSchedules.setLedgerHash(ledgerHash); + + return installSchedules; + } + + @Override + public PeerInstallSchedules init(String ledgerAndNodeKey) { + + PeerLocalConfig localConfig = umpStateService.readConfig(ledgerAndNodeKey); + + if (localConfig != null) { + + // 获取LedgerIdentification + LedgerIdentification identification = umpStateService.readIdentification(ledgerAndNodeKey); + + return init(identification, localConfig, ledgerAndNodeKey); + } + throw new IllegalStateException("Can not find LocalConfig from DataBase !!!"); + } + + +// @Override +// public PeerInstallSchedules startup(String ledgerAndNodeKey) { +// +// PeerLocalConfig localConfig = umpStateService.readConfig(ledgerAndNodeKey); +// +// if (localConfig != null) { +// +// PeerInstallSchedules installSchedules = umpStateService.loadState(ledgerAndNodeKey); +// +// // Peer节点启动 +// return peerStart(localConfig.getPeerPath(), installSchedules); +// +// } +// throw new IllegalStateException("Can not find LocalConfig from DataBase !!!"); +// } + + @Override + public PeerStartupSchedules startup() { + + PeerStartupSchedules startupSchedules = new PeerStartupSchedules(UmpConstant.PROJECT_PATH); + + return peerStart(startupSchedules); + } + + @Override + public boolean stop(String ledgerAndNodeKey) { + PeerLocalConfig localConfig = umpStateService.readConfig(ledgerAndNodeKey); + + if (localConfig != null) { + + // Peer节点停止 + return peerStop(localConfig.getPeerPath()); + } + throw new IllegalStateException("Can not find LocalConfig from DataBase !!!"); + } + + @Override + public boolean stop() { + + return peerStop(UmpConstant.PROJECT_PATH); + } + + private MasterAddr loadMaster(PeerLocalConfig localConfig) { + + // 开始安装之后则可以将内存中的数据释放 + String sharedKey = localConfig.getSharedKey(); + + if (sharedKey != null) { + ledgerConfigMemory.remove(sharedKey); + } + + if (localConfig.master()) { + return null; + } + + return localConfig.masterAddr(); + } + + private List sharedConfigKeys(String ledgerKey, PeerSharedConfigs sharedConfigs) { + + List sharedConfigKeys = new ArrayList<>(); + + List pscs = sharedConfigs.getSharedConfigs(); + + for(PeerSharedConfig psc : pscs) { + sharedConfigKeys.add(ledgerAndNodeKey(ledgerKey, psc)); + } + + return sharedConfigKeys; + } + + private String ledgerAndNodeKey(String ledgerKey, PeerSharedConfig sharedConfig) { + + return ledgerKey + "-" + sharedConfig.getName(); + } + + private String ledgerInit(String peerPath, PeerInstallSchedules installSchedules, MasterAddr masterAddr, String ledgerKey, String ledgerAndNodeKey) { + + String newLedgerHash = ""; + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + "Steps to start processing LedgerInit !!!", + ScheduleState.INIT); + + // 获取当前已经存在的Ledger列表 + LedgerBindingConf ledgerBindingConf = ledgerService.allLedgerHashs(peerPath); + + Set currentLedgerHashs = ledgerBindingConf.getLedgerHashs(); + + long lastTime = ledgerBindingConf.getLastTime(); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Find History Ledger's Size = %s", currentLedgerHashs.size()), + ScheduleState.INIT); + + String ledgerInitCommand = ledgerService.ledgerInitCommand(peerPath); + + try { + + LOGGER.info("Execute Ledger-Init's Shell {}", ledgerInitCommand); + + Process ledgerInitProcess; + + try { + // 调用ledgerInit初始化脚本 + ledgerInitProcess = CommandUtils.execute(CommandUtils.toCommandList(ledgerInitCommand)); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Execute LedgerInit's Command -> %s", SUCCESS), + ScheduleState.INIT); + } catch (Exception e) { + LOGGER.error("Execute Ledger-Init's Shell !!!", e); + throw new IllegalStateException(e); + } + + int maxSize = 512; + + boolean isInitSuccess = false; + + int checkIndex = 1; + + while (maxSize > 0) { + // 时延 + Thread.sleep(6000); + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("%s Check LedgerInit's Status ...... ", checkIndex++), + ScheduleState.INIT); + + // 检查账本是否增加 + CurrentLedger currentLedger = checkNewLedger(lastTime, peerPath, currentLedgerHashs); + + lastTime = currentLedger.getLastTime(); + + newLedgerHash = currentLedger.getLedgerHash(); + + if (newLedgerHash != null && newLedgerHash.length() > 0) { + isInitSuccess = true; + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Find New Ledger = %s", newLedgerHash), + ScheduleState.INIT); + break; + } + maxSize --; + } + + // 完成后,不管是否处理完,都将命令停止 + // 为防止其他应用仍在访问,延时6秒停止 + try { + Thread.sleep(6000); + ledgerInitProcess = ledgerInitProcess.destroyForcibly(); + if (ledgerInitProcess.isAlive()) { + // 再尝试一次 + ledgerInitProcess.destroyForcibly(); + } + } catch (Exception e) { + // 暂时打印日志 + LOGGER.error("Stop Ledger Init Command !!!", e); + } + + // 再次判断是否初始化账本成功 + if (newLedgerHash == null) { + + CurrentLedger currentLedger = checkNewLedger(lastTime, peerPath, currentLedgerHashs); + + newLedgerHash = currentLedger.getLedgerHash(); + + if (newLedgerHash != null && newLedgerHash.length() > 0) { + isInitSuccess = true; + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Final Find New Ledger = %s", newLedgerHash), + ScheduleState.INIT); + } + } + + if (!isInitSuccess) { + // 失败则抛出异常 + throw new IllegalStateException("Can Not Find New Ledger !!!"); + } + } catch (Exception e) { + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + "Execute Ledger-Init Command Fail !!!", + ScheduleState.INIT_FAIL); + LOGGER.error("Execute Ledger-Init Command Fail !!!", e); + throw new IllegalStateException(e); + } + + saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, + String.format("Steps to processing LedgerInit -> %s", SUCCESS), + ScheduleState.INIT_SUCCESS); + + // 将账本Hash写入数据库 + ledgerService.save(ledgerAndNodeKey, newLedgerHash); + + return newLedgerHash; + } + + private CurrentLedger checkNewLedger(long lastTime, String peerPath, Set currentLedgerHashs) { + // 再次判断是否初始化账本成功 + LedgerBindingConf ledgerBindingConf = ledgerService.allLedgerHashs(lastTime, peerPath); + + Set newLedgerHashs = ledgerBindingConf.getLedgerHashs(); + + CurrentLedger currentLedger = new CurrentLedger(ledgerBindingConf.getLastTime()); + + if (newLedgerHashs.size() > currentLedgerHashs.size()) { + // 获取其新安装的LedgerHash + for (String ledgerHash : newLedgerHashs) { + if (!currentLedgerHashs.contains(ledgerHash)) { + // 新获取的LedgerHash为当前值 + currentLedger.ledgerHash = ledgerHash; + break; + } + } + } + return currentLedger; + } + + + private PeerInstallSchedules peerStart(String peerPath, PeerInstallSchedules installSchedules) { + + saveInstallSchedule(installSchedules, + "Steps to start processing PeerNodeStart !!!", + ScheduleState.STARTUP_START); + // 启动Peer + // 说明初始化成功 + // 判断是否需要启动Peer + String peerVerify = ledgerService.peerVerifyKey(peerPath); + + try { + if (!CommandUtils.isActive(peerVerify)) { + // 不存在,则需要再启动 + String peerStartCmd = ledgerService.peerStartCommand(peerPath); + + LOGGER.info("Execute Peer-Startup's Shell {}", peerStartCmd); + + if (!CommandUtils.executeAndVerify(CommandUtils.toCommandList(peerStartCmd), peerVerify)) { + // Peer节点启动失败 + throw new IllegalStateException("Peer Node Start UP Fail !!!"); + } + saveInstallSchedule(installSchedules, + String.format("Peer's process %s start -> %s", peerVerify, SUCCESS), + ScheduleState.STARTUP_SUCCESS); + } else { + // 命令已经存在 + saveInstallSchedule(installSchedules, + String.format("Peer's process is exist -> %s", peerVerify), + ScheduleState.NO_STARTUP); + } + } catch (Exception e) { + saveInstallSchedule(installSchedules, + e.getMessage(), + ScheduleState.STARTUP_FAIL); + throw new IllegalStateException(e); + } + + saveInstallSchedule(installSchedules, + "Steps to start processing PeerNodeStart over !!!", + ScheduleState.STARTUP_OVER); + + return installSchedules; + } + + private PeerStartupSchedules peerStart(PeerStartupSchedules startupSchedules) { + + String peerPath = startupSchedules.getPeerPath(); + + saveStartupSchedules(startupSchedules, + "Steps to start processing PeerNodeStart !!!", + ScheduleState.STARTUP_START); + // 启动Peer + // 说明初始化成功 + // 判断是否需要启动Peer + String peerVerify = ledgerService.peerVerifyKey(peerPath); + + try { + if (!CommandUtils.isActive(peerVerify)) { + // 不存在,则需要再启动 + String peerStartCmd = ledgerService.peerStartCommand(peerPath); + + LOGGER.info("Execute Peer-Startup's Shell {}", peerStartCmd); + + if (!CommandUtils.executeAndVerify(CommandUtils.toCommandList(peerStartCmd), peerVerify)) { + // Peer节点启动失败 + throw new IllegalStateException("Peer Node Start UP Fail !!!"); + } + saveStartupSchedules(startupSchedules, + String.format("Peer's process %s start -> %s", peerVerify, SUCCESS), + ScheduleState.STARTUP_SUCCESS); + } else { + // 命令已经存在 + saveStartupSchedules(startupSchedules, + String.format("Peer's process is exist -> %s", peerVerify), + ScheduleState.NO_STARTUP); + } + } catch (Exception e) { + saveStartupSchedules(startupSchedules, + e.getMessage(), + ScheduleState.STARTUP_FAIL); + throw new IllegalStateException(e); + } + + saveStartupSchedules(startupSchedules, + "Steps to start processing PeerNodeStart over !!!", + ScheduleState.STARTUP_OVER); + + return startupSchedules; + } + +// private PeerInstallSchedules peerStart(String peerPath, PeerInstallSchedules installSchedules) { +// +// MasterAddr masterAddr = installSchedules.getIdentification().getMasterAddr(); +// +// String ledgerKey = installSchedules.getIdentification().getLedgerKey(); +// +// String ledgerAndNodeKey = installSchedules.getIdentification().getLedgerAndNodeKey(); +// +// saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, +// "Steps to start processing PeerNodeStart !!!", +// ScheduleState.STARTUP_START); +// // 启动Peer +// // 说明初始化成功 +// // 判断是否需要启动Peer +// String peerVerify = ledgerService.peerVerifyKey(peerPath); +// +// try { +// if (!CommandUtils.isActive(peerVerify)) { +// // 不存在,则需要再启动 +// String peerStartCmd = ledgerService.peerStartCommand(peerPath); +// +// LOGGER.info("Execute Peer-Startup's Shell {}", peerStartCmd); +// +// if (!CommandUtils.executeAndVerify(CommandUtils.toCommandList(peerStartCmd), peerVerify)) { +// // Peer节点启动失败 +// throw new IllegalStateException("Peer Node Start UP Fail !!!"); +// } +// saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, +// String.format("Peer's process %s start -> %s", peerVerify, SUCCESS), +// ScheduleState.STARTUP_SUCCESS); +// } else { +// // 命令已经存在 +// saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, +// String.format("Peer's process is exist -> %s", peerVerify), +// ScheduleState.NO_STARTUP); +// } +// } catch (Exception e) { +// saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, +// e.getMessage(), +// ScheduleState.STARTUP_FAIL); +// throw new IllegalStateException(e); +// } +// +// saveInstallSchedule(installSchedules, masterAddr, ledgerKey, ledgerAndNodeKey, +// "Steps to start processing PeerNodeStart over !!!", +// ScheduleState.STARTUP_OVER); +// +// return installSchedules; +// } + + private boolean peerStop(String peerPath) { + + // 判断是否需要停止Peer + String peerVerify = ledgerService.peerVerifyKey(peerPath); + + try { + if (CommandUtils.isActive(peerVerify)) { + + LOGGER.info("We need stop peer {}", peerVerify); + // 需要停止Peer节点 + CommandUtils.killVm(peerVerify); + + // 最多循环5次进行判断 + int maxSize = 5; + + while (maxSize > 0) { + try { + Thread.sleep(3000); + if (!CommandUtils.isActive(peerVerify)) { + return true; + } + } catch (Exception e) { + LOGGER.error("Check Peer Stop State !!!", e); + } finally { + maxSize--; + } + } + } else { + LOGGER.info("We do not need stop peer {}", peerVerify); + return false; + } + } catch (Exception e) { + LOGGER.error("Stop Peer Node", e); + throw new IllegalStateException(e); + } + return false; + } + + private String writeConsensusContent(String consensusContent, File consensusFile) throws IOException { + // 将字符串转换为字节数组 + byte[] consensusBytes = Base58Utils.decode(consensusContent); + forceWrite(consensusBytes, consensusFile); + return consensusFile.getName(); + } + + private void forceWrite(List lines, File file) throws IOException { + if (file.exists()) { + FileUtils.moveFile(file, new File(file.getPath() + "_bak_" + currentDate())); + } + + FileUtils.writeLines(file, StandardCharsets.UTF_8.toString(), lines); + } + + private void forceWrite(byte[] content, File file) throws IOException { + if (file.exists()) { + FileUtils.moveFile(file, new File(file.getPath() + "_bak_" + currentDate())); + } + + FileUtils.writeByteArrayToFile(file, content); + } + + private void verify(PeerSharedConfigs peerSharedConfigs) { + // 校验其中内容 + List sharedConfigs = peerSharedConfigs.getSharedConfigs(); + + // 首先保证其中的数据一致性 + // 1、name不能重复; + // 2、pubKey不能重复; + // 3、ipAddr + initPort不能重复; + + Set nameSet = new HashSet<>(), + pubKeySet = new HashSet<>(), + addrSet = new HashSet<>(); + + for (PeerSharedConfig sharedConfig : sharedConfigs) { + String name = sharedConfig.getName(), + pubKey = sharedConfig.getPubKey(), + addr = sharedConfig.addr(); + if (nameSet.contains(name)) { + throw new IllegalStateException(String.format("Name [%s] is Conflict !!!", name)); + } else { + nameSet.add(name); + } + + if (pubKeySet.contains(pubKey)) { + throw new IllegalStateException(String.format("PubKey [%s] is Conflict !!!", pubKey)); + } else { + pubKeySet.add(pubKey); + } + + if (addrSet.contains(addr)) { + throw new IllegalStateException(String.format("Address [%s] is Conflict !!!", addr)); + } else { + addrSet.add(addr); + } + } + } + + private void saveInstallSchedule(PeerInstallSchedules installSchedules, MasterAddr masterAddr, String ledgerKey, String ledgerAndNodeKey, String content, ScheduleState state) { + + // 日志打印相关内容 + LOGGER.info(content); + + // 生成InstallSchedule对象 + InstallSchedule schedule = installSchedule(ledgerKey, ledgerAndNodeKey, content, state); + + // 加入反馈列表 + installSchedules.addInstallSchedule( + new PeerInstallSchedule(new InstallProcess(content), state)); + + // 将InstallSchedule写入数据库 + umpStateService.save(schedule, masterAddr); + } + + private void saveInstallSchedule(PeerInstallSchedules installSchedules, String content, ScheduleState state) { + + // 日志打印相关内容 + LOGGER.info(content); + + // 加入反馈列表 + installSchedules.addInstallSchedule( + new PeerInstallSchedule(new InstallProcess(content), state)); + } + + private void saveStartupSchedules(PeerStartupSchedules startupSchedules, String content, ScheduleState state) { + + // 日志打印相关内容 + LOGGER.info(content); + + // 加入反馈列表 + startupSchedules.addInstallSchedule( + new PeerInstallSchedule(new InstallProcess(content), state)); + } + + private InstallSchedule installSchedule(String ledgerKey, String ledgerAndNodeKey, String content, ScheduleState state) { + + InstallProcess process = new InstallProcess(content); + + return new InstallSchedule(ledgerKey, ledgerAndNodeKey, process, state); + + } + + private List localConfContents(PeerLocalConfig localConfig, int nodeId) { + /** + * #当前参与方的 id,与ledger.init文件中cons_parti.id一致,默认从0开始 + * local.parti.id=0 + * + * #当前参与方的公钥 + * local.parti.pubkey= + * + * #当前参与方的私钥(密文编码) + * local.parti.privkey= + * + * #当前参与方的私钥解密密钥(原始口令的一次哈希,Base58格式),如果不设置,则启动过程中需要从控制台输入 + * local.parti.pwd= + * + * #账本初始化完成后生成的"账本绑定配置文件"的输出目录 + * #推荐使用绝对路径,相对路径以当前文件(local.conf)所在目录为基准 + * ledger.binding.out=../ + * + * #账本数据库的连接字符 + * #rocksdb数据库连接格式:rocksdb://{path},例如:rocksdb:///export/App08/peer/rocks.db/rocksdb0.db + * #redis数据库连接格式:redis://{ip}:{prot}/{db},例如:redis://127.0.0.1:6379/0 + * ledger.db.uri= + * + * #账本数据库的连接口令 + * ledger.db.pwd= + */ + + List localContents = new ArrayList<>(); + + localContents.add(valueToConfig(UmpConstant.LOCAL_PARTI_ID_PREFIX, nodeId)); + + localContents.add(valueToConfig(UmpConstant.LOCAL_PARTI_PUBKEY_PREFIX, localConfig.getPubKey())); + + localContents.add(valueToConfig(UmpConstant.LOCAL_PARTI_PRIVKEY_PREFIX, localConfig.getPrivKey())); + + localContents.add(valueToConfig(UmpConstant.LOCAL_PARTI_PWD_PREFIX, localConfig.getEncodePwd())); + + localContents.add(valueToConfig(UmpConstant.LEDGER_BINDING_OUT_PREFIX, localConfig.bindingOutPath())); + + localContents.add(valueToConfig(UmpConstant.LEDGER_DB_URI_PREFIX, dbUri(localConfig.getDbName(), localConfig.getPeerPath()))); + + localContents.add(valueToConfig(UmpConstant.LEDGER_DB_PWD_PREFIX, "")); + + return localContents; + } + + private String valueToConfig(String prefix, Object value) { + return prefix + "=" + value; + } + + private String currentDate() { + return SDF.format(new Date()); + } + + private String dbUri(final String dbName, final String peerPath) { + + String dbDirectoryPath = peerPath + File.separator + dbName; + + String dbUri = ROCKSDB_PROTOCOL + dbDirectoryPath; + + File dbDirectory = new File(dbDirectoryPath); + + if (!dbDirectory.exists()) { + return dbUri; + } + throw new IllegalStateException(String.format("DB name = %s, path = %s is Exist !!!", dbName, dbDirectoryPath)); + } + + private static class CurrentLedger { + + private String ledgerHash; + + private long lastTime; + + public CurrentLedger() { + } + + public CurrentLedger(long lastTime) { + this.lastTime = lastTime; + } + + public String getLedgerHash() { + return ledgerHash; + } + + public void setLedgerHash(String ledgerHash) { + this.ledgerHash = ledgerHash; + } + + public long getLastTime() { + return lastTime; + } + + public void setLastTime(long lastTime) { + this.lastTime = lastTime; + } + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateService.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateService.java new file mode 100644 index 00000000..c6c8ea82 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateService.java @@ -0,0 +1,17 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.config.PeerSharedConfigVv; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.ump.model.user.UserKeysVv; + +public interface UmpSimulateService { + + UserKeysVv userKeysVv(int nodeId); + + UserKeys userKeys(int nodeId); + + PeerLocalConfig nodePeerLocalConfig(int nodeId, boolean isMaster); + + PeerSharedConfigVv peerSharedConfigVv(int nodeId, boolean isMaster); +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateServiceHandler.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateServiceHandler.java new file mode 100644 index 00000000..3bfb20bf --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpSimulateServiceHandler.java @@ -0,0 +1,134 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.config.PeerSharedConfigVv; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.ump.model.user.UserKeysVv; +import com.jd.blockchain.ump.service.consensus.providers.BftsmartConsensusProvider; +import org.apache.commons.codec.binary.Hex; +import org.springframework.stereotype.Service; + +import java.util.Random; + +@Service +public class UmpSimulateServiceHandler implements UmpSimulateService { + + private static final Random RANDOM_ROCKSDB = new Random(); + + private static final String SHARED_KEY = "JDChain"; + + private static final int TOTAL_SIZE = 4; + + private static final String LOCALHOST = "127.0.0.1"; + + private static final String CONSENSUS_PROVIDER = BftsmartConsensusProvider.BFTSMART_PROVIDER; + + private static final String CONSENSUS_CONF = BftsmartConsensusProvider.BFTSMART_CONFIG_FILE; + + private static final int INIT_PORT_START = 9000; + + private static final String[] PUBKEYS = new String[]{ + "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", + "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", + "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", + "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; + + private static final String[] PRIVKEYS = new String[]{ + "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", + "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", + "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", + "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; + + private static final String ENCODE_PWD = "DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY"; + + private static final String BINDING_OUT = "../"; + + private static final String[] DB_URIS = new String[]{ + "rocksdb:///Users/shaozhuguang/Documents/simulate/peer0/rocksdb", + "rocksdb:///Users/shaozhuguang/Documents/simulate/peer1/rocksdb", + "rocksdb:///Users/shaozhuguang/Documents/simulate/peer2/rocksdb", + "rocksdb:///Users/shaozhuguang/Documents/simulate/peer3/rocksdb"}; + + private static final String DB_PWD = ""; + + private static final String DB_NAME = "rocksdb_"; + + private static final String[] PEER_PATHS = new String[]{ + "/Users/shaozhuguang/Documents/simulate/peer0", + "/Users/shaozhuguang/Documents/simulate/peer1", + "/Users/shaozhuguang/Documents/simulate/peer2", + "/Users/shaozhuguang/Documents/simulate/peer3"}; + + private static final String[] CONSENSUS_NODES = new String[]{ + "127.0.0.1:6000", + "127.0.0.1:6010", + "127.0.0.1:6020", + "127.0.0.1:6030"}; + + + @Override + public UserKeysVv userKeysVv(int nodeId) { + + UserKeys userKeys = userKeys(nodeId); + + return userKeys.toUserKeysVv(); + } + + @Override + public UserKeys userKeys(int nodeId) { + + return new UserKeys("Peer-" + nodeId, PRIVKEYS[nodeId], PUBKEYS[nodeId], ENCODE_PWD); + } + + @Override + public PeerLocalConfig nodePeerLocalConfig(int nodeId, boolean isMaster) { + + UserKeys userKeys = userKeys(nodeId); + + return peerSharedConfigVv(nodeId, isMaster).toPeerLocalConfig(userKeys); + } + + @Override + public PeerSharedConfigVv peerSharedConfigVv(int nodeId, boolean isMaster) { + + PeerSharedConfigVv sharedConfigVv = new PeerSharedConfigVv(); + + sharedConfigVv.setSharedKey(SHARED_KEY); + sharedConfigVv.setName(SHARED_KEY + "-" + nodeId); + sharedConfigVv.setInitAddr(LOCALHOST); + sharedConfigVv.setInitPort(INIT_PORT_START + nodeId * 10); + sharedConfigVv.setConsensusNode(CONSENSUS_NODES[nodeId]); + sharedConfigVv.setPubKey(PUBKEYS[nodeId]); + sharedConfigVv.setUserId(nodeId); + sharedConfigVv.setPeerPath(PEER_PATHS[nodeId]); + sharedConfigVv.setDbName(dbName()); + + if (isMaster) { + sharedConfigVv.setLedgerName(ledgerName()); + sharedConfigVv.setNodeSize(TOTAL_SIZE); + } else { + sharedConfigVv.setMasterAddr(LOCALHOST); + sharedConfigVv.setMasterPort(8080); + } + + return sharedConfigVv; + } + + private String ledgerName() { + + byte[] nameBytes = new byte[4]; + + RANDOM_ROCKSDB.nextBytes(nameBytes); + + return Hex.encodeHexString(nameBytes); + } + + private String dbName() { + + byte[] nameBytes = new byte[4]; + + RANDOM_ROCKSDB.nextBytes(nameBytes); + + return DB_NAME + Hex.encodeHexString(nameBytes); + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateService.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateService.java new file mode 100644 index 00000000..161ed867 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateService.java @@ -0,0 +1,62 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.ump.model.MasterAddr; +import com.jd.blockchain.ump.model.config.LedgerIdentification; +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.state.*; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.ump.model.user.UserKeysVv; + +import java.util.List; +import java.util.Map; + +public interface UmpStateService { + + void save(String ledgerAndNodeKey, PeerLocalConfig localConfig); + + void save(String ledgerKey, List sharedConfigKeys); + + void save(InstallSchedule installSchedule, MasterAddr masterAddr); + + void save(UserKeys userKeys); + + void save(LedgerPeerInstall peerInstall); + + void save(LedgerMasterInstall masterInstall); + + void save(LedgerIdentification identification); + + void saveLedgerHash(String ledgerAndNodeKey, String ledgerHash); + + List readUserKeysList(); + + List readUserKeysVvList(); + + UserKeys readUserKeys(int id); + + PeerLocalConfig readConfig(String ledgerAndNodeKey); + + PeerInstallSchedules loadState(String ledgerAndNodeKey); + + PeerInstallSchedules loadInitState(String ledgerAndNodeKey); + + PeerInstallSchedules readState(String ledgerAndNodeKey); + + PeerInstallSchedules readInitState(String ledgerAndNodeKey); + + Map> readStates(String ledgerKey); + + LedgerIdentification readIdentification(String ledgerAndNodeKey); + + List readLedgerPeerInstalls(); + + List readLedgerMasterInstalls(); + + List readLedgerPeerIniteds(); + + List readLedgerPeerIniteds(String search); + + List readLedgerIniteds(String search); + + String readLedgerHash(String ledgerAndNodeKey); +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateServiceHandler.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateServiceHandler.java new file mode 100644 index 00000000..5ed51aca --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UmpStateServiceHandler.java @@ -0,0 +1,880 @@ +package com.jd.blockchain.ump.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.jd.blockchain.ump.dao.DBConnection; +import com.jd.blockchain.ump.model.*; +import com.jd.blockchain.ump.model.config.LedgerIdentification; +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.state.*; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.ump.model.user.UserKeysVv; +import com.jd.blockchain.ump.util.CommandUtils; +import com.jd.blockchain.ump.util.HttpJsonClientUtils; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class UmpStateServiceHandler implements UmpStateService, Closeable { + + private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMM-ddHHmmss"); + + private static final String PEER_IDENTIFICATION_FORMAT = "PEER_IDENTIFICATION_INDEX_%s"; + + private static final String PEER_INSTALL_MAX_KEY = "PEER_INSTALL_MAX_INDEX"; + + private static final String PEER_INSTALL_KEY_FORMAT = "PEER_INSTALL_INDEX_%s"; + + private static final String MASTER_INSTALL_MAX_KEY = "MASTER_INSTALL_MAX_INDEX"; + + private static final String MASTER_INSTALL_KEY_FORMAT = "MASTER_INSTALL_INDEX_%s"; + + private static final String USERS_KEY_MAX_KEY = "USERS_KEY_MAX_INDEX"; + + private static final String USERS_KEY_FORMAT = "USERS_%s_REGISTER"; + + private static final String MAX_SIZE_KEY_SUFFIX = "_MAX_SIZE_KEY"; + + private static final String LEDGER_HASH_KEY_SUFFIX = "_LEDGER_HASH_KEY"; + + private static final String LEDGER_NODE_KEY_CONFIG_SUFFIX = "_LEDGER_NODE_CONFIG_KEY"; + + private static final String LEDGER_NODE_KEY_SUFFIX = "_LEDGER_NODE_KEY"; + + private static final String CURRENT_INDEX_KEY_SUFFIX_FORMAT = "_%s_INDEX_KEY"; + + private static final String PORT_ARG = "-p"; + + private static final String LOCALHOST = "127.0.0.1"; + + private ExecutorService singleHttpThread = Executors.newSingleThreadExecutor(); + + @Autowired + private DBConnection dbConnection; + + @Autowired + private LedgerService ledgerService; + + @Override + public synchronized void save(String ledgerAndNodeKey, PeerLocalConfig localConfig) { + + String ledgerAndNodeConfigKey = ledgerAndNodeConfigKey(ledgerAndNodeKey); + + dbConnection.put(ledgerAndNodeConfigKey, JSON.toJSONString(localConfig)); + } + + @Override + public synchronized void save(String ledgerKey, List sharedConfigKeys) { + + String ledgerAllNodeKey = ledgerAllNodeKey(ledgerKey); + + StringBuilder sBuilder = new StringBuilder(); + + for (String sharedConfigKey : sharedConfigKeys) { + if (sBuilder.length() > 0) { + sBuilder.append(";"); + } + sBuilder.append(sharedConfigKey); + } + + dbConnection.put(ledgerAllNodeKey, sBuilder.toString()); + } + + @Override + public synchronized void save(InstallSchedule installSchedule, MasterAddr masterAddr) { + try { + String ledgerAndNodeKey = installSchedule.getLedgerAndNodeKey(); + // 不使用队列,直接将其写入数据库 + // 需要查询目前该Key对应的最大值是多少 + String maxKey = ledgerAndNodeMaxKey(ledgerAndNodeKey); + String maxIdChars = dbConnection.get(maxKey); + int maxId = 0; + if (maxIdChars != null && maxIdChars.length() > 0) { + maxId = Integer.parseInt(maxIdChars) + 1; + } + + String newKey = ledgerAndNodeCurrentNewKey(ledgerAndNodeKey, maxId); + + // 内容写入数据库 + dbConnection.put(newKey, installSchedule, InstallSchedule.class); + + // 更新最大值 + dbConnection.put(maxKey, String.valueOf(maxId)); + + if (masterAddr != null && masterAddr.legal()) { + singleHttpThread.execute(() -> { + + try { + // 发送HTTP请求 + HttpJsonClientUtils.httpPost(masterAddr, UmpConstant.REQUEST_STATE_URL, installSchedule, String.class, false); + } catch (Exception e) { + // 暂不关注是否发送成功 + LOGGER.error(e.toString()); + } + + }); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public synchronized void save(UserKeys userKeys) { + + int maxIndex = maxIndex(USERS_KEY_MAX_KEY); + + String userKey = usersKey(maxIndex); + + // 重置userId + userKeys.setId(maxIndex); + + // 将用户信息写入数据库 + dbConnection.put(userKey, JSON.toJSONString(userKeys)); + + // 更新最大值 + dbConnection.put(USERS_KEY_MAX_KEY, String.valueOf(maxIndex)); + + try { + // 将其放入文件中 + String keysDirPath = UmpConstant.PROJECT_PATH + UmpConstant.PATH_CONFIG_KEYS; + + File keysDir = new File(keysDirPath); + + if (!keysDir.exists()) { + // 创建文件夹 + keysDir.mkdir(); + } + saveKeys2Files(keysDirPath, userKeys); + } catch (Exception e) { + LOGGER.error("Save Keys To File !", e); + } + } + + @Override + public synchronized void save(LedgerPeerInstall peerInstall) { + + int maxIndex = maxIndex(PEER_INSTALL_MAX_KEY); + + // 将用户信息写入数据库 + dbConnection.put(peerInstallKey(maxIndex), JSON.toJSONString(peerInstall)); + + // 更新最大值 + dbConnection.put(PEER_INSTALL_MAX_KEY, String.valueOf(maxIndex)); + + } + + @Override + public synchronized void save(LedgerMasterInstall masterInstall) { + + int maxIndex = maxIndex(MASTER_INSTALL_MAX_KEY); + + // 将用户信息写入数据库 + dbConnection.put(masterInstallKey(maxIndex), JSON.toJSONString(masterInstall)); + + // 更新最大值 + dbConnection.put(MASTER_INSTALL_MAX_KEY, String.valueOf(maxIndex)); + } + + @Override + public synchronized void save(LedgerIdentification identification) { + + String ledgerAndNodeKey = identification.getLedgerAndNodeKey(); + + String idKey = String.format(PEER_IDENTIFICATION_FORMAT, ledgerAndNodeKey); + + dbConnection.put(idKey, JSON.toJSONString(identification)); + } + + @Override + public void saveLedgerHash(String ledgerAndNodeKey, String ledgerHash) { + + String ledgerHashKey = ledgerAndNodeHashKey(ledgerAndNodeKey); + + dbConnection.put(ledgerHashKey, ledgerHash); + } + + @Override + public List readUserKeysList() { + + List userKeysList = new ArrayList<>(); + + String maxIndexChars = dbConnection.get(USERS_KEY_MAX_KEY); + + if (maxIndexChars != null && maxIndexChars.length() > 0) { + + int maxIndex = Integer.parseInt(maxIndexChars); + + for (int i = 0; i <= maxIndex; i++) { + try { + + String json = dbConnection.get(usersKey(i)); + + if (json != null && json.length() > 0) { + userKeysList.add(JSON.parseObject(json, UserKeys.class)); + } + } catch (Exception e) { + LOGGER.error(e.toString()); + } + } + } + + return userKeysList; + } + + @Override + public List readUserKeysVvList() { + + List userKeysVvList = new ArrayList<>(); + + List userKeysList = readUserKeysList(); + + if (!userKeysList.isEmpty()) { + for (UserKeys userKeys : userKeysList) { + + userKeysVvList.add(userKeys.toUserKeysVv()); + } + } + + return userKeysVvList; + } + + @Override + public UserKeys readUserKeys(int id) { + + String userKey = usersKey(id); + + String userKeysJson = dbConnection.get(userKey); + + if (userKeysJson != null && userKeysJson.length() > 0) { + return JSON.parseObject(userKeysJson, UserKeys.class); + } + + return null; + } + + @Override + public PeerLocalConfig readConfig(String ledgerAndNodeKey) { + + String json = dbConnection.get(ledgerAndNodeConfigKey(ledgerAndNodeKey)); + + if (json != null && json.length() > 0) { + + return JSON.parseObject(json, PeerLocalConfig.class); + } + + return null; + } + + @Override + public PeerInstallSchedules loadState(String ledgerAndNodeKey) { + + PeerInstallSchedules installSchedules = loadInitState(ledgerAndNodeKey); + + String ledgerHash = ledgerService.readLedgerHash(ledgerAndNodeKey); + + if (ledgerHash == null || ledgerHash.length() == 0) { + throw new IllegalStateException("Can not find LedgerHash from DataBase !!!"); + } + + return installSchedules.initLedgerHash(ledgerHash); + + } + + @Override + public PeerInstallSchedules loadInitState(String ledgerAndNodeKey) { + // 获取LedgerIdentification + LedgerIdentification identification = readIdentification(ledgerAndNodeKey); + + if (identification == null) { + throw new IllegalStateException("Can not find LedgerIdentification from DataBase !!!"); + } + + return new PeerInstallSchedules(identification); + } + + @Override + public PeerInstallSchedules readState(final String ledgerAndNodeKey) { + + PeerInstallSchedules installSchedules = loadState(ledgerAndNodeKey); + + loadInstallSchedules(installSchedules, ledgerAndNodeKey); + + return installSchedules; + } + + @Override + public PeerInstallSchedules readInitState(String ledgerAndNodeKey) { + + PeerInstallSchedules installSchedules = loadInitState(ledgerAndNodeKey); + + loadInstallSchedules(installSchedules, ledgerAndNodeKey); + + return installSchedules; + } + + @Override + public Map> readStates(String ledgerKey) { + + String ledgerAllNodeKey = ledgerAllNodeKey(ledgerKey); + + String ledgerAllNodeValues = dbConnection.get(ledgerAllNodeKey); + + String[] ledgerAndNodeKeys = ledgerAllNodeValues.split(";"); + + Map> allInstallSchedules = new HashMap<>(); + + // 不存在就返回空值 + if (ledgerAndNodeKeys.length > 0) { + + for (String ledgerAndNodeKey : ledgerAndNodeKeys) { + // 获取每个LedgerAndNodeKey数据 + List installSchedules = readInstallSchedules(ledgerAndNodeKey); + + if (installSchedules != null) { + + allInstallSchedules.put(ledgerAndNodeKey, installSchedules); + } + } + } + + return allInstallSchedules; + } + + @Override + public LedgerIdentification readIdentification(String ledgerAndNodeKey) { + + String idKey = String.format(PEER_IDENTIFICATION_FORMAT, ledgerAndNodeKey); + + String identificationJson = dbConnection.get(idKey); + + if (identificationJson != null && identificationJson.length() > 0) { + + return JSON.parseObject(identificationJson, LedgerIdentification.class); + } + + return null; + } + + @Override + public List readLedgerPeerInstalls() { + + List peerInstallList = new ArrayList<>(); + + String maxIndexChars = dbConnection.get(PEER_INSTALL_MAX_KEY); + + if (maxIndexChars != null && maxIndexChars.length() > 0) { + + int maxIndex = Integer.parseInt(maxIndexChars); + + for (int i = 1; i <= maxIndex; i++) { + try { + String json = dbConnection.get(peerInstallKey(i)); + + if (json != null && json.length() > 0) { + peerInstallList.add(JSON.parseObject(json, LedgerPeerInstall.class)); + } + } catch (Exception e) { + LOGGER.error(e.toString()); + } + } + } + + return peerInstallList; + } + + @Override + public List readLedgerMasterInstalls() { + + List masterInstalls = new ArrayList<>(); + + String maxIndexChars = dbConnection.get(PEER_INSTALL_MAX_KEY); + + if (maxIndexChars != null && maxIndexChars.length() > 0) { + + int maxIndex = Integer.parseInt(maxIndexChars); + + for (int i = 1; i <= maxIndex; i++) { + try { + String json = dbConnection.get(masterInstallKey(i)); + + if (json != null && json.length() > 0) { + masterInstalls.add(JSON.parseObject(json, LedgerMasterInstall.class)); + } + } catch (Exception e) { + LOGGER.error(e.toString()); + } + } + } + + return masterInstalls; + } + + @Override + public List readLedgerPeerIniteds() { + + List peerIniteds = new ArrayList<>(); + + List peerInstalls = readLedgerPeerInstalls(); + + if (!peerInstalls.isEmpty()) { + + LOGGER.info("Read LedgerPeerInstalls, Size = {}", peerInstalls.size()); + for (LedgerPeerInstall peerInstall : peerInstalls) { + + String ledgerAndNodeKey = peerInstall.getLedgerAndNodeKey(); + + // 数据库中读取存放的LedgerHash + String ledgerHash = readLedgerHash(ledgerAndNodeKey); + + if (ledgerHash == null || ledgerHash.length() == 0) { + continue; + } + + LedgerPeerInited peerInited = new LedgerPeerInited(ledgerHash, peerInstall); + + // 检测账本中的Hash是否真正存在 + StartupState startupState = StartupState.UNKNOWN; + try { + startupState = startupState(ledgerHash, peerInstall); + } catch (Exception e) { + LOGGER.error("Check Ledger Hash Exist !!!", e); + } + + // 设置账本状态 + peerInited.setStartupState(startupState); + + // 添加到集合 + peerIniteds.add(peerInited); + } + } else { + LOGGER.error("Read LedgerPeerInstalls is Empty !!!"); + } + return peerIniteds; + } + + @Override + public List readLedgerPeerIniteds(String search) { + + List initedList = readLedgerPeerIniteds(); + + if (search != null && search.length() > 0 && !initedList.isEmpty()) { + + List filterInitedList = new ArrayList<>(); + + for (LedgerPeerInited peerInited : initedList) { + if (isMatch(peerInited, search)) { + filterInitedList.add(peerInited); + } + } + + return filterInitedList; + } + + return initedList; + } + + @Override + public List readLedgerIniteds(String search) { + + List ledgerInitedsFromConf = loadAllLedgerIniteds(UmpConstant.PROJECT_PATH); + + if (!ledgerInitedsFromConf.isEmpty()) { + + List ledgerIniteds = new ArrayList<>(); + + for (LedgerInited ledgerInited : ledgerInitedsFromConf) { + + if (isMatch(ledgerInited, search)) { + ledgerIniteds.add(ledgerInited); + } + } + + return ledgerIniteds; + } + + return ledgerInitedsFromConf; + } + + @Override + public String readLedgerHash(String ledgerAndNodeKey) { + + String ledgerHashKey = ledgerAndNodeHashKey(ledgerAndNodeKey); + + return dbConnection.get(ledgerHashKey); + } + + @Override + public void close() throws IOException { +// writeRunner.close(); + } + + private boolean isMatch(LedgerInited ledgerInited, String search) { + + if (search == null || search.length() == 0) { + return true; + } + + String ledgerHash = ledgerInited.getLedgerHash(); + String ledgerName = ledgerInited.getLedgerName(); + String partiName = ledgerInited.getPartiName(); + String partiAddress = ledgerInited.getPartiAddress(); + String dbUri = ledgerInited.getDbUri(); + StartupState startupState = ledgerInited.getStartupState(); + + if ( + ledgerHash.contains(search) || + startupState.toString().equals(search) || + ledgerName.contains(search) || + partiName.contains(search) || + partiAddress.contains(search) || + dbUri.contains(search) + ) { + return true; + } + return false; + } + + private boolean isMatch(LedgerPeerInited peerInited, String search) { + + if (search == null || search.length() == 0) { + return true; + } + + String ledgerHash = peerInited.getLedgerHash(); + StartupState startupState = peerInited.getStartupState(); + LedgerPeerInstall peerInstall = peerInited.getPeerInstall(); + + if (ledgerHash.contains(search) || + startupState.toString().equals(search) || + peerInstall.getNodeName().contains(search) || + peerInstall.getCreateTime().contains(search) + ) { + return true; + } + return false; + } + + private void loadInstallSchedules(PeerInstallSchedules installSchedules, String ledgerAndNodeKey) { + List schedules = readInstallSchedules(ledgerAndNodeKey); + + for (InstallSchedule installSchedule : schedules) { + installSchedules.addInstallSchedule( + new PeerInstallSchedule(installSchedule.getProcess(), installSchedule.getState())); + } + } + + private List readInstallSchedules(String ledgerAndNodeKey) { + String maxKey = ledgerAndNodeMaxKey(ledgerAndNodeKey); + String maxIdChars = dbConnection.get(maxKey); + if (maxIdChars == null || maxIdChars.length() == 0) { + return null; + } + int maxId = Integer.parseInt(maxIdChars); + + List schedules = new ArrayList<>(); + + for (int i = 0; i <= maxId; i++) { + + try { + String currentKey = ledgerAndNodeCurrentNewKey(ledgerAndNodeKey, i); + + String jsonChars = dbConnection.get(currentKey); + + if (jsonChars != null && jsonChars.length() > 0) { + schedules.add(JSON.parseObject(jsonChars, InstallSchedule.class)); + } + } catch (Exception e) { + // 打印错误,暂不处理其他 + LOGGER.error(e.toString()); + } + } + + return schedules; + } + + private List loadAllLedgerIniteds(String peerPath) { + + List ledgerInitedsFromConf = ledgerService.allLedgerIniteds(peerPath); + + if (!ledgerInitedsFromConf.isEmpty()) { + + // 逐个检查其状态 + for (LedgerInited ledgerInited : ledgerInitedsFromConf) { + // 判断该账本对应的数据库是否存在 + if (!dbConnection.exist(ledgerInited.getDbUri())) { + ledgerInited.setStartupState(StartupState.DB_UNEXIST); + continue; + } + + String peerVerify = ledgerService.peerVerifyKey(peerPath); + + try { + if (!CommandUtils.isActive(peerVerify)) { + // 进程不存在 + LOGGER.info("Can not find Peer Process {} !!!", peerVerify); + ledgerInited.setStartupState(StartupState.UNLOAD); + continue; + } + } catch (Exception e) { + // 进程处理错误打印日志即可 + LOGGER.error(String.format("Command Check %s !!!", peerVerify), e); + } + // 查看该进程对应的监听端口 + try { + int listenPort = listenPort(peerVerify); + + LOGGER.info("Find Listen Port = {} !", listenPort); + + if (listenPort > 0) { + + int maxSize = 5, checkIndex = 1; + + boolean isRead = false; + + while (maxSize > 0) { + + try { + // 发送请求到对应地址 + JSONArray ledgerHashs = HttpJsonClientUtils.httpGet(ledgersUrl(listenPort), JSONArray.class, true); + + if (ledgerHashs != null && !ledgerHashs.isEmpty()) { + for(Object hashObj : ledgerHashs) { + if (hashObj instanceof JSONObject) { + if (ledgerInited.getLedgerHash().equals(((JSONObject) hashObj).getString("value"))) { + // 说明该账本已经被加载 + ledgerInited.setStartupState(StartupState.LOADED); + isRead = true; + break; + } + } + } + if (isRead) { + break; + } + } + + // 6秒休眠 + Thread.sleep(6000); + } catch (Exception e) { + LOGGER.error(String.format("Request LedgerHashs from PeerNode [%s]", checkIndex++), e); + } + + maxSize --; + } + + if (!isRead) { + // 表明等待加载,无须再启动 + ledgerInited.setStartupState(StartupState.LOADING); + } + } + } catch (Exception e) { + LOGGER.error(String.format("Command [%s] 'Listen Port Check !!!", peerVerify), e); + } + } + } + + return ledgerInitedsFromConf; + } + + private StartupState startupState(String ledgerHash, LedgerPeerInstall peerInstall) { + + String peerPath = peerInstall.getPeerPath(); + + // 首先检查文件中是否存在该Hash值 + LedgerBindingConf ledgerBindingConf = ledgerService.allLedgerHashs(peerPath); + + Set allLedgerHashs = ledgerBindingConf.getLedgerHashs(); + + if (!allLedgerHashs.contains(ledgerHash)) { + + // 文件中不存在 + return StartupState.UNEXIST; + } + + // 判断该账本对应的数据库是否存在 + if (!ledgerService.dbExist(peerPath, ledgerHash)) { + + // 该账本对应数据库不存在 + return StartupState.DB_UNEXIST; + } + + // 文件中存在则检查进程是否存在 + // 进程存在标识为LOADED,否则标识为LOADING,暂时用不到LOADING + String peerVerify = ledgerService.peerVerifyKey(peerPath); + + try { + if (!CommandUtils.isActive(peerVerify)) { + // 进程不存在 + return StartupState.UNLOAD; + } + + } catch (Exception e) { + // 进程处理错误打印日志即可 + LOGGER.error(String.format("Command Check %s !!!", peerVerify), e); + } + + // 查看该进程对应的监听端口 + try { + int listenPort = listenPort(peerVerify); + + LOGGER.info("Find Listen Port = {} !", listenPort); + + if (listenPort > 0) { + // 发送请求到对应地址 + JSONArray ledgerHashs = HttpJsonClientUtils.httpGet(ledgersUrl(listenPort), JSONArray.class, true); + + if (ledgerHashs != null && !ledgerHashs.isEmpty()) { + for(Object hashObj : ledgerHashs) { + if (hashObj instanceof JSONObject) { + if (ledgerHash.equals(((JSONObject) hashObj).getString("value"))) { + // 说明该账本已经被加载 + return StartupState.LOADED; + } + } + } + // 表明等待加载,无须再启动 + return StartupState.LOADING; + } + } + } catch (Exception e) { + LOGGER.error(String.format("Command [%s] 'Listen Port Check !!!", peerVerify), e); + } + + return StartupState.UNKNOWN; + } + + private String ledgersUrl(int listenPort) { + return "http://" + LOCALHOST + ":" + listenPort + "/ledgers"; + } + + private int listenPort(String peerVerify) throws Exception { + + String portArg = mainArg(peerVerify, PORT_ARG); + + if (portArg != null && portArg.length() > 0) { + return Integer.parseInt(portArg); + } + + return 0; + } + + private String mainArg(String processName, String argKey) throws Exception { + + String[] cmdLineArray = mainArgs(processName); + + if (cmdLineArray != null && cmdLineArray.length > 0) { + for (int i = 0; i < cmdLineArray.length; i++) { + String currArg = cmdLineArray[i].trim(); + if (currArg.equals(argKey) && (i + 1) < cmdLineArray.length) { + return cmdLineArray[i+1].trim(); + } + } + } + return null; + } + + private String[] mainArgs(String processName) throws Exception { + + String mainArgs = CommandUtils.mainArgs(processName); + + if (mainArgs != null && mainArgs.length() > 0) { + ///Users/shaozhuguang/Documents/newenv/peer4/system/deployment-peer-1.1.0-SNAPSHOT.jar -home=/Users/shaozhuguang/Documents/newenv/peer4 -c /Users/shaozhuguang/Documents/newenv/peer4/config/ledger-binding.conf -p 7080 + return mainArgs.split(" "); + } + + return null; + } + + private synchronized int maxIndex(String key) { + int maxIndex = 1; + String maxIndexChars = dbConnection.get(key); + if (maxIndexChars != null && maxIndexChars.length() > 0) { + maxIndex = Integer.parseInt(maxIndexChars) + 1; + } + return maxIndex; + } + + private String usersKey(int userId) { + return String.format(USERS_KEY_FORMAT, userId); + } + + private String peerInstallKey(int index) { + return String.format(PEER_INSTALL_KEY_FORMAT, index); + } + + private String masterInstallKey(int index) { + return String.format(MASTER_INSTALL_KEY_FORMAT, index); + } + + private String ledgerAndNodeConfigKey(String ledgerAndNodeKey) { + + return ledgerAndNodeKey + LEDGER_NODE_KEY_CONFIG_SUFFIX; + } + + private String ledgerAllNodeKey(String ledgerKey) { + + return ledgerKey + LEDGER_NODE_KEY_SUFFIX; + } + + private String ledgerAndNodeMaxKey(String ledgerAndNodeKey) { + + return ledgerAndNodeKey + MAX_SIZE_KEY_SUFFIX; + } + + private String ledgerAndNodeHashKey(String ledgerAndNodeKey) { + + return ledgerAndNodeKey + LEDGER_HASH_KEY_SUFFIX; + } + + private String ledgerAndNodeCurrentNewKey(String ledgerAndNodeKey, int currentId) { + + return String.format( + ledgerAndNodeKey + CURRENT_INDEX_KEY_SUFFIX_FORMAT, + currentId); + } + + private void saveKeys2Files(String keysDirPath, UserKeys userKeys) throws IOException { + + // 写入私钥 + write(keysDirPath, userKeys.getName(), UmpConstant.PRIVATE_KEY_SUFFIX, userKeys.getPrivKey()); + // 写入公钥 + write(keysDirPath, userKeys.getName(), UmpConstant.PUBLIC_KEY_SUFFIX, userKeys.getPubKey()); + // 写入密钥 + write(keysDirPath, userKeys.getName(), UmpConstant.PWD_SUFFIX, userKeys.getEncodePwd()); + } + + private void write(String keysDirPath, String name, String suffix, String writeContent) throws IOException { + + String keyeFilePath = keysDirPath + File.separator + name + suffix; + + File keysFile = new File(keyeFilePath); + + if (keysFile.exists()) { + // 文件存在,备份文件 + FileUtils.copyFile(keysFile, new File(keyeFilePath + "_bak_" + currentTime())); + } + + // 将Priv文件内容写入 + FileUtils.writeStringToFile(keysFile, writeContent, StandardCharsets.UTF_8); + } + + private String currentTime() { + return SDF.format(new Date()); + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilService.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilService.java new file mode 100644 index 00000000..a2083d88 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilService.java @@ -0,0 +1,15 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.ump.model.user.UserKeyBuilder; +import com.jd.blockchain.ump.model.user.UserKeys; + +public interface UtilService { + + UserKeys create(UserKeyBuilder builder); + + UserKeys create(String name, String seed, String pwd); + + UserKeys read(int id); + + boolean verify(UserKeys userKeys, String pwd); +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilServiceHandler.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilServiceHandler.java new file mode 100644 index 00000000..3abf5bb4 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/UtilServiceHandler.java @@ -0,0 +1,80 @@ +package com.jd.blockchain.ump.service; + +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; +import com.jd.blockchain.crypto.utils.classic.ED25519Utils; +import com.jd.blockchain.ump.model.user.UserKeyBuilder; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.security.ShaUtils; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters; +import org.bouncycastle.crypto.prng.FixedSecureRandom; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.nio.charset.Charset; + +import static com.jd.blockchain.tools.keygen.KeyGenCommand.encodePrivKey; +import static com.jd.blockchain.tools.keygen.KeyGenCommand.encodePubKey; + +@Service +public class UtilServiceHandler implements UtilService { + + private static final String UTF_8 = "UTF-8"; + + @Autowired + private UmpStateService umpStateService; + + @Override + public UserKeys create(UserKeyBuilder builder) { + + return create(builder.getName(), builder.getSeed(), builder.getPwd()); + } + + @Override + public UserKeys create(String name, String seed, String pwd) { + + AsymmetricCipherKeyPair keyPair = ED25519Utils.generateKeyPair( + new FixedSecureRandom(seed.getBytes(Charset.forName(UTF_8)))); + + PubKey pubKey = new PubKey(ClassicAlgorithm.ED25519, + ((Ed25519PublicKeyParameters) keyPair.getPublic()).getEncoded()); + + PrivKey privKey = new PrivKey(ClassicAlgorithm.ED25519, + ((Ed25519PrivateKeyParameters) keyPair.getPrivate()).getEncoded()); + + return create(name, pubKey, privKey, pwd); + } + + @Override + public UserKeys read(int userId) { + + return umpStateService.readUserKeys(userId); + } + + @Override + public boolean verify(UserKeys userKeys, String pwd) { + + String encodePwd = Base58Utils.encode((ShaUtils.hash_256(pwd.getBytes(Charset.forName(UTF_8))))); + + if (encodePwd.equals(userKeys.getEncodePwd())) { + return true; + } + return false; + } + + private UserKeys create(String name, PubKey pubKey, PrivKey privKey, String pwd) { + + byte[] pwdBytes = ShaUtils.hash_256(pwd.getBytes(Charset.forName(UTF_8))); + + return new UserKeys( + name, + encodePrivKey(privKey, pwdBytes), + encodePubKey(pubKey), +// pwd, // 密码不保存到数据库,防止泄露 + Base58Utils.encode(pwdBytes)); + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusProvider.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusProvider.java new file mode 100644 index 00000000..124b6d10 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusProvider.java @@ -0,0 +1,21 @@ +package com.jd.blockchain.ump.service.consensus; + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; + +import java.util.List; +import java.util.Properties; + +public interface ConsensusProvider { + + String NEXT_LINE = "\r\n"; + + String provider(); + + String configFilePath(); + + void setConfig(Properties properties); + + Properties getConfig(); + + byte[] handleSharedConfigs(List sharedConfigs); +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusService.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusService.java new file mode 100644 index 00000000..aeb5fcba --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusService.java @@ -0,0 +1,10 @@ +package com.jd.blockchain.ump.service.consensus; + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; + +import java.util.List; + +public interface ConsensusService { + + String initConsensusConf(String consensusProvider, List sharedConfigs); +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusServiceHandler.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusServiceHandler.java new file mode 100644 index 00000000..50cdb00f --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/ConsensusServiceHandler.java @@ -0,0 +1,79 @@ +package com.jd.blockchain.ump.service.consensus; + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.util.Base58Utils; +import org.reflections.Reflections; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class ConsensusServiceHandler implements ConsensusService { + + private static final Map CONSENSUS_PROVIDERS = new ConcurrentHashMap<>(); + + static { + try { + initProviders(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public String initConsensusConf(String consensusProvider, List sharedConfigs) { + // 首先根据provider获取对应的配置信息 + ConsensusProvider provider = CONSENSUS_PROVIDERS.get(consensusProvider); + + if (provider == null) { + throw new IllegalStateException( + String.format("ConsensusProvider[%s] can not find Manage-Class !!!", consensusProvider)); + } + + byte[] result = provider.handleSharedConfigs(sharedConfigs); + + return Base58Utils.encode(result); + } + + private static void initProviders() { + // 初始化所有实现类 + Reflections reflections = new Reflections("com.jd.blockchain.ump.service.consensus"); + + Set> providerSet = + reflections.getSubTypesOf(ConsensusProvider.class); + + for (Class clazz : providerSet) { + + if (!clazz.isInterface()) { + try { + // 根据class生成对象 + ConsensusProvider provider = clazz.newInstance(); + String providerKey = provider.provider(); + if (providerKey != null && providerKey.length() > 0 && + !CONSENSUS_PROVIDERS.containsKey(providerKey)) { + + // 根据value读取配置文件中的内容 + InputStream currentFileInputStream = ConsensusServiceHandler.class.getResourceAsStream( + File.separator + provider.configFilePath()); + + Properties currentProps = new Properties(); + + currentProps.load(currentFileInputStream); + + provider.setConfig(currentProps); + + CONSENSUS_PROVIDERS.put(providerKey, provider); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConsensusProvider.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConsensusProvider.java new file mode 100644 index 00000000..acb89a84 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConsensusProvider.java @@ -0,0 +1,162 @@ +package com.jd.blockchain.ump.service.consensus.providers; + + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.config.PeerSharedConfig; +import com.jd.blockchain.ump.service.consensus.ConsensusProvider; + +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class BftsmartConsensusProvider implements ConsensusProvider { + + public static final String BFTSMART_PROVIDER = "com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider"; + + public static final String BFTSMART_CONFIG_FILE = "bftsmart.config"; + + private static final int MIN_PARTI_SIZE = 4; + + private Properties bftsmartProps; + + @Override + public String provider() { + return BFTSMART_PROVIDER; + } + + @Override + public String configFilePath() { + return BFTSMART_CONFIG_FILE; + } + + @Override + public void setConfig(Properties properties) { + bftsmartProps = properties; + } + + @Override + public Properties getConfig() { + return bftsmartProps; + } + + @Override + public byte[] handleSharedConfigs(List sharedConfigs) { + + // 首先校验其中的ConsensusNode是否完全一致,若完全一致则不可以 + verify(sharedConfigs); + + StringBuilder sBuilder = new StringBuilder(); + + // 先加入当前节点信息 + List nodeConfigs = nodeConfigs(sharedConfigs); + + for (String nodeConfig : nodeConfigs) { + sBuilder.append(nodeConfig).append(NEXT_LINE); + } + + int nodeNum = sharedConfigs.size(); + + // 写入之前配置文件中的内容 + for (Map.Entry entry : bftsmartProps.entrySet()) { + + // 获取Key-Value + String key = (String) entry.getKey(), value = (String) entry.getValue(); + + // 对特殊的Key和Value单独处理 + /** + * system.servers.num = 4 + * + * system.servers.f = 1 + * + * system.initial.view = 0,1,2,3 + */ + if (key.startsWith(BftsmartConstant.SERVERS_NUM_PREFIX)) { + + sBuilder.append(BftsmartConstant.SERVERS_NUM_PREFIX + " = " + nodeNum).append(NEXT_LINE); + } else if (key.startsWith(BftsmartConstant.SERVERS_F_PREFIX)) { + + sBuilder.append(BftsmartConstant.SERVERS_F_PREFIX + " = " + nodeFNum(nodeNum)).append(NEXT_LINE); + } else if (key.startsWith(BftsmartConstant.INIT_VIEW_PREFIX)) { + + sBuilder.append(BftsmartConstant.INIT_VIEW_PREFIX + " = " + initView(nodeNum)).append(NEXT_LINE); + } else { + + sBuilder.append(key + " = " + value).append(NEXT_LINE); + } + } + + return sBuilder.toString().getBytes(StandardCharsets.UTF_8); + } + + private String initView(int nodeNum) { + + StringBuilder views = new StringBuilder(); + + for (int i = 0; i < nodeNum; i++) { + if (views.length() > 0) { + views.append(","); + } + views.append(i); + } + return views.toString(); + } + + private void verify(List sharedConfigs) { + + Set consensusInfos = new HashSet<>(); + + if (sharedConfigs == null) { + throw new IllegalStateException("Shared Configs is NULL !!!"); + } + + if (sharedConfigs.size() < MIN_PARTI_SIZE) { + throw new IllegalStateException( + String.format("Shared Configs's size = %s, can not meet minimum %s !!!", + sharedConfigs.size(), MIN_PARTI_SIZE)); + } + + for (PeerLocalConfig sharedConfig : sharedConfigs) { + String consensusInfo = sharedConfig.getConsensusNode(); + if (consensusInfos.contains(consensusInfo)) { + throw new IllegalStateException("Shared Configs's Consensus may be conflict !!!"); + } + consensusInfos.add(consensusInfo); + } + } + + private List nodeConfigs(List sharedConfigs) { + + List nodeConfigs = new ArrayList<>(); + + if (sharedConfigs != null && !sharedConfigs.isEmpty()) { + for (int i = 0; i < sharedConfigs.size(); i++) { + + PeerSharedConfig sharedConfig = sharedConfigs.get(i); + + String consensusNode = sharedConfig.getConsensusNode(); + + String[] hostAndPort = consensusNode.split(":"); + + nodeConfigs.add(String.format(BftsmartConstant.HOST_FORMAT, i, hostAndPort[0])); + + nodeConfigs.add(String.format(BftsmartConstant.PORT_FORMAT, i, hostAndPort[1])); + + nodeConfigs.add(String.format(BftsmartConstant.SECURE_FORMAT, i, false)); + + } + } + + return nodeConfigs; + } + + private int nodeFNum(int nodeNum) { + /** + * 3F+1 + * + * 假设有4个节点,则可有一个,若有N个,则N-1/3 + */ + if (nodeNum < 4) { + return 0; + } + return (nodeNum - 1) / 3; + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConstant.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConstant.java new file mode 100644 index 00000000..78afdbe4 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/BftsmartConstant.java @@ -0,0 +1,17 @@ +package com.jd.blockchain.ump.service.consensus.providers; + +public class BftsmartConstant { + + public static final String HOST_FORMAT = "system.server.%s.network.host=%s"; + + public static final String PORT_FORMAT = "system.server.%s.network.port=%s"; + + public static final String SECURE_FORMAT = "system.server.%s.network.secure=%s"; + + public static final String SERVERS_NUM_PREFIX = "system.servers.num"; + + public static final String SERVERS_F_PREFIX = "system.servers.f"; + + public static final String INIT_VIEW_PREFIX = "system.initial.view"; + +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/MsgQueueConsensusProvider.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/MsgQueueConsensusProvider.java new file mode 100644 index 00000000..d4b197e7 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/service/consensus/providers/MsgQueueConsensusProvider.java @@ -0,0 +1,41 @@ +package com.jd.blockchain.ump.service.consensus.providers; + +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.service.consensus.ConsensusProvider; + +import java.util.List; +import java.util.Properties; + +public class MsgQueueConsensusProvider implements ConsensusProvider { + + private static final String MSGQUEUE_PROVIDER = "com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider"; + + private static final String MSGQUEUE_CONFIG_FILE = "mq.config"; + + private Properties msgQueueProps; + + @Override + public String provider() { + return MSGQUEUE_PROVIDER; + } + + @Override + public String configFilePath() { + return MSGQUEUE_CONFIG_FILE; + } + + @Override + public void setConfig(Properties properties) { + this.msgQueueProps = properties; + } + + @Override + public Properties getConfig() { + return msgQueueProps; + } + + @Override + public byte[] handleSharedConfigs(List sharedConfigs) { + return new byte[0]; + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/Base58Utils.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/Base58Utils.java new file mode 100644 index 00000000..ff74ba9b --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/Base58Utils.java @@ -0,0 +1,153 @@ +package com.jd.blockchain.ump.util; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +public class Base58Utils { + + public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + + private static final int[] INDEXES = new int[128]; + + static { + for (int i = 0; i < INDEXES.length; i++) { + INDEXES[i] = -1; + } + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + input = copyOfRange(input, 0, input.length); + // Count leading zeroes. + int zeroCount = 0; + while (zeroCount < input.length && input[zeroCount] == 0) { + ++zeroCount; + } + // The actual encoding. + byte[] temp = new byte[input.length * 2]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input.length) { + byte mod = divmod58(input, startAt); + if (input[startAt] == 0) { + ++startAt; + } + temp[--j] = (byte) ALPHABET[mod]; + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.length && temp[j] == ALPHABET[0]) { + ++j; + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = (byte) ALPHABET[0]; + } + + byte[] output = copyOfRange(temp, j, temp.length); + try { + return new String(output, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + public static byte[] decode(String input) throws IllegalArgumentException { + if (input.length() == 0) { + return new byte[0]; + } + byte[] input58 = new byte[input.length()]; + // Transform the String to a base58 byte sequence + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + + int digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = INDEXES[c]; + } + if (digit58 < 0) { + throw new IllegalArgumentException("Illegal character " + c + " at " + i); + } + + input58[i] = (byte) digit58; + } + // Count leading zeroes + int zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + byte[] temp = new byte[input.length()]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input58.length) { + byte mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + + temp[--j] = mod; + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.length && temp[j] == 0) { + ++j; + } + + return copyOfRange(temp, j - zeroCount, temp.length); + } + + public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { + return new BigInteger(1, decode(input)); + } + + // + // number -> number / 58, returns number % 58 + // + private static byte divmod58(byte[] number, int startAt) { + int remainder = 0; + for (int i = startAt; i < number.length; i++) { + int digit256 = (int) number[i] & 0xFF; + int temp = remainder * 256 + digit256; + + number[i] = (byte) (temp / 58); + + remainder = temp % 58; + } + + return (byte) remainder; + } + + // + // number -> number / 256, returns number % 256 + // + private static byte divmod256(byte[] number58, int startAt) { + int remainder = 0; + for (int i = startAt; i < number58.length; i++) { + int digit58 = (int) number58[i] & 0xFF; + int temp = remainder * 58 + digit58; + + number58[i] = (byte) (temp / 256); + + remainder = temp % 256; + } + + return (byte) remainder; + } + + private static byte[] copyOfRange(byte[] source, int from, int to) { + byte[] range = new byte[to - from]; + System.arraycopy(source, from, range, 0, range.length); + + return range; + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/CommandUtils.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/CommandUtils.java new file mode 100644 index 00000000..3290d2ce --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/CommandUtils.java @@ -0,0 +1,133 @@ +package com.jd.blockchain.ump.util; + +import sun.jvmstat.monitor.MonitoredHost; +import sun.jvmstat.monitor.MonitoredVm; +import sun.jvmstat.monitor.MonitoredVmUtil; +import sun.jvmstat.monitor.VmIdentifier; + +import java.util.*; + +public class CommandUtils { + + public static void killVm(String processName) throws Exception { + + MonitoredVm activeVm = activeVm(processName); + + if (activeVm != null) { + killVm(activeVm); + } + } + + public static void killVm(MonitoredVm vm) throws Exception { + if (vm != null) { + int vmId = vm.getVmIdentifier().getLocalVmId(); + List killCmd = killCommand(vmId); + execute(killCmd); + } + } + + public static List toCommandList(String cmd) { + // 要求使用空格 + String[] cmdArray = cmd.split(" "); + + if (cmdArray.length > 0) { + return Arrays.asList(cmdArray); + } + + return null; + + } + + public static Process execute(List cmds) throws Exception { + + if (cmds == null || cmds.isEmpty()) { + throw new IllegalStateException("Command's List is NULL !!!"); + } + + ProcessBuilder pBuilder = new ProcessBuilder(cmds); + + Process process = pBuilder.start(); + + return process; + + } + + public static boolean executeAndVerify(List cmds, String verify) throws Exception { + + if (cmds == null || cmds.isEmpty()) { + throw new IllegalStateException("Command's List is NULL !!!"); + } + + ProcessBuilder pBuilder = new ProcessBuilder(cmds); + + pBuilder.start(); + + // 时延5s,再进行判断 + Thread.sleep(5000); + + return isActive(verify); + + } + + public static MonitoredVm activeVm(String processName) throws Exception { + + MonitoredHost localMonitored = MonitoredHost.getMonitoredHost("localhost"); + + Set activeVms = new HashSet<>(localMonitored.activeVms()); + + for (Integer vmId : activeVms) { + + try { + MonitoredVm vm = localMonitored.getMonitoredVm(new VmIdentifier("//" + vmId)); + + String vmProcessName = MonitoredVmUtil.mainClass(vm, true); + + if (vmProcessName.contains(processName)) { + return vm; + } + } catch (Exception e) { + // 此处异常打印即可,不需要处理 + System.err.println(e); + } + } + + return null; + } + + public static boolean isActive(String processName) throws Exception { + + MonitoredVm activeVm = activeVm(processName); + + return activeVm != null; + } + + public static String mainArgs(MonitoredVm vm) { + if (vm != null) { + try { + return MonitoredVmUtil.mainArgs(vm); + } catch (Exception e) { + // 打印日志即可 + System.err.println(e); + } + } + return null; + } + + public static String mainArgs(String processName) throws Exception { + + return mainArgs(activeVm(processName)); + } + + public static List killCommand(int vmId) { + if (vmId > 1) { + List killCmd = new ArrayList<>(); + killCmd.add("kill"); + killCmd.add("-9"); + killCmd.add(String.valueOf(vmId)); + + return killCmd; + } + + throw new IllegalStateException(String.format("Can not kill Process ID = [%s]", vmId)); + } +} diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpClientPool.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpClientPool.java new file mode 100644 index 00000000..dd575d0d --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpClientPool.java @@ -0,0 +1,289 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.utils.http.agent.HttpClientPool + * Author: shaozhuguang + * Department: 区块链研发部 + * Date: 2019/1/14 下午3:20 + * Description: + */ +package com.jd.blockchain.ump.util; + +import org.apache.http.*; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.UnsupportedEncodingException; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * + * @author shaozhuguang + * @create 2019/1/14 + * @since 1.0.0 + */ + +public class HttpClientPool { + + private static final int TEN_MINUTE = 600 * 1000; + + private static final int TIME_OUT = TEN_MINUTE; + + private static final int CONNECT_TIME_OUT = TEN_MINUTE; + + private static final int SOCKET_TIME_OUT = TEN_MINUTE; + + private static final int MAX_TOTAL = 200; + + private static final int MAX_PER_ROUTE = 40; + + private static final int MAX_ROUTE = 100; + + private static final int RETRY_COUNT = 5; + + private static final String DEFAULT_CHARSET = "UTF-8"; + + private static final Map httpClients = new ConcurrentHashMap<>(); + + private final static Lock lock = new ReentrantLock(); + + private static void config(HttpRequestBase httpRequestBase) { + // 配置请求的超时设置 + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(TIME_OUT) + .setConnectTimeout(CONNECT_TIME_OUT) + .setSocketTimeout(SOCKET_TIME_OUT) + .build(); + httpRequestBase.setConfig(requestConfig); + } + + /** + * 获取HttpClient对象 + * + * @param url + * @return + */ + public static CloseableHttpClient getHttpClient(String url) { + String hostName = url.split("/")[2]; + int port = 80; + if (hostName.contains(":")) { + String[] arr = hostName.split(":"); + hostName = arr[0]; + port = Integer.parseInt(arr[1]); + } + return getHttpClient(hostName, port); + } + + /** + * 获取HttpClient对象 + * + * @param hostName + * @param port + * @return + */ + public static CloseableHttpClient getHttpClient(String hostName, int port) { + String key = hostName + ":" + port; + CloseableHttpClient httpClient = httpClients.get(key); + if (httpClient == null) { + try { + lock.lock(); + if (httpClient == null) { + httpClient = createHttpClient(MAX_TOTAL, MAX_PER_ROUTE, MAX_ROUTE, hostName, port); + httpClients.put(key, httpClient); + } + } finally { + lock.unlock(); + } + } + return httpClient; + } + + /** + * 创建HttpClient + * + * @param maxTotal + * @param maxPerRoute + * @param maxRoute + * @param hostname + * @param port + * @return + */ + public static CloseableHttpClient createHttpClient(int maxTotal, + int maxPerRoute, int maxRoute, String hostname, int port) { + ConnectionSocketFactory plainsf = PlainConnectionSocketFactory + .getSocketFactory(); + LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory + .getSocketFactory(); + Registry registry = RegistryBuilder + . create().register("http", plainsf) + .register("https", sslsf).build(); + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager( + registry); + cm.setMaxTotal(maxTotal); + cm.setDefaultMaxPerRoute(maxPerRoute); + HttpHost httpHost = new HttpHost(hostname, port); + cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute); + HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> { + if (executionCount >= RETRY_COUNT) {// 最多重试5次 + return false; + }else if (exception instanceof NoHttpResponseException) { + return true; + }else if (exception instanceof SSLException) { + return false; + }else if (exception instanceof InterruptedIOException) { + return false; + }else if (exception instanceof SSLHandshakeException) { + return false; + }else if (exception instanceof UnknownHostException) { + return false; + }else if (exception instanceof ConnectTimeoutException) { + return false; + } + + HttpClientContext clientContext = HttpClientContext + .adapt(context); + HttpRequest request = clientContext.getRequest(); + if (!(request instanceof HttpEntityEnclosingRequest)) { + return true; + } + return false; + }; + + CloseableHttpClient httpClient = HttpClients.custom() + .setConnectionManager(cm) + .setRetryHandler(httpRequestRetryHandler).build(); + + return httpClient; + } + + private static void setPostParams(HttpPost httpPost, + Map params) { + List nameValuePairs = new ArrayList<>(); + Set keySet = params.keySet(); + for (String key : keySet) { + nameValuePairs.add(new BasicNameValuePair(key, params.get(key).toString())); + } + try { + httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET)); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + private static void setJsonPostParams(HttpPost httpPost, String json) { + httpPost.addHeader("Content-type","application/json; charset=utf-8"); + httpPost.setHeader("Accept", "application/json"); + httpPost.setEntity(new StringEntity(json, Charset.forName("UTF-8"))); + } + + /** + * POST请求 + * + * @param url + * @param params + * @return String + * @throws IOException + */ + public static String post(String url, Map params) throws IOException { + HttpPost httpPost = new HttpPost(url); + config(httpPost); + setPostParams(httpPost, params); + try (CloseableHttpResponse response = httpPost(url, httpPost)) { + return parseResponse(response); + } + } + + public static String jsonPost(String url, String json) throws IOException { + HttpPost httpPost = new HttpPost(url); + config(httpPost); + setJsonPostParams(httpPost, json); + try (CloseableHttpResponse response = httpPost(url, httpPost)) { + return parseResponse(response); + } + } + + /** + * GET请求 + * + * @param url + * @return String + */ + public static String get(String url) throws IOException { + HttpGet httpGet = new HttpGet(url); + config(httpGet); + try (CloseableHttpResponse response = httpGet(url, httpGet)) { + return parseResponse(response); + } + } + + /** + * Get请求的真实执行 + * + * @param url + * @param httpGet + * @return + * @throws IOException + */ + private static CloseableHttpResponse httpGet(String url, HttpGet httpGet) throws IOException { + return getHttpClient(url) + .execute(httpGet, HttpClientContext.create()); + } + + /** + * POST请求的真实执行 + * + * @param url + * @param httpPost + * @return + * @throws IOException + */ + private static CloseableHttpResponse httpPost(String url, HttpPost httpPost) throws IOException { + return getHttpClient(url) + .execute(httpPost, HttpClientContext.create()); + } + + /** + * 解析response + * + * @param response + * @return + * @throws IOException + */ + private static String parseResponse(CloseableHttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + String result = EntityUtils.toString(entity, DEFAULT_CHARSET); + EntityUtils.consume(entity); + return result; + } +} \ No newline at end of file diff --git a/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpJsonClientUtils.java b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpJsonClientUtils.java new file mode 100644 index 00000000..04c8c927 --- /dev/null +++ b/source/manager/ump-service/src/main/java/com/jd/blockchain/ump/util/HttpJsonClientUtils.java @@ -0,0 +1,61 @@ +package com.jd.blockchain.ump.util; + +import com.alibaba.fastjson.JSON; +import com.jd.blockchain.ump.model.MasterAddr; +import com.jd.blockchain.ump.model.web.WebResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpJsonClientUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpJsonClientUtils.class); + + public static T httpPost(MasterAddr masterAddr, String url, Object body, Class returnType, boolean isWrapper) { + + try { + String responseJson = HttpClientPool.jsonPost(masterAddr.toHttpUrl() + url, JSON.toJSONString(body)); + + LOGGER.info("Http Post Receive info =[ {} ] from {} ", responseJson, masterAddr.toHttpUrl() + url); + + return response(responseJson, returnType, isWrapper); + } catch (Exception e) { + + LOGGER.error("HttpPostRequestException {}", e.getMessage()); + + throw new IllegalStateException(e); + } + } + + public static T httpGet(String url, Class returnType, boolean isWrapper) { + try { + String responseJson = HttpClientPool.get(url); + + LOGGER.info("Http Get Receive info =[ {} ] from {} ", responseJson, url); + + return response(responseJson, returnType, isWrapper); + + } catch (Exception e) { + + LOGGER.error("HttpGetRequestException {}", e.toString()); + + throw new IllegalStateException(e); + } + } + + private static T response(String responseJson, Class returnType, boolean isWrapper) { + if (isWrapper) { + // 封装类型的情况下使用的是WebResponse + WebResponse webResponse = JSON.parseObject(responseJson, WebResponse.class); + LOGGER.info("Wrapper JSON Data = {}", JSON.toJSONString(webResponse)); + return webResponse.getData(); + } + + if (!JSON.isValid(responseJson)) { + return (T)responseJson; + } + // 对responseJson进行转换 + T data = JSON.parseObject(responseJson, returnType); + LOGGER.info("UnWrapper JSON Data = {}", JSON.toJSONString(data)); + return data; + } +} diff --git a/source/manager/ump-web/pom.xml b/source/manager/ump-web/pom.xml new file mode 100644 index 00000000..658f3205 --- /dev/null +++ b/source/manager/ump-web/pom.xml @@ -0,0 +1,83 @@ + + + + + manager + com.jd.blockchain + 1.1.0-SNAPSHOT + + 4.0.0 + + ump-web + + ump-web + + + UTF-8 + 1.8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-devtools + true + + + + com.jd.blockchain + ump-service + ${project.version} + + + + com.jd.blockchain + ump-model + ${project.version} + + + + com.jd.blockchain + utils-common + ${project.version} + + + + com.jd.blockchain + ump-explorer + + + + junit + junit + test + + + diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpDBController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpDBController.java new file mode 100644 index 00000000..be55fa34 --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpDBController.java @@ -0,0 +1,22 @@ +package com.jd.blockchain.ump.controller; + +import com.jd.blockchain.ump.dao.DBConnection; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(path = "/db/") +public class UmpDBController { + + @Autowired + private DBConnection dbConnection; + + @RequestMapping(method = RequestMethod.GET, path = "read/{key}") + public String read(@PathVariable(name = "key") String key) { + + return dbConnection.get(key); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java new file mode 100644 index 00000000..3b274530 --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java @@ -0,0 +1,69 @@ +package com.jd.blockchain.ump.controller; + +import com.jd.blockchain.ump.model.user.UserKeyBuilder; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.ump.model.user.UserKeysVv; +import com.jd.blockchain.ump.service.UmpStateService; +import com.jd.blockchain.ump.service.UtilService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping(path = "/keys/") +public class UmpKeyController { + + @Autowired + private UtilService utilService; + + @Autowired + private UmpStateService umpStateService; + + @RequestMapping(method = RequestMethod.POST, path = "create") + public UserKeysVv create(@RequestBody final UserKeyBuilder builder) { + + // 使用种子生成公私钥 + UserKeys userKeys = utilService.create(builder); + + // 将userKeys保存至数据库 + umpStateService.save(userKeys); + + return userKeys.toUserKeysVv(); + } + + @RequestMapping(method = RequestMethod.GET, path = "list") + public List list() { + + // 从数据库中读取,返回 + return umpStateService.readUserKeysVvList(); + } + + @RequestMapping(method = RequestMethod.GET, path = "read/{user}/{pubKey}") + public UserKeysVv read(@PathVariable(name = "user") int userId, + @PathVariable(name = "pubKey") String pubKey) { + + UserKeys userKeys = utilService.read(userId); + + if (userKeys != null) { + if (userKeys.getPubKey().equals(pubKey)) { + + return userKeys.toUserKeysVv(); + } + } + throw new IllegalStateException(String.format("Can not find UserKeys by %s", pubKey)); + } + + @RequestMapping(method = RequestMethod.GET, path = "resolve/{user}/{pwd}") + public UserKeys resolve(@PathVariable(name = "user") int userId, + @PathVariable(name = "pwd") String pwd) { + + UserKeys userKeys = utilService.read(userId); + + if (utilService.verify(userKeys, pwd)) { + + return userKeys; + } + throw new IllegalStateException(String.format("Can not resolve UserKeys by %s", pwd)); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java new file mode 100644 index 00000000..753ab3c0 --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java @@ -0,0 +1,68 @@ +package com.jd.blockchain.ump.controller; + +import com.jd.blockchain.ump.model.PeerSharedConfigs; +import com.jd.blockchain.ump.model.config.LedgerConfig; +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.state.InstallSchedule; +import com.jd.blockchain.ump.model.state.LedgerMasterInstall; +import com.jd.blockchain.ump.service.UmpService; +import com.jd.blockchain.ump.service.UmpStateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping(path = "/master/") +public class UmpMasterController { + + @Autowired + private UmpService umpService; + + @Autowired + private UmpStateService umpStateService; + + /** + * 需要支持的接口 + * 1、接收节点Share的信息 + * 2、接收节点发送来的状态信息 + * 3、接收前端查看某些节点状态的请求 + */ + @RequestMapping(method = RequestMethod.POST, path = "share") + public LedgerConfig share(@RequestBody final PeerLocalConfig sharedConfig) { + + PeerSharedConfigs sharedConfigs = umpService.loadPeerSharedConfigs(sharedConfig); + + if (sharedConfigs == null) { + throw new IllegalStateException("PeerSharedConfig may be exits Conflict !!!"); + } + + return umpService.response(sharedConfigs, sharedConfig); + } + + @RequestMapping(method = RequestMethod.POST, path = "receive") + public String receive(@RequestBody final InstallSchedule installSchedule) { + + try { + umpStateService.save(installSchedule, null); + } catch (Exception e) { + return "FAIL"; + } + + return "SUCCESS"; + } + + @RequestMapping(method = RequestMethod.GET, path = "read/{ledgerKey}") + public Map> readState(@PathVariable(name = "ledgerKey") String ledgerKey) { + + return umpStateService.readStates(ledgerKey); + } + + @RequestMapping(method = RequestMethod.GET, path = "list") + public List ledgerInstallList() { + + // 返回当前Master收到的所有节点所有的安装信息 + return umpStateService.readLedgerMasterInstalls(); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerController.java new file mode 100644 index 00000000..9222b14c --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerController.java @@ -0,0 +1,147 @@ +package com.jd.blockchain.ump.controller; + +import com.jd.blockchain.ump.model.MasterAddr; +import com.jd.blockchain.ump.model.UmpConstant; +import com.jd.blockchain.ump.model.config.LedgerConfig; +import com.jd.blockchain.ump.model.config.LedgerIdentification; +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.config.PeerSharedConfigVv; +import com.jd.blockchain.ump.model.state.*; +import com.jd.blockchain.ump.model.user.UserKeys; +import com.jd.blockchain.ump.service.UmpService; +import com.jd.blockchain.ump.service.UmpStateService; +import com.jd.blockchain.ump.service.UtilService; +import com.jd.blockchain.ump.util.HttpJsonClientUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping(path = "/peer/") +public class UmpPeerController { + + @Autowired + private UmpService umpService; + + @Autowired + private UmpStateService umpStateService; + + @Autowired + private UmpMasterController masterController; + + @Autowired + private UtilService utilService; + +// @RequestMapping(method = RequestMethod.POST, path = "share") + public LedgerIdentification share(@RequestBody PeerLocalConfig localConfig) { + + //首先校验配置信息 + localConfig.verify(); + + MasterAddr masterAddr = localConfig.masterAddr(); + + LedgerConfig ledgerConfig; + + if (localConfig.master()) { + // 当前节点本身是master,直接调用Controller方法 + ledgerConfig = masterController.share(localConfig); + } else { + ledgerConfig = HttpJsonClientUtils.httpPost(masterAddr, UmpConstant.REQUEST_SHARED_URL, localConfig, LedgerConfig.class, false); + } + + if (ledgerConfig == null) { + // 未加载成功 + throw new IllegalStateException("Can not load Ledger-Config's Data from Master Node !!!"); + } + + String ledgerAndNodeKey = umpService.save(masterAddr, ledgerConfig, localConfig); + + int nodeId = ledgerConfig.getInitConfig().nodeId(localConfig.getPubKey()); + + LedgerIdentification identification = new LedgerIdentification(nodeId, localConfig, + masterAddr, ledgerAndNodeKey, ledgerConfig.getInitConfig()); + + // 将数据写入数据库 + umpStateService.save(identification); + + return identification; + } + + @RequestMapping(method = RequestMethod.POST, path = "share") + public LedgerIdentification share(@RequestBody PeerSharedConfigVv sharedConfigVv) { + + String pubKey = sharedConfigVv.getPubKey(); + + if (pubKey == null || pubKey.length() == 0) { + throw new IllegalStateException("Public Key can not be empty !!!"); + } + + // 获取对应的UsersKey,转换为LocalConfig + UserKeys userKeys = utilService.read(sharedConfigVv.getUserId()); + + if (userKeys == null || !pubKey.equals(userKeys.getPubKey())) { + throw new IllegalStateException(String.format("Can not find UserKeys by %s", pubKey)); + } + + PeerLocalConfig localConfig = sharedConfigVv.toPeerLocalConfig(userKeys); + + return share(localConfig); + } + + @RequestMapping(method = RequestMethod.POST, path = "install/{ledgerAndNodeKey}") + public PeerInstallSchedules install(@PathVariable(name = "ledgerAndNodeKey") String ledgerAndNodeKey) { + + return umpService.install(ledgerAndNodeKey); + } + + @RequestMapping(method = RequestMethod.POST, path = "init/{ledgerAndNodeKey}") + public PeerInstallSchedules init(@PathVariable(name = "ledgerAndNodeKey") String ledgerAndNodeKey) { + + return umpService.init(ledgerAndNodeKey); + } + + @RequestMapping(method = RequestMethod.POST, path = "startup") + public PeerStartupSchedules startup() { + + return umpService.startup(); + } + +// @RequestMapping(method = RequestMethod.POST, path = "stop/{ledgerAndNodeKey}") + public boolean stop(@PathVariable(name = "ledgerAndNodeKey") String ledgerAndNodeKey) { + + return umpService.stop(ledgerAndNodeKey); + } + + @RequestMapping(method = RequestMethod.POST, path = "stop") + public boolean stop() { + + return umpService.stop(); + } + + @RequestMapping(method = RequestMethod.GET, path = "init/read/{ledgerAndNodeKey}") + public PeerInstallSchedules readInitState(@PathVariable(name = "ledgerAndNodeKey") String ledgerAndNodeKey) { + + return umpStateService.readInitState(ledgerAndNodeKey); + } + + @RequestMapping(method = RequestMethod.GET, path = "list") + public List ledgerInstallList() { + + // 返回当前Peer节点所有的安装信息 + return umpStateService.readLedgerPeerInstalls(); + } + + public List ledgerInitedList(@RequestParam(name = "search", required = false) String search) { + + // 返回当前Peer节点所有的初始化后信息 + return umpStateService.readLedgerPeerIniteds(search); + } + + @RequestMapping(method = RequestMethod.GET, path = "initeds") + public List ledgerIniteds(@RequestParam(name = "search", required = false) String search) { + + // 返回当前Peer节点所有的初始化后信息 + return umpStateService.readLedgerIniteds(search); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerSimulateController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerSimulateController.java new file mode 100644 index 00000000..35f0e8af --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpPeerSimulateController.java @@ -0,0 +1,64 @@ +package com.jd.blockchain.ump.controller; + +import com.jd.blockchain.ump.model.config.LedgerIdentification; +import com.jd.blockchain.ump.model.config.PeerLocalConfig; +import com.jd.blockchain.ump.model.state.PeerInstallSchedules; +import com.jd.blockchain.ump.service.UmpService; +import com.jd.blockchain.ump.service.UmpSimulateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@RestController +@RequestMapping(path = "/peer/") +public class UmpPeerSimulateController { + + private final Map ledgerAndNodeKeys = new ConcurrentHashMap<>(); + + @Autowired + private UmpService umpService; + + @Autowired + private UmpSimulateService simulateService; + + @Autowired + private UmpPeerController peerController; + + @RequestMapping(method = RequestMethod.GET, path = "share/simulate/{node}") + public LedgerIdentification share(@PathVariable(name = "node") int nodeId) { + + boolean isMaster = false; + if (nodeId == 0) { + isMaster = true; + } + + PeerLocalConfig localConfig = simulateService.nodePeerLocalConfig(nodeId, isMaster); + + LedgerIdentification identification = peerController.share(localConfig); + + // 作为缓存使用 + ledgerAndNodeKeys.put(nodeId, identification.getLedgerAndNodeKey()); + + return identification; + } + + + @RequestMapping(method = RequestMethod.GET, path = "install/simulate/{node}") + public PeerInstallSchedules install(@PathVariable(name = "node") int nodeId) { + + String ledgerAndNodeKey = ledgerAndNodeKeys.get(nodeId); + + return umpService.install(ledgerAndNodeKey); + } + + @RequestMapping(method = RequestMethod.GET, path = "init/simulate/{node}") + public PeerInstallSchedules init(@PathVariable(name = "node") int nodeId) { + + return umpService.init(ledgerAndNodeKeys.get(nodeId)); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ControllerConfigurer.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ControllerConfigurer.java new file mode 100644 index 00000000..6c5be2f3 --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ControllerConfigurer.java @@ -0,0 +1,29 @@ +package com.jd.blockchain.ump.web; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class ControllerConfigurer implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + + // 添加打印日志的拦截器 + registry.addInterceptor(new LogPrintInterceptor()).addPathPatterns("/**"); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources"); + } + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("web/index.html"); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ExceptionResponseAdvice.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ExceptionResponseAdvice.java new file mode 100644 index 00000000..c079fccc --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/ExceptionResponseAdvice.java @@ -0,0 +1,38 @@ +package com.jd.blockchain.ump.web; + +import com.jd.blockchain.ump.model.web.ErrorCode; +import com.jd.blockchain.ump.model.web.WebResponse; +import com.jd.blockchain.utils.BusinessException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; + +@RestControllerAdvice +public class ExceptionResponseAdvice { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @ExceptionHandler(value = Exception.class) + @ResponseBody + public WebResponse json(HttpServletRequest req, Exception ex) { + + WebResponse.ErrorMessage message; + + String reqURL = "[" + req.getMethod() + "] " + req.getRequestURL().toString(); + + if (ex instanceof BusinessException) { + BusinessException businessException = (BusinessException) ex; + message = new WebResponse.ErrorMessage(businessException.getErrorCode(), businessException.getMessage()); + } else { + logger.error("Exception occurred! --[RequestURL=" + reqURL + "][" + ex.getClass().toString() + + "]" + ex.getMessage(), ex); + + message = new WebResponse.ErrorMessage(ErrorCode.UNEXPECTED.getValue(), ex.toString()); + } + return WebResponse.createFailureResult(message); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/JsonResponseAdvice.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/JsonResponseAdvice.java new file mode 100644 index 00000000..822aee64 --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/JsonResponseAdvice.java @@ -0,0 +1,51 @@ +package com.jd.blockchain.ump.web; + +import com.jd.blockchain.ump.model.config.LedgerConfig; +import com.jd.blockchain.ump.model.web.WebResponse; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice +public class JsonResponseAdvice implements ResponseBodyAdvice { + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + if (MappingJackson2HttpMessageConverter.class == converterType + && (returnType.getContainingClass().getName().startsWith("com.jd.blockchain.ump") + || returnType.getDeclaringClass().getName().startsWith("com.jd.blockchain.ump"))) { + return true; + } + return false; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, + Class> selectedConverterType, ServerHttpRequest request, + ServerHttpResponse response) { + if (body == null) { + return WebResponse.createSuccessResult(null); + } + + if (body instanceof ResponseEntity) { + return body; + } + + // LedgerConfig单独处理 + if (body instanceof LedgerConfig) { + return body; + } + + // 把返回结果自动转换为 WebResponse; + if (body instanceof WebResponse) { + return body; + } + return WebResponse.createSuccessResult(body); + } +} diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/LogPrintInterceptor.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/LogPrintInterceptor.java new file mode 100644 index 00000000..c68ef5e8 --- /dev/null +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/web/LogPrintInterceptor.java @@ -0,0 +1,32 @@ +package com.jd.blockchain.ump.web; + +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class LogPrintInterceptor implements HandlerInterceptor { + + private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 记录日志 + // 请求的参数: + String parameters = ""; + Map requestParameters = request.getParameterMap(); + if (requestParameters != null && !requestParameters.isEmpty()) { + parameters = JSON.toJSONString(requestParameters); + } + LOGGER.info("Request[{}][{}], parameters=[{}]", + request.getRequestURL().toString(), // 请求URL + request.getMethod(), // 请求的方法 + parameters); // 请求的参数 + + return true; + } +} From 9f0fe0855cef541405f73f892fd894d98e44a1cd Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Fri, 9 Aug 2019 09:08:18 +0800 Subject: [PATCH 035/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ump=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/com/jd/blockchain/AppTest.java | 20 +++ .../test/java/com/jd/blockchain/AppTest.java | 20 +++ source/deployment/deployment-peer/pom.xml | 7 +- .../src/main/resources/assembly.xml | 12 +- .../src/main/resources/scripts/jump-start.sh | 9 ++ .../src/main/resources/scripts/jump-stop.sh | 16 +++ source/manager/pom.xml | 49 +------ source/manager/ump-booter/pom.xml | 67 ++++++++++ .../java/com/jd/blockchain/ump/UmpBooter.java | 14 +- source/manager/ump-service/pom.xml | 5 + .../src/main/resources/bftsmart.config | 122 ++++++++++++++++++ .../ump-service/src/main/resources/mq.config | 0 source/pom.xml | 14 ++ .../test/java/com/jd/blockchain/AppTest.java | 20 +++ 14 files changed, 315 insertions(+), 60 deletions(-) create mode 100644 source/crypto/crypto-composite/src/test/java/com/jd/blockchain/AppTest.java create mode 100644 source/deployment/deployment-autotest/src/test/java/com/jd/blockchain/AppTest.java create mode 100644 source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh create mode 100644 source/deployment/deployment-peer/src/main/resources/scripts/jump-stop.sh create mode 100644 source/manager/ump-service/src/main/resources/bftsmart.config create mode 100644 source/manager/ump-service/src/main/resources/mq.config create mode 100644 source/tools/tools-joiner/src/test/java/com/jd/blockchain/AppTest.java diff --git a/source/crypto/crypto-composite/src/test/java/com/jd/blockchain/AppTest.java b/source/crypto/crypto-composite/src/test/java/com/jd/blockchain/AppTest.java new file mode 100644 index 00000000..941b907f --- /dev/null +++ b/source/crypto/crypto-composite/src/test/java/com/jd/blockchain/AppTest.java @@ -0,0 +1,20 @@ +package com.jd.blockchain; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} diff --git a/source/deployment/deployment-autotest/src/test/java/com/jd/blockchain/AppTest.java b/source/deployment/deployment-autotest/src/test/java/com/jd/blockchain/AppTest.java new file mode 100644 index 00000000..941b907f --- /dev/null +++ b/source/deployment/deployment-autotest/src/test/java/com/jd/blockchain/AppTest.java @@ -0,0 +1,20 @@ +package com.jd.blockchain; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} diff --git a/source/deployment/deployment-peer/pom.xml b/source/deployment/deployment-peer/pom.xml index d5f818ac..941178c7 100644 --- a/source/deployment/deployment-peer/pom.xml +++ b/source/deployment/deployment-peer/pom.xml @@ -17,20 +17,19 @@ com.jd.blockchain - manager + runtime-modular ${project.version} com.jd.blockchain - runtime-modular + runtime-modular-booter ${project.version} com.jd.blockchain - runtime-modular-booter + ump-booter ${project.version} - com.jd.blockchain storage-composite diff --git a/source/deployment/deployment-peer/src/main/resources/assembly.xml b/source/deployment/deployment-peer/src/main/resources/assembly.xml index e68857e2..9ad916a2 100644 --- a/source/deployment/deployment-peer/src/main/resources/assembly.xml +++ b/source/deployment/deployment-peer/src/main/resources/assembly.xml @@ -36,7 +36,6 @@ com.jd.blockchain:runtime-modular com.jd.blockchain:runtime-modular-booter com.jd.blockchain:peer - com.jd.blockchain:manager com.jd.blockchain:deployment-peer @@ -53,10 +52,19 @@ com.jd.blockchain:runtime-modular com.jd.blockchain:runtime-modular-booter com.jd.blockchain:peer - com.jd.blockchain:manager com.jd.blockchain:deployment-peer + + + false + true + ext + + com.jd.blockchain:ump-booter + + + diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh b/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh new file mode 100644 index 00000000..a31a5b28 --- /dev/null +++ b/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +HOME=$(cd `dirname $0`;cd ../; pwd) +UMP=$(ls $HOME/ext | grep ump-booter-) +if [ ! -n "UMP" ]; then + echo "Unified Management Platform Is Null !!!" +else + nohup java -jar -server -Djump.log=$HOME $HOME/ext/$UMP $* >$HOME/bin/jump.out 2>&1 & +fi \ No newline at end of file diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/jump-stop.sh b/source/deployment/deployment-peer/src/main/resources/scripts/jump-stop.sh new file mode 100644 index 00000000..b7155c88 --- /dev/null +++ b/source/deployment/deployment-peer/src/main/resources/scripts/jump-stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +#启动Home路径 +BOOT_HOME=$(cd `dirname $0`;cd ../; pwd) + +#获取进程PID +PID=`ps -ef | grep $BOOT_HOME/ext/ump-booter | grep -v grep | awk '{print $2}'` + +#通过Kill命令将进程杀死 +if [ -z "$PID" ]; then + echo "Unable to find UMP PID. stop aborted." +else + echo "Start to kill PID = $PID ..." + kill -9 $PID + echo "Unified Management Platform has been stopped ..." +fi \ No newline at end of file diff --git a/source/manager/pom.xml b/source/manager/pom.xml index cd6c9205..a56f3423 100644 --- a/source/manager/pom.xml +++ b/source/manager/pom.xml @@ -2,52 +2,17 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + ump-booter + ump-web + ump-service + ump-model + com.jd.blockchain jdchain-root 1.1.0-SNAPSHOT manager - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - - - org.springframework.boot - spring-boot-starter-log4j2 - - - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - + pom \ No newline at end of file diff --git a/source/manager/ump-booter/pom.xml b/source/manager/ump-booter/pom.xml index 63461b0f..69ae2ff4 100644 --- a/source/manager/ump-booter/pom.xml +++ b/source/manager/ump-booter/pom.xml @@ -32,6 +32,24 @@ + + com.jd.blockchain + ump-web + ${project.version} + + + + com.jd.blockchain + ump-service + ${project.version} + + + + com.jd.blockchain + ump-model + ${project.version} + + org.springframework.boot spring-boot-starter-log4j2 @@ -57,4 +75,53 @@ test + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + make-assembly + package + + single + + + jump + + src/main/resources/assembly.xml + + + + + + + + + + ${project.basedir}/libs + BOOT-INF/lib/ + + *.jar + + + + ${project.basedir}/src/main/resources + BOOT-INF/classes/ + + log4j2-jump.xml + *.txt + *.properties + + + + + diff --git a/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java b/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java index 97656b03..4bf8d7ae 100644 --- a/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java +++ b/source/manager/ump-booter/src/main/java/com/jd/blockchain/ump/UmpBooter.java @@ -30,22 +30,12 @@ public class UmpBooter { private static final String CONFIG_PROP_DB_URL_DEFAULT = "rocksdb://#project#/jumpdb"; public static void main(String[] args) { - try { - startServer(server(args)); - System.out.println("Server Start SUCCESS !!!"); - } catch (Exception e) { - e.printStackTrace(); - System.err.printf("Server Start FAIL -> %s, Exit JVM !!!", e.toString()); - // 正常退出 - System.exit(0); - } + startServer(server(args)); + System.out.println("Unified Management Platform Server Start SUCCESS !!!"); } private static void startServer(Server server) { - System.out.printf("server.address = %s, server.port = %s, db.url = %s \r\n", - server.host, server.port, server.dbUrl); - List argList = new ArrayList<>(); argList.add(String.format("--server.address=%s", server.host)); argList.add(String.format("--server.port=%s", server.port)); diff --git a/source/manager/ump-service/pom.xml b/source/manager/ump-service/pom.xml index 99b5f2c4..b01c60cc 100644 --- a/source/manager/ump-service/pom.xml +++ b/source/manager/ump-service/pom.xml @@ -92,6 +92,11 @@ reflections + + com.google.guava + guava + + org.apache.httpcomponents httpclient diff --git a/source/manager/ump-service/src/main/resources/bftsmart.config b/source/manager/ump-service/src/main/resources/bftsmart.config new file mode 100644 index 00000000..5ec6e530 --- /dev/null +++ b/source/manager/ump-service/src/main/resources/bftsmart.config @@ -0,0 +1,122 @@ + +# Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags +# +# Licensed 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. + +############################################ +####### Communication Configurations ####### +############################################ + +#HMAC algorithm used to authenticate messages between processes (HmacMD5 is the default value) +#This parameter is not currently being used +#system.authentication.hmacAlgorithm = HmacSHA1 + +#Specify if the communication system should use a thread to send data (true or false) +system.communication.useSenderThread = true + +#Force all processes to use the same public/private keys pair and secret key. This is useful when deploying experiments +#and benchmarks, but must not be used in production systems. +system.communication.defaultkeys = true + +############################################ +### Replication Algorithm Configurations ### +############################################ + +#Number of servers in the group +system.servers.num = 4 + +#Maximum number of faulty replicas +system.servers.f = 1 + +#Timeout to asking for a client request +system.totalordermulticast.timeout = 2000 + + +#Maximum batch size (in number of messages) +system.totalordermulticast.maxbatchsize = 400 + +#Number of nonces (for non-determinism actions) generated +system.totalordermulticast.nonces = 10 + +#if verification of leader-generated timestamps are increasing +#it can only be used on systems in which the network clocks +#are synchronized +system.totalordermulticast.verifyTimestamps = false + +#Quantity of messages that can be stored in the receive queue of the communication system +system.communication.inQueueSize = 500000 + +# Quantity of messages that can be stored in the send queue of each replica +system.communication.outQueueSize = 500000 + +#Set to 1 if SMaRt should use signatures, set to 0 if otherwise +system.communication.useSignatures = 0 + +#Set to 1 if SMaRt should use MAC's, set to 0 if otherwise +system.communication.useMACs = 1 + +#Set to 1 if SMaRt should use the standard output to display debug messages, set to 0 if otherwise +system.debug = 0 + +#Print information about the replica when it is shutdown +system.shutdownhook = true + +############################################ +###### State Transfer Configurations ####### +############################################ + +#Activate the state transfer protocol ('true' to activate, 'false' to de-activate) +system.totalordermulticast.state_transfer = true + +#Maximum ahead-of-time message not discarded +system.totalordermulticast.highMark = 10000 + +#Maximum ahead-of-time message not discarded when the replica is still on EID 0 (after which the state transfer is triggered) +system.totalordermulticast.revival_highMark = 10 + +#Number of ahead-of-time messages necessary to trigger the state transfer after a request timeout occurs +system.totalordermulticast.timeout_highMark = 200 + +############################################ +###### Log and Checkpoint Configurations ### +############################################ + +system.totalordermulticast.log = true +system.totalordermulticast.log_parallel = false +system.totalordermulticast.log_to_disk = false +system.totalordermulticast.sync_log = false + +#Period at which BFT-SMaRt requests the state to the application (for the state transfer state protocol) +system.totalordermulticast.checkpoint_period = 1000 +system.totalordermulticast.global_checkpoint_period = 120000 + +system.totalordermulticast.checkpoint_to_disk = false +system.totalordermulticast.sync_ckp = false + + +############################################ +###### Reconfiguration Configurations ###### +############################################ + +#Replicas ID for the initial view, separated by a comma. +# The number of replicas in this parameter should be equal to that specified in 'system.servers.num' +system.initial.view = 0,1,2,3 + +#The ID of the trust third party (TTP) +system.ttp.id = 7002 + +#This sets if the system will function in Byzantine or crash-only mode. Set to "true" to support Byzantine faults +system.bft = true + +#Custom View Storage; +#view.storage.handler=bftsmart.reconfiguration.views.DefaultViewStorage \ No newline at end of file diff --git a/source/manager/ump-service/src/main/resources/mq.config b/source/manager/ump-service/src/main/resources/mq.config new file mode 100644 index 00000000..e69de29b diff --git a/source/pom.xml b/source/pom.xml index 867606b1..2e93f9ba 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -43,6 +43,8 @@ 0.8.1-SNAPSHOT 0.0.8.RELEASE 0.7.0.RELEASE + 1.0.0-SNAPSHOT + 2.4 @@ -116,6 +118,18 @@ ${explorer.version} + + com.jd.blockchain + ump-explorer + ${ump-explorer.version} + + + + commons-io + commons-io + ${commons-io.version} + + junit diff --git a/source/tools/tools-joiner/src/test/java/com/jd/blockchain/AppTest.java b/source/tools/tools-joiner/src/test/java/com/jd/blockchain/AppTest.java new file mode 100644 index 00000000..941b907f --- /dev/null +++ b/source/tools/tools-joiner/src/test/java/com/jd/blockchain/AppTest.java @@ -0,0 +1,20 @@ +package com.jd.blockchain; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} From 9641c82e9308259554ad84b0bf1d19c768fd542a Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Sat, 10 Aug 2019 19:20:13 +0800 Subject: [PATCH 036/124] Fixed compilation error and refactored the API of querying meta info of Ledger; --- newfeatures-authorize.txt | 6 + .../com/jd/blockchain/consts/DataCodes.java | 25 +- .../gateway/service/GatewayQueryService.java | 50 - .../service/GatewayQueryServiceHandler.java | 144 --- .../gateway/web/BlockBrowserController.java | 1064 +++++++++-------- .../web/GatewayWebServerConfigurer.java | 3 + .../ledger/core/LedgerAdminAccount.java | 9 +- .../ledger/core/LedgerAdministration.java | 16 - .../ledger/core/LedgerRepository.java | 3 +- .../ledger/core/impl/LedgerManager.java | 2 +- .../ledger/core/impl/LedgerQueryService.java | 19 +- .../core/impl/LedgerRepositoryImpl.java | 15 +- .../core/impl/LedgerTransactionalEditor.java | 2 +- .../impl/handles/ContractLedgerContext.java | 5 + .../ledger/LedgerAdminAccountTest.java | 19 +- .../blockchain/ledger/LedgerMetaDataTest.java | 11 +- .../jd/blockchain/ledger/LedgerAdminInfo.java | 23 + .../jd/blockchain/ledger/LedgerMetadata.java | 8 - .../transaction/BlockchainQueryService.java | 267 +++-- .../peer/web/LedgerQueryController.java | 13 +- .../peer/web/ManagementController.java | 9 +- .../sdk/proxy/BlockchainServiceProxy.java | 5 + .../blockchain/mocker/MockerNodeContext.java | 37 +- 23 files changed, 834 insertions(+), 921 deletions(-) create mode 100644 newfeatures-authorize.txt delete mode 100644 source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java delete mode 100644 source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdministration.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java diff --git a/newfeatures-authorize.txt b/newfeatures-authorize.txt new file mode 100644 index 00000000..3bb8e333 --- /dev/null +++ b/newfeatures-authorize.txt @@ -0,0 +1,6 @@ + + +1、网关节点移除查询接口 HTTP GET ledgers/{ledgerHash}/settings (出于设计合理性原因) + + +2、网关节点增加查询接口 HTTP GET ledgers/{ledgerHash}/admininfo ,接口返回指定账本管理配置信息; \ No newline at end of file diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 4b670ba4..7a49e2ba 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -9,7 +9,7 @@ package com.jd.blockchain.consts; public interface DataCodes { public static final int BYTES_VALUE = 0x080; - + public static final int BYTES_VALUE_LIST = 0x081; public static final int BLOCK_CHAIN_IDENTITY = 0x090; @@ -21,6 +21,8 @@ public interface DataCodes { public static final int BLOCK_GENESIS = 0x120; public static final int DATA_SNAPSHOT = 0x130; + + public static final int LEDGER_ADMIN_INFO = 0x131; public static final int TX = 0x200; @@ -52,14 +54,14 @@ public interface DataCodes { public static final int TX_RESPONSE = 0x350; public static final int TX_OP_RESULT = 0x360; - + // enum types of permissions; public static final int ENUM_TX_PERMISSIONS = 0x401; public static final int ENUM_LEDGER_PERMISSIONS = 0x402; // contract types of metadata; public static final int METADATA = 0x600; - + public static final int METADATA_INIT_SETTING = 0x610; public static final int METADATA_INIT_PROPOSAL = 0x611; @@ -70,14 +72,14 @@ public interface DataCodes { public static final int METADATA_CONSENSUS_PARTICIPANT = 0x621; - // public static final int METADATA_CONSENSUS_NODE = 0x630; - - public static final int METADATA_CONSENSUS_SETTING = 0x631; - - // public static final int METADATA_PARTICIPANT_INFO = 0x640; +// public static final int METADATA_CONSENSUS_NODE = 0x630; +// +// public static final int METADATA_CONSENSUS_SETTING = 0x631; +// +// public static final int METADATA_PARTICIPANT_INFO = 0x640; public static final int METADATA_CRYPTO_SETTING = 0x642; - + public static final int METADATA_CRYPTO_SETTING_PROVIDER = 0x643; // public static final int ACCOUNT = 0x700; @@ -88,11 +90,10 @@ public interface DataCodes { public static final int DATA = 0x900; - //contract related; + // contract related; public static final int CONTRACT = 0xA00; - - //...0xA19 + // ...0xA19 public static final int HASH = 0xB00; public static final int HASH_OBJECT = 0xB10; diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java deleted file mode 100644 index 340527c7..00000000 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.jd.blockchain.gateway.service; - -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.sdk.ContractSettings; -import com.jd.blockchain.sdk.LedgerInitSettings; -import com.jd.blockchain.utils.Bytes; - -/** - * queryService only for gateway; - * @Author zhaogw - * @Date 2019/2/22 10:37 - */ -public interface GatewayQueryService { - /** - * get all ledgers hashs; - * @param fromIndex - * @param count - */ - HashDigest[] getLedgersHash(int fromIndex, int count); - - /** - * get the participants by range; - * @param ledgerHash - * @param fromIndex - * @param count - * @return - */ - ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash, int fromIndex, int count); - - /** - * 获取账本初始化配置信息 - * - * @param ledgerHash - * 账本Hash - * @return - */ - LedgerInitSettings getLedgerInitSettings(HashDigest ledgerHash); - - /** - * 获取账本指定合约信息 - * - * @param ledgerHash - * 账本Hash - * @param address - * 合约地址 - * @return - */ - ContractSettings getContractSettings(HashDigest ledgerHash, String address); -} diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java deleted file mode 100644 index 4421dde1..00000000 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.jd.blockchain.gateway.service; - -import com.jd.blockchain.consensus.ConsensusProvider; -import com.jd.blockchain.consensus.ConsensusProviders; -import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider; -import com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.gateway.PeerService; -import com.jd.blockchain.gateway.decompiler.utils.DecompilerUtils; -import com.jd.blockchain.ledger.ContractInfo; -import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.sdk.ContractSettings; -import com.jd.blockchain.sdk.LedgerInitSettings; -import com.jd.blockchain.utils.QueryUtil; -import com.jd.blockchain.utils.codec.HexUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import java.util.Arrays; - - -/** - * @Author zhaogw - * @Date 2019/2/22 10:39 - */ -@Component -public class GatewayQueryServiceHandler implements GatewayQueryService { - - @Autowired - private PeerService peerService; - - @Override - public HashDigest[] getLedgersHash(int fromIndex, int count) { - HashDigest ledgersHash[] = peerService.getQueryService().getLedgerHashs(); - int indexAndCount[] = QueryUtil.calFromIndexAndCount(fromIndex,count,ledgersHash.length); - HashDigest ledgersHashNew[] = Arrays.copyOfRange(ledgersHash,indexAndCount[0],indexAndCount[0]+indexAndCount[1]); - return ledgersHashNew; - } - - @Override - public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash, int fromIndex, int count) { - ParticipantNode participantNode[] = peerService.getQueryService().getConsensusParticipants(ledgerHash); - int indexAndCount[] = QueryUtil.calFromIndexAndCount(fromIndex,count,participantNode.length); - ParticipantNode participantNodesNew[] = Arrays.copyOfRange(participantNode,indexAndCount[0],indexAndCount[0]+indexAndCount[1]); - return participantNodesNew; - } - - @Override - public LedgerInitSettings getLedgerInitSettings(HashDigest ledgerHash) { - - ParticipantNode[] participantNodes = peerService.getQueryService().getConsensusParticipants(ledgerHash); - - LedgerMetadata ledgerMetadata = peerService.getQueryService().getLedgerMetadata(ledgerHash); - - return initLedgerInitSettings(participantNodes, ledgerMetadata); - } - - @Override - public ContractSettings getContractSettings(HashDigest ledgerHash, String address) { - ContractInfo contractInfo = peerService.getQueryService().getContract(ledgerHash, address); - return contractSettings(contractInfo); - } - - private ContractSettings contractSettings(ContractInfo contractInfo) { - ContractSettings contractSettings = new ContractSettings(contractInfo.getAddress(), contractInfo.getPubKey(), contractInfo.getRootHash()); - byte[] chainCodeBytes = contractInfo.getChainCode(); - // 将反编译chainCode - String mainClassJava = DecompilerUtils.decompileMainClassFromBytes(chainCodeBytes); - contractSettings.setChainCode(mainClassJava); - return contractSettings; - } - - /** - * 初始化账本配置 - * - * @param participantNodes - * 参与方列表 - * @param ledgerMetadata - * 账本元数据 - * @return - */ - private LedgerInitSettings initLedgerInitSettings(ParticipantNode[] participantNodes, LedgerMetadata ledgerMetadata) { - LedgerInitSettings ledgerInitSettings = new LedgerInitSettings(); - - // 设置参与方 - ledgerInitSettings.setParticipantNodes(participantNodes); - - // 设置共识设置 - ledgerInitSettings.setConsensusSettings(initConsensusSettings(ledgerMetadata)); - - // 设置参与方根Hash - ledgerInitSettings.setParticipantsHash(ledgerMetadata.getParticipantsHash()); - - // 设置算法配置 - ledgerInitSettings.setCryptoSetting(ledgerMetadata.getSetting().getCryptoSetting()); - - // 设置种子 - ledgerInitSettings.setSeed(initSeed(ledgerMetadata.getSeed())); - - // 设置共识协议 - ledgerInitSettings.setConsensusProtocol(ledgerMetadata.getSetting().getConsensusProvider()); - - return ledgerInitSettings; - } - - /** - * 初始化账本种子信息 - * - * @param seedBytes - * 种子的字节数组显示 - * @return - * 种子以十六进制方式显示,为方便阅读,每隔八个字符中间以"-"分割 - */ - private String initSeed(byte[] seedBytes) { - String seedString = HexUtils.encode(seedBytes); - // 每隔八个字符中加入一个一个横线 - StringBuffer seed = new StringBuffer(); - - for( int i = 0; i < seedString.length(); i++) { - char c = seedString.charAt(i); - if (i != 0 && i % 8 == 0) { - seed.append("-"); - } - seed.append(c); - } - - return seed.toString(); - } - - /** - * 初始化共识配置 - * - * @param ledgerMetadata - * 账本元数据 - * @return - */ - private ConsensusSettings initConsensusSettings(LedgerMetadata ledgerMetadata) { - String consensusProvider = ledgerMetadata.getSetting().getConsensusProvider(); - ConsensusProvider provider = ConsensusProviders.getProvider(consensusProvider); - byte[] consensusSettingsBytes = ledgerMetadata.getSetting().getConsensusSetting().toBytes(); - return provider.getSettingsFactory().getConsensusSettingsEncoder().decode(consensusSettingsBytes); - } -} diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java index 91c498aa..50255bd3 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java @@ -1,546 +1,580 @@ package com.jd.blockchain.gateway.web; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.PeerService; +import com.jd.blockchain.gateway.decompiler.utils.DecompilerUtils; import com.jd.blockchain.gateway.service.DataRetrievalService; -import com.jd.blockchain.gateway.service.GatewayQueryService; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.ContractInfo; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.KVInfoVO; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.sdk.BlockchainExtendQueryService; import com.jd.blockchain.sdk.ContractSettings; -import com.jd.blockchain.sdk.LedgerInitSettings; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.BaseConstant; import com.jd.blockchain.utils.ConsoleUtils; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.List; @RestController @RequestMapping(path = "/") public class BlockBrowserController implements BlockchainExtendQueryService { - private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(BlockBrowserController.class); + private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(BlockBrowserController.class); @Autowired private PeerService peerService; @Autowired - private DataRetrievalService dataRetrievalService; - - @Autowired - private GatewayQueryService gatewayQueryService; + private DataRetrievalService dataRetrievalService; - private String dataRetrievalUrl; + private String dataRetrievalUrl; private static final long BLOCK_MAX_DISPLAY = 3L; private static final long GENESIS_BLOCK_HEIGHT = 0L; - @Deprecated -// @RequestMapping(method = RequestMethod.GET, path = "ledgers") - @Override - public HashDigest[] getLedgerHashs() { - return peerService.getQueryService().getLedgerHashs(); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}") - @Override - public LedgerInfo getLedger(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getLedger(ledgerHash); - } - -// @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants") - @Override - public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { - return peerService.getQueryService().getConsensusParticipants(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/metadata") - @Override - public LedgerMetadata getLedgerMetadata(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getLedgerMetadata(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/settings") - public LedgerInitSettings getLedgerInitSettings(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return gatewayQueryService.getLedgerInitSettings(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks") - public LedgerBlock[] getBlocks(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); - long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); - List ledgerBlocks = new ArrayList<>(); - for (long blockHeight = maxBlockHeight; blockHeight > GENESIS_BLOCK_HEIGHT; blockHeight--) { - LedgerBlock ledgerBlock = peerService.getQueryService().getBlock(ledgerHash, blockHeight); - ledgerBlocks.add(0, ledgerBlock); - if (ledgerBlocks.size() == BLOCK_MAX_DISPLAY) { - break; - } - } - // 最后增加创世区块 - LedgerBlock genesisBlock = peerService.getQueryService().getBlock(ledgerHash, GENESIS_BLOCK_HEIGHT); - ledgerBlocks.add(0, genesisBlock); - LedgerBlock[] blocks = new LedgerBlock[ledgerBlocks.size()]; - ledgerBlocks.toArray(blocks); - return blocks; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}") - @Override - public LedgerBlock getBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - return peerService.getQueryService().getBlock(ledgerHash, blockHeight); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}") - @Override - public LedgerBlock getBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - return peerService.getQueryService().getBlock(ledgerHash, blockHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs/count") - @Override - public long getTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - return peerService.getQueryService().getTransactionCount(ledgerHash, blockHeight); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/txs/count") - @Override - public long getTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - return peerService.getQueryService().getTransactionCount(ledgerHash, blockHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/count") - @Override - public long getTransactionTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getTransactionTotalCount(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/accounts/count") - @Override - public long getDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - return peerService.getQueryService().getDataAccountCount(ledgerHash, blockHeight); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/accounts/count") - @Override - public long getDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - return peerService.getQueryService().getDataAccountCount(ledgerHash, blockHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/count") - @Override - public long getDataAccountTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getDataAccountTotalCount(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/users/count") - @Override - public long getUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - return peerService.getQueryService().getUserCount(ledgerHash, blockHeight); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/users/count") - @Override - public long getUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - return peerService.getQueryService().getUserCount(ledgerHash, blockHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/count") - @Override - public long getUserTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getUserTotalCount(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/contracts/count") - @Override - public long getContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - return peerService.getQueryService().getContractCount(ledgerHash, blockHeight); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/contracts/count") - @Override - public long getContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - return peerService.getQueryService().getContractCount(ledgerHash, blockHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/count") - @Override - public long getContractTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getContractTotalCount(ledgerHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs") - @Override - public LedgerTransaction[] getTransactions(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return peerService.getQueryService().getTransactions(ledgerHash, blockHeight, fromIndex, count); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/txs") - @Override - public LedgerTransaction[] getTransactions(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return peerService.getQueryService().getTransactions(ledgerHash, blockHash, fromIndex, count); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/hash/{contentHash}") - @Override - public LedgerTransaction getTransactionByContentHash(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "contentHash") HashDigest contentHash) { - return peerService.getQueryService().getTransactionByContentHash(ledgerHash, contentHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/state/{contentHash}") - @Override - public TransactionState getTransactionStateByContentHash(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "contentHash") HashDigest contentHash) { - return peerService.getQueryService().getTransactionStateByContentHash(ledgerHash, contentHash); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/address/{address}") - @Override - public UserInfo getUser(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "address") String address) { - return peerService.getQueryService().getUser(ledgerHash, address); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/address/{address}") - @Override - public AccountHeader getDataAccount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "address") String address) { - - return peerService.getQueryService().getDataAccount(ledgerHash, address); - } - - @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}, path = "ledgers/{ledgerHash}/accounts/{address}/entries") - @Override - public KVDataEntry[] getDataEntries(@PathVariable("ledgerHash") HashDigest ledgerHash, - @PathVariable("address") String address, - @RequestParam("keys") String... keys) { - return peerService.getQueryService().getDataEntries(ledgerHash, address, keys); - } - - @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}, path = "ledgers/{ledgerHash}/accounts/{address}/entries-version") - public KVDataEntry[] getDataEntries(@PathVariable("ledgerHash") HashDigest ledgerHash, - @PathVariable("address") String address, - @RequestBody KVInfoVO kvInfoVO) { - return peerService.getQueryService().getDataEntries(ledgerHash, address, kvInfoVO); - } - - @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}, path = "ledgers/{ledgerHash}/accounts/address/{address}/entries") - @Override - public KVDataEntry[] getDataEntries(@PathVariable("ledgerHash") HashDigest ledgerHash, - @PathVariable("address") String address, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return peerService.getQueryService().getDataEntries(ledgerHash, address, fromIndex, count); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/address/{address}/entries/count") - @Override - public long getDataEntriesTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "address") String address) { - return peerService.getQueryService().getDataEntriesTotalCount(ledgerHash, address); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/address/{address}") - public ContractSettings getContractSettings(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "address") String address) { - return gatewayQueryService.getContractSettings(ledgerHash, address); - } + @RequestMapping(method = RequestMethod.GET, path = "ledgers") + @Override + public HashDigest[] getLedgerHashs() { + return peerService.getQueryService().getLedgerHashs(); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}") + @Override + public LedgerInfo getLedger(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getLedger(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/admininfo") + @Override + public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { + return peerService.getQueryService().getLedgerAdminInfo(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants") + @Override + public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { + return peerService.getQueryService().getConsensusParticipants(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/metadata") + @Override + public LedgerMetadata getLedgerMetadata(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getLedgerMetadata(ledgerHash); + } + +// @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/settings") +// public LedgerInitSettings getLedgerInitSettings(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { +// return gatewayQueryService.getLedgerInitSettings(ledgerHash); +// } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks") + public LedgerBlock[] getBlocks(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); + long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); + List ledgerBlocks = new ArrayList<>(); + for (long blockHeight = maxBlockHeight; blockHeight > GENESIS_BLOCK_HEIGHT; blockHeight--) { + LedgerBlock ledgerBlock = peerService.getQueryService().getBlock(ledgerHash, blockHeight); + ledgerBlocks.add(0, ledgerBlock); + if (ledgerBlocks.size() == BLOCK_MAX_DISPLAY) { + break; + } + } + // 最后增加创世区块 + LedgerBlock genesisBlock = peerService.getQueryService().getBlock(ledgerHash, GENESIS_BLOCK_HEIGHT); + ledgerBlocks.add(0, genesisBlock); + LedgerBlock[] blocks = new LedgerBlock[ledgerBlocks.size()]; + ledgerBlocks.toArray(blocks); + return blocks; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}") + @Override + public LedgerBlock getBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + return peerService.getQueryService().getBlock(ledgerHash, blockHeight); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}") + @Override + public LedgerBlock getBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + return peerService.getQueryService().getBlock(ledgerHash, blockHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs/count") + @Override + public long getTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + return peerService.getQueryService().getTransactionCount(ledgerHash, blockHeight); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/txs/count") + @Override + public long getTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + return peerService.getQueryService().getTransactionCount(ledgerHash, blockHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/count") + @Override + public long getTransactionTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getTransactionTotalCount(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/accounts/count") + @Override + public long getDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + return peerService.getQueryService().getDataAccountCount(ledgerHash, blockHeight); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/accounts/count") + @Override + public long getDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + return peerService.getQueryService().getDataAccountCount(ledgerHash, blockHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/count") + @Override + public long getDataAccountTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getDataAccountTotalCount(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/users/count") + @Override + public long getUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + return peerService.getQueryService().getUserCount(ledgerHash, blockHeight); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/users/count") + @Override + public long getUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + return peerService.getQueryService().getUserCount(ledgerHash, blockHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/count") + @Override + public long getUserTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getUserTotalCount(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/contracts/count") + @Override + public long getContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + return peerService.getQueryService().getContractCount(ledgerHash, blockHeight); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/contracts/count") + @Override + public long getContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + return peerService.getQueryService().getContractCount(ledgerHash, blockHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/count") + @Override + public long getContractTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getContractTotalCount(ledgerHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs") + @Override + public LedgerTransaction[] getTransactions(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight, + @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, + @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { + return peerService.getQueryService().getTransactions(ledgerHash, blockHeight, fromIndex, count); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/txs") + @Override + public LedgerTransaction[] getTransactions(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash, + @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, + @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { + return peerService.getQueryService().getTransactions(ledgerHash, blockHash, fromIndex, count); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/hash/{contentHash}") + @Override + public LedgerTransaction getTransactionByContentHash(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "contentHash") HashDigest contentHash) { + return peerService.getQueryService().getTransactionByContentHash(ledgerHash, contentHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/state/{contentHash}") + @Override + public TransactionState getTransactionStateByContentHash(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "contentHash") HashDigest contentHash) { + return peerService.getQueryService().getTransactionStateByContentHash(ledgerHash, contentHash); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/address/{address}") + @Override + public UserInfo getUser(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "address") String address) { + return peerService.getQueryService().getUser(ledgerHash, address); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/address/{address}") + @Override + public AccountHeader getDataAccount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "address") String address) { + + return peerService.getQueryService().getDataAccount(ledgerHash, address); + } + + @RequestMapping(method = { RequestMethod.GET, + RequestMethod.POST }, path = "ledgers/{ledgerHash}/accounts/{address}/entries") + @Override + public KVDataEntry[] getDataEntries(@PathVariable("ledgerHash") HashDigest ledgerHash, + @PathVariable("address") String address, @RequestParam("keys") String... keys) { + return peerService.getQueryService().getDataEntries(ledgerHash, address, keys); + } + + @RequestMapping(method = { RequestMethod.GET, + RequestMethod.POST }, path = "ledgers/{ledgerHash}/accounts/{address}/entries-version") + public KVDataEntry[] getDataEntries(@PathVariable("ledgerHash") HashDigest ledgerHash, + @PathVariable("address") String address, @RequestBody KVInfoVO kvInfoVO) { + return peerService.getQueryService().getDataEntries(ledgerHash, address, kvInfoVO); + } + + @RequestMapping(method = { RequestMethod.GET, + RequestMethod.POST }, path = "ledgers/{ledgerHash}/accounts/address/{address}/entries") + @Override + public KVDataEntry[] getDataEntries(@PathVariable("ledgerHash") HashDigest ledgerHash, + @PathVariable("address") String address, + @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, + @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { + return peerService.getQueryService().getDataEntries(ledgerHash, address, fromIndex, count); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/address/{address}/entries/count") + @Override + public long getDataEntriesTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "address") String address) { + return peerService.getQueryService().getDataEntriesTotalCount(ledgerHash, address); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/address/{address}") + public ContractSettings getContractSettings(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "address") String address) { + ContractInfo contractInfo = peerService.getQueryService().getContract(ledgerHash, address); + return contractSettings(contractInfo); + } + + private ContractSettings contractSettings(ContractInfo contractInfo) { + ContractSettings contractSettings = new ContractSettings(contractInfo.getAddress(), contractInfo.getPubKey(), + contractInfo.getRootHash()); + byte[] chainCodeBytes = contractInfo.getChainCode(); + // 将反编译chainCode + String mainClassJava = DecompilerUtils.decompileMainClassFromBytes(chainCodeBytes); + contractSettings.setChainCode(mainClassJava); + return contractSettings; + } // @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/address/{address}") - @Override - public ContractInfo getContract(HashDigest ledgerHash, String address) { - return peerService.getQueryService().getContract(ledgerHash, address); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/latest") - @Override - public LedgerBlock getLatestBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - long latestBlockHeight = peerService.getQueryService().getLedger(ledgerHash).getLatestBlockHeight(); - return peerService.getQueryService().getBlock(ledgerHash, latestBlockHeight); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs/additional-count") - @Override - public long getAdditionalTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - // 获取某个区块的交易总数 - long currentBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, blockHeight); - if (blockHeight == GENESIS_BLOCK_HEIGHT) { - return currentBlockTxCount; - } - long lastBlockHeight = blockHeight - 1; - long lastBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, lastBlockHeight); - // 当前区块交易数减上个区块交易数 - return currentBlockTxCount - lastBlockTxCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/txs/additional-count") - @Override - public long getAdditionalTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); - long currentBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, blockHash); - if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { - return currentBlockTxCount; - } - HashDigest previousHash = currentBlock.getPreviousHash(); - long lastBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, previousHash); - // 当前区块交易数减上个区块交易数 - return currentBlockTxCount - lastBlockTxCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/additional-count") - @Override - public long getAdditionalTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); - long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); - long totalCount = peerService.getQueryService().getTransactionTotalCount(ledgerHash); - if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 - return totalCount; - } - long lastTotalCount = peerService.getQueryService().getTransactionCount(ledgerHash, maxBlockHeight - 1); - return totalCount - lastTotalCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/accounts/additional-count") - @Override - public long getAdditionalDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - long currentDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, blockHeight); - if (blockHeight == GENESIS_BLOCK_HEIGHT) { - return currentDaCount; - } - long lastBlockHeight = blockHeight - 1; - long lastDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, lastBlockHeight); - return currentDaCount - lastDaCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/accounts/additional-count") - @Override - public long getAdditionalDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); - long currentBlockDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, blockHash); - if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { - return currentBlockDaCount; - } - HashDigest previousHash = currentBlock.getPreviousHash(); - long lastBlockDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, previousHash); - // 当前区块数据账户数量减上个区块数据账户数量 - return currentBlockDaCount - lastBlockDaCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/additional-count") - @Override - public long getAdditionalDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); - long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); - long totalCount = peerService.getQueryService().getDataAccountTotalCount(ledgerHash); - if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 - return totalCount; - } - long lastTotalCount = peerService.getQueryService().getDataAccountCount(ledgerHash, maxBlockHeight - 1); - return totalCount - lastTotalCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/users/additional-count") - @Override - public long getAdditionalUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - long currentUserCount = peerService.getQueryService().getUserCount(ledgerHash, blockHeight); - if (blockHeight == GENESIS_BLOCK_HEIGHT) { - return currentUserCount; - } - long lastBlockHeight = blockHeight - 1; - long lastUserCount = peerService.getQueryService().getUserCount(ledgerHash, lastBlockHeight); - return currentUserCount - lastUserCount; - } - - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/users/additional-count") - @Override - public long getAdditionalUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); - long currentBlockUserCount = peerService.getQueryService().getUserCount(ledgerHash, blockHash); - if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { - return currentBlockUserCount; - } - HashDigest previousHash = currentBlock.getPreviousHash(); - long lastBlockUserCount = peerService.getQueryService().getUserCount(ledgerHash, previousHash); - // 当前区块用户数量减上个区块用户数量 - return currentBlockUserCount - lastBlockUserCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/additional-count") - @Override - public long getAdditionalUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); - long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); - long totalCount = peerService.getQueryService().getUserTotalCount(ledgerHash); - if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 - return totalCount; - } - long lastTotalCount = peerService.getQueryService().getUserCount(ledgerHash, maxBlockHeight - 1); - return totalCount - lastTotalCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/contracts/additional-count") - @Override - public long getAdditionalContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHeight") long blockHeight) { - long currentContractCount = peerService.getQueryService().getContractCount(ledgerHash, blockHeight); - if (blockHeight == GENESIS_BLOCK_HEIGHT) { - return currentContractCount; - } - long lastBlockHeight = blockHeight - 1; - long lastContractCount = peerService.getQueryService().getUserCount(ledgerHash, lastBlockHeight); - return currentContractCount - lastContractCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/contracts/additional-count") - @Override - public long getAdditionalContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); - long currentBlockContractCount = peerService.getQueryService().getContractCount(ledgerHash, blockHash); - if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { - return currentBlockContractCount; - } - HashDigest previousHash = currentBlock.getPreviousHash(); - long lastBlockContractCount = peerService.getQueryService().getUserCount(ledgerHash, previousHash); - // 当前区块合约数量减上个区块合约数量 - return currentBlockContractCount - lastBlockContractCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/additional-count") - @Override - public long getAdditionalContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); - long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); - long totalCount = peerService.getQueryService().getContractTotalCount(ledgerHash); - if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 - return totalCount; - } - long lastTotalCount = peerService.getQueryService().getContractCount(ledgerHash, maxBlockHeight - 1); - return totalCount - lastTotalCount; - } - - @RequestMapping(method = RequestMethod.GET, path = "utils/pubkey/{pubkey}/addr") - public String getAddrByPubKey(@PathVariable(name = "pubkey") String strPubKey) { - PubKey pubKey = KeyGenCommand.decodePubKey(strPubKey); - return AddressEncoding.generateAddress(pubKey).toBase58(); - } - - @RequestMapping(method = RequestMethod.GET, value = "ledgers/{ledgerHash}/**/search") - public Object dataRetrieval(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - HttpServletRequest request) { - String result; - if (dataRetrievalUrl == null || dataRetrievalUrl.length() <= 0) { - result = "{'message':'OK','data':'" + "data.retrieval.url is empty" + "'}"; - } else { - String queryParams = request.getQueryString() == null ? "": request.getQueryString(); - String fullQueryUrl = new StringBuffer(dataRetrievalUrl) - .append(request.getRequestURI()) - .append(BaseConstant.DELIMETER_QUESTION) - .append(queryParams) - .toString(); - try { - result = dataRetrievalService.retrieval(fullQueryUrl); - ConsoleUtils.info("request = {%s} \r\n result = {%s} \r\n", fullQueryUrl, result); - } catch (Exception e) { - result = "{'message':'OK','data':'" + e.getMessage() + "'}"; - } - } - return result; - } - - public void setDataRetrievalUrl(String dataRetrievalUrl) { - this.dataRetrievalUrl = dataRetrievalUrl; - } - - /** - * get all ledgers count; - */ - @RequestMapping(method = RequestMethod.GET, path = "ledgers/count") - @Override - public int getLedgersCount() { - return peerService.getQueryService().getLedgerHashs().length; - } - - /** - * get all ledgers hashs; - */ - @RequestMapping(method = RequestMethod.GET, path = "ledgers") - public HashDigest[] getLedgersHash(@RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return gatewayQueryService.getLedgersHash(fromIndex, count); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants/count") - public int getConsensusParticipantCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - return peerService.getQueryService().getConsensusParticipants(ledgerHash).length; - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants") - public ParticipantNode[] getConsensusParticipants(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return gatewayQueryService.getConsensusParticipants(ledgerHash,fromIndex,count); - } - - /** - * get more users by fromIndex and count; - * @param ledgerHash - * @param fromIndex - * @param count - * @return - */ - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users") - @Override - public AccountHeader[] getUsers(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return peerService.getQueryService().getUsers(ledgerHash, fromIndex, count); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts") - @Override - public AccountHeader[] getDataAccounts(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return peerService.getQueryService().getDataAccounts(ledgerHash, fromIndex, count); - } - - @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts") - @Override - public AccountHeader[] getContractAccounts(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, - @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, - @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - return peerService.getQueryService().getContractAccounts(ledgerHash, fromIndex, count); - } + @Override + public ContractInfo getContract(HashDigest ledgerHash, String address) { + return peerService.getQueryService().getContract(ledgerHash, address); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/latest") + @Override + public LedgerBlock getLatestBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + long latestBlockHeight = peerService.getQueryService().getLedger(ledgerHash).getLatestBlockHeight(); + return peerService.getQueryService().getBlock(ledgerHash, latestBlockHeight); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs/additional-count") + @Override + public long getAdditionalTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + // 获取某个区块的交易总数 + long currentBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, blockHeight); + if (blockHeight == GENESIS_BLOCK_HEIGHT) { + return currentBlockTxCount; + } + long lastBlockHeight = blockHeight - 1; + long lastBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, lastBlockHeight); + // 当前区块交易数减上个区块交易数 + return currentBlockTxCount - lastBlockTxCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/txs/additional-count") + @Override + public long getAdditionalTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); + long currentBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, blockHash); + if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { + return currentBlockTxCount; + } + HashDigest previousHash = currentBlock.getPreviousHash(); + long lastBlockTxCount = peerService.getQueryService().getTransactionCount(ledgerHash, previousHash); + // 当前区块交易数减上个区块交易数 + return currentBlockTxCount - lastBlockTxCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/additional-count") + @Override + public long getAdditionalTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); + long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); + long totalCount = peerService.getQueryService().getTransactionTotalCount(ledgerHash); + if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 + return totalCount; + } + long lastTotalCount = peerService.getQueryService().getTransactionCount(ledgerHash, maxBlockHeight - 1); + return totalCount - lastTotalCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/accounts/additional-count") + @Override + public long getAdditionalDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + long currentDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, blockHeight); + if (blockHeight == GENESIS_BLOCK_HEIGHT) { + return currentDaCount; + } + long lastBlockHeight = blockHeight - 1; + long lastDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, lastBlockHeight); + return currentDaCount - lastDaCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/accounts/additional-count") + @Override + public long getAdditionalDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); + long currentBlockDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, blockHash); + if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { + return currentBlockDaCount; + } + HashDigest previousHash = currentBlock.getPreviousHash(); + long lastBlockDaCount = peerService.getQueryService().getDataAccountCount(ledgerHash, previousHash); + // 当前区块数据账户数量减上个区块数据账户数量 + return currentBlockDaCount - lastBlockDaCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/additional-count") + @Override + public long getAdditionalDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); + long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); + long totalCount = peerService.getQueryService().getDataAccountTotalCount(ledgerHash); + if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 + return totalCount; + } + long lastTotalCount = peerService.getQueryService().getDataAccountCount(ledgerHash, maxBlockHeight - 1); + return totalCount - lastTotalCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/users/additional-count") + @Override + public long getAdditionalUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + long currentUserCount = peerService.getQueryService().getUserCount(ledgerHash, blockHeight); + if (blockHeight == GENESIS_BLOCK_HEIGHT) { + return currentUserCount; + } + long lastBlockHeight = blockHeight - 1; + long lastUserCount = peerService.getQueryService().getUserCount(ledgerHash, lastBlockHeight); + return currentUserCount - lastUserCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/users/additional-count") + @Override + public long getAdditionalUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); + long currentBlockUserCount = peerService.getQueryService().getUserCount(ledgerHash, blockHash); + if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { + return currentBlockUserCount; + } + HashDigest previousHash = currentBlock.getPreviousHash(); + long lastBlockUserCount = peerService.getQueryService().getUserCount(ledgerHash, previousHash); + // 当前区块用户数量减上个区块用户数量 + return currentBlockUserCount - lastBlockUserCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/additional-count") + @Override + public long getAdditionalUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); + long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); + long totalCount = peerService.getQueryService().getUserTotalCount(ledgerHash); + if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 + return totalCount; + } + long lastTotalCount = peerService.getQueryService().getUserCount(ledgerHash, maxBlockHeight - 1); + return totalCount - lastTotalCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/contracts/additional-count") + @Override + public long getAdditionalContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHeight") long blockHeight) { + long currentContractCount = peerService.getQueryService().getContractCount(ledgerHash, blockHeight); + if (blockHeight == GENESIS_BLOCK_HEIGHT) { + return currentContractCount; + } + long lastBlockHeight = blockHeight - 1; + long lastContractCount = peerService.getQueryService().getUserCount(ledgerHash, lastBlockHeight); + return currentContractCount - lastContractCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/contracts/additional-count") + @Override + public long getAdditionalContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @PathVariable(name = "blockHash") HashDigest blockHash) { + LedgerBlock currentBlock = peerService.getQueryService().getBlock(ledgerHash, blockHash); + long currentBlockContractCount = peerService.getQueryService().getContractCount(ledgerHash, blockHash); + if (currentBlock.getHeight() == GENESIS_BLOCK_HEIGHT) { + return currentBlockContractCount; + } + HashDigest previousHash = currentBlock.getPreviousHash(); + long lastBlockContractCount = peerService.getQueryService().getUserCount(ledgerHash, previousHash); + // 当前区块合约数量减上个区块合约数量 + return currentBlockContractCount - lastBlockContractCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/additional-count") + @Override + public long getAdditionalContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + LedgerInfo ledgerInfo = peerService.getQueryService().getLedger(ledgerHash); + long maxBlockHeight = ledgerInfo.getLatestBlockHeight(); + long totalCount = peerService.getQueryService().getContractTotalCount(ledgerHash); + if (maxBlockHeight == GENESIS_BLOCK_HEIGHT) { // 只有一个创世区块 + return totalCount; + } + long lastTotalCount = peerService.getQueryService().getContractCount(ledgerHash, maxBlockHeight - 1); + return totalCount - lastTotalCount; + } + + @RequestMapping(method = RequestMethod.GET, path = "utils/pubkey/{pubkey}/addr") + public String getAddrByPubKey(@PathVariable(name = "pubkey") String strPubKey) { + PubKey pubKey = KeyGenCommand.decodePubKey(strPubKey); + return AddressEncoding.generateAddress(pubKey).toBase58(); + } + + @RequestMapping(method = RequestMethod.GET, value = "ledgers/{ledgerHash}/**/search") + public Object dataRetrieval(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, HttpServletRequest request) { + String result; + if (dataRetrievalUrl == null || dataRetrievalUrl.length() <= 0) { + result = "{'message':'OK','data':'" + "data.retrieval.url is empty" + "'}"; + } else { + String queryParams = request.getQueryString() == null ? "" : request.getQueryString(); + String fullQueryUrl = new StringBuffer(dataRetrievalUrl).append(request.getRequestURI()) + .append(BaseConstant.DELIMETER_QUESTION).append(queryParams).toString(); + try { + result = dataRetrievalService.retrieval(fullQueryUrl); + ConsoleUtils.info("request = {%s} \r\n result = {%s} \r\n", fullQueryUrl, result); + } catch (Exception e) { + result = "{'message':'OK','data':'" + e.getMessage() + "'}"; + } + } + return result; + } + + public void setDataRetrievalUrl(String dataRetrievalUrl) { + this.dataRetrievalUrl = dataRetrievalUrl; + } + + /** + * get all ledgers count; + */ + @RequestMapping(method = RequestMethod.GET, path = "ledgers/count") + @Override + public int getLedgersCount() { + return peerService.getQueryService().getLedgerHashs().length; + } + + // 注: 账本的数量不会很多,不需要分页; +// /** +// * get all ledgers hashs; +// */ +// @RequestMapping(method = RequestMethod.GET, path = "ledgers") +// public HashDigest[] getLedgersHash( +// @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, +// @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { +// return gatewayQueryService.getLedgersHash(fromIndex, count); +// } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants/count") + public int getConsensusParticipantCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return peerService.getQueryService().getConsensusParticipants(ledgerHash).length; + } + +// @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants") +// public ParticipantNode[] getConsensusParticipants(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, +// @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, +// @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { +// +// ParticipantNode participantNode[] = peerService.getQueryService().getConsensusParticipants(ledgerHash); +// int indexAndCount[] = QueryUtil.calFromIndexAndCount(fromIndex, count, participantNode.length); +// ParticipantNode participantNodesNew[] = Arrays.copyOfRange(participantNode, indexAndCount[0], +// indexAndCount[0] + indexAndCount[1]); +// return participantNodesNew; +// } + + /** + * get more users by fromIndex and count; + * + * @param ledgerHash + * @param fromIndex + * @param count + * @return + */ + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users") + @Override + public AccountHeader[] getUsers(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, + @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { + return peerService.getQueryService().getUsers(ledgerHash, fromIndex, count); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts") + @Override + public AccountHeader[] getDataAccounts(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, + @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { + return peerService.getQueryService().getDataAccounts(ledgerHash, fromIndex, count); + } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts") + @Override + public AccountHeader[] getContractAccounts(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, + @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, + @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { + return peerService.getQueryService().getContractAccounts(ledgerHash, fromIndex, count); + } } diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java index 4481088f..5fa20ae0 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java @@ -35,6 +35,9 @@ public class GatewayWebServerConfigurer implements WebMvcConfigurer { JSONSerializeUtils.disableCircularReferenceDetect(); JSONSerializeUtils.configStringSerializer(ByteArray.class); DataContractRegistry.register(BftsmartNodeSettings.class); + + + DataContractRegistry.register(LedgerAdminInfo.class); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java index 89e1bc77..1e494766 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java @@ -10,6 +10,7 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.HashFunction; +import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.ParticipantNode; @@ -19,7 +20,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class LedgerAdminAccount implements Transactional, LedgerAdministration { +public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { static { DataContractRegistry.register(LedgerMetadata.class); @@ -174,8 +175,8 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { } Bytes key = encodeSettingsKey(settingsHash); byte[] bytes = storage.get(key); - HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); - if (!hashFunc.verify(adminAccountHash, bytes)) { + HashFunction hashFunc = Crypto.getHashFunction(settingsHash.getAlgorithm()); + if (!hashFunc.verify(settingsHash, bytes)) { String errorMsg = "Verification of the hash for ledger setting failed! --[HASH=" + key + "]"; LOGGER.error(errorMsg); throw new LedgerException(errorMsg); @@ -240,7 +241,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { * * @return */ - public LedgerSettings getSetting() { + public LedgerSettings getSettings() { return settings; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdministration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdministration.java deleted file mode 100644 index cc09138f..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdministration.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.ParticipantNode; - -public interface LedgerAdministration { - - LedgerMetadata getMetadata(); - - long getParticipantCount(); - -// ParticipantNode getParticipant(int id); - - ParticipantNode[] getParticipants(); - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java index faa23138..c548e531 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java @@ -3,6 +3,7 @@ package com.jd.blockchain.ledger.core; import java.io.Closeable; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerBlock; public interface LedgerRepository extends Closeable { @@ -51,7 +52,7 @@ public interface LedgerRepository extends Closeable { */ LedgerBlock getBlock(long height); - LedgerAdministration getAdminInfo(); + LedgerAdminInfo getAdminInfo(); LedgerBlock getBlock(HashDigest hash); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java index 19dfee44..77cb4e2f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java @@ -69,7 +69,7 @@ public class LedgerManager implements LedgerManage { ledgerVersioningStorage); // 校验 crypto service provider ; - CryptoSetting cryptoSetting = ledgerRepo.getAdminAccount().getSetting().getCryptoSetting(); + CryptoSetting cryptoSetting = ledgerRepo.getAdminAccount().getSettings().getCryptoSetting(); checkCryptoSetting(cryptoSetting, ledgerHash); // 创建账本上下文; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java index 5696ded7..2433b9d2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java @@ -9,7 +9,6 @@ import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DataAccountSet; -import com.jd.blockchain.ledger.core.LedgerAdministration; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.TransactionSet; @@ -40,15 +39,23 @@ public class LedgerQueryService implements BlockchainQueryService { ledgerInfo.setLatestBlockHeight(ledger.getLatestBlockHeight()); return ledgerInfo; } + + @Override + public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { + LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerBlock block = ledger.getLatestBlock(); + LedgerAdminInfo administration = ledger.getAdminAccount(block); + return administration; + } @Override public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { - return ledgerAdministration(ledgerHash).getParticipants(); + return getLedgerAdminInfo(ledgerHash).getParticipants(); } @Override public LedgerMetadata getLedgerMetadata(HashDigest ledgerHash) { - return ledgerAdministration(ledgerHash).getMetadata(); + return getLedgerAdminInfo(ledgerHash).getMetadata(); } @Override @@ -390,10 +397,4 @@ public class LedgerQueryService implements BlockchainQueryService { return contractAccountSet.getAccounts(pages[0], pages[1]); } - private LedgerAdministration ledgerAdministration(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); - LedgerBlock block = ledger.getLatestBlock(); - LedgerAdministration administration = ledger.getAdminAccount(block); - return administration; - } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java index 9505aaad..4dff3f06 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java @@ -9,7 +9,6 @@ import com.jd.blockchain.ledger.core.AccountAccessPolicy; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.DataAccountSet; import com.jd.blockchain.ledger.core.LedgerAdminAccount; -import com.jd.blockchain.ledger.core.LedgerAdministration; import com.jd.blockchain.ledger.core.LedgerConsts; import com.jd.blockchain.ledger.core.LedgerDataSet; import com.jd.blockchain.ledger.core.LedgerEditor; @@ -244,7 +243,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { } @Override - public LedgerAdministration getAdminInfo() { + public LedgerAdminInfo getAdminInfo() { return getAdminAccount(getLatestBlock()); } @@ -263,7 +262,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { if (transactionSet == null) { LedgerAdminAccount adminAccount = getAdminAccount(block); transactionSet = loadTransactionSet(block.getTransactionSetHash(), - adminAccount.getMetadata().getSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, + adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); state.transactionSet = transactionSet; } @@ -272,7 +271,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { LedgerAdminAccount adminAccount = getAdminAccount(block); // All of existing block is readonly; return loadTransactionSet(block.getTransactionSetHash(), - adminAccount.getMetadata().getSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, + adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); } @@ -402,7 +401,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { } LedgerBlock previousBlock = getLatestBlock(); LedgerTransactionalEditor editor = LedgerTransactionalEditor.createEditor(previousBlock, - getAdminInfo().getMetadata().getSetting(), keyPrefix, exPolicyStorage, + getAdminInfo().getSettings(), keyPrefix, exPolicyStorage, versioningStorage); NewBlockCommittingMonitor committingMonitor = new NewBlockCommittingMonitor(editor, this); this.nextBlockEditor = committingMonitor; @@ -453,7 +452,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { // PrefixAppender.prefix(USER_SET_PREFIX, ledgerExStorage), // PrefixAppender.prefix(USER_SET_PREFIX, ledgerVerStorage), // DEFAULT_ACCESS_POLICY); - UserAccountSet userAccountSet = new UserAccountSet(adminAccount.getSetting().getCryptoSetting(), + UserAccountSet userAccountSet = new UserAccountSet(adminAccount.getSettings().getCryptoSetting(), usersetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); // DataAccountSet dataAccountSet = new @@ -461,7 +460,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { // PrefixAppender.prefix(DATA_SET_PREFIX, ledgerExStorage), // PrefixAppender.prefix(DATA_SET_PREFIX, ledgerVerStorage), // DEFAULT_ACCESS_POLICY); - DataAccountSet dataAccountSet = new DataAccountSet(adminAccount.getSetting().getCryptoSetting(), + DataAccountSet dataAccountSet = new DataAccountSet(adminAccount.getSettings().getCryptoSetting(), datasetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); // ContractAccountSet contractAccountSet = new @@ -469,7 +468,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { // PrefixAppender.prefix(CONTRACT_SET_PREFIX, ledgerExStorage), // PrefixAppender.prefix(CONTRACT_SET_PREFIX, ledgerVerStorage), // DEFAULT_ACCESS_POLICY); - ContractAccountSet contractAccountSet = new ContractAccountSet(adminAccount.getSetting().getCryptoSetting(), + ContractAccountSet contractAccountSet = new ContractAccountSet(adminAccount.getSettings().getCryptoSetting(), contractsetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); LedgerDataSetImpl newDataSet = new LedgerDataSetImpl(adminAccount, userAccountSet, dataAccountSet, diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java index 1f63302f..c23d3d5f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java @@ -262,7 +262,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { GenesisSnapshot snpht = (GenesisSnapshot) startingPoint; txDataset = LedgerRepositoryImpl.newDataSet(snpht.initSetting, ledgerKeyPrefix, txBufferedStorage, txBufferedStorage); - txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminAccount().getSetting(), + txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminAccount().getSettings(), ledgerKeyPrefix, txBufferedStorage, txBufferedStorage); } else if (startingPoint instanceof TxSnapshot) { // 新的区块; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java index 5a673722..0aa0e3c9 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java @@ -38,6 +38,11 @@ public class ContractLedgerContext implements LedgerContext { public LedgerInfo getLedger(HashDigest ledgerHash) { return innerQueryService.getLedger(ledgerHash); } + + @Override + public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { + return innerQueryService.getLedgerAdminInfo(ledgerHash); + } @Override public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java index 1293a9d8..fe65af65 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java @@ -166,14 +166,15 @@ public class LedgerAdminAccountTest { assertTrue(BytesUtils.equals(expMeta.getSeed(), rlmeta.getSeed())); - assertNotNull(rlmeta.getSetting()); - assertTrue(expMeta.getSetting().getConsensusSetting().equals(rlmeta.getSetting().getConsensusSetting())); - assertEquals(expMeta.getSetting().getConsensusProvider(), rlmeta.getSetting().getConsensusProvider()); - - assertEquals(expMeta.getSetting().getCryptoSetting().getAutoVerifyHash(), - rlmeta.getSetting().getCryptoSetting().getAutoVerifyHash()); - assertEquals(expMeta.getSetting().getCryptoSetting().getHashAlgorithm(), - rlmeta.getSetting().getCryptoSetting().getHashAlgorithm()); + assertNotNull(rlmeta.getSettingsHash()); + assertEquals(expMeta.getSettingsHash(), rlmeta.getSettingsHash()); +// assertTrue(expMeta.getSettings().getConsensusSetting().equals(rlmeta.getSettings().getConsensusSetting())); +// assertEquals(expMeta.getSettings().getConsensusProvider(), rlmeta.getSettings().getConsensusProvider()); +// +// assertEquals(expMeta.getSettings().getCryptoSetting().getAutoVerifyHash(), +// rlmeta.getSettings().getCryptoSetting().getAutoVerifyHash()); +// assertEquals(expMeta.getSettings().getCryptoSetting().getHashAlgorithm(), +// rlmeta.getSettings().getCryptoSetting().getHashAlgorithm()); } private void verifyReadlingParities(LedgerAdminAccount actualAccount, ParticipantNode[] expParties) { @@ -210,7 +211,7 @@ public class LedgerAdminAccountTest { ex = null; try { - LedgerConfiguration newLedgerSetting = new LedgerConfiguration(actualAccount.getSetting()); + LedgerConfiguration newLedgerSetting = new LedgerConfiguration(actualAccount.getSettings()); actualAccount.setLedgerSetting(newLedgerSetting); } catch (Exception e) { ex = e; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java index 7fbef523..d6a16e5d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java @@ -16,6 +16,7 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PubKey; @@ -74,12 +75,14 @@ public class LedgerMetaDataTest { cryptoConfig.setAutoVerifyHash(true); cryptoConfig.setHashAlgorithm(ClassicAlgorithm.SHA256); - LedgerConfiguration ledgerConfiguration = new LedgerConfiguration(consensusProvider, - new Bytes(consensusSettingBytes), cryptoConfig); +// LedgerConfiguration ledgerConfiguration = new LedgerConfiguration(consensusProvider, +// new Bytes(consensusSettingBytes), cryptoConfig); + HashDigest settingsHash = Crypto.getHashFunction("SHA256").hash(consensusSettingBytes); + LedgerAdminAccount.LedgerMetadataImpl ledgerMetadata = new LedgerAdminAccount.LedgerMetadataImpl(); ledgerMetadata.setSeed(seed); - ledgerMetadata.setSetting(ledgerConfiguration); + ledgerMetadata.setSettingsHash(settingsHash); HashDigest hashDigest = new HashDigest(ClassicAlgorithm.SHA256, rawDigestBytes); ledgerMetadata.setParticipantsHash(hashDigest); @@ -91,7 +94,7 @@ public class LedgerMetaDataTest { // verify start assertArrayEquals(ledgerMetadata.getSeed(), deLedgerMetaData.getSeed()); assertEquals(ledgerMetadata.getParticipantsHash(), deLedgerMetaData.getParticipantsHash()); - assertNotEquals(ledgerMetadata.getSetting(), deLedgerMetaData.getSetting()); + assertEquals(ledgerMetadata.getSettingsHash(), deLedgerMetaData.getSettingsHash()); return; } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java new file mode 100644 index 00000000..93ef4234 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java @@ -0,0 +1,23 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +@DataContract(code = DataCodes.LEDGER_ADMIN_INFO, name = "LEDGER-ADMIN-INFO") +public interface LedgerAdminInfo { + + @DataField(order = 1, refContract = true) + LedgerMetadata getMetadata(); + + @DataField(order = 2, refContract = true) + LedgerSettings getSettings(); + + @DataField(order = 3, primitiveType = PrimitiveType.INT64) + long getParticipantCount(); + + @DataField(order = 4, refContract = true, list = true) + ParticipantNode[] getParticipants(); + +} \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java index 6ab04c89..f7108ff3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata.java @@ -31,14 +31,6 @@ public interface LedgerMetadata { @DataField(order = 2, primitiveType = PrimitiveType.BYTES) HashDigest getParticipantsHash(); -// /** -// * 账本配置; -// * -// * @return -// */ -// @DataField(order = 3, refContract = true) -// LedgerSetting getSetting(); - /** * 账本配置的哈希; * diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainQueryService.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainQueryService.java index a9f47141..9710b50d 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainQueryService.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainQueryService.java @@ -1,7 +1,21 @@ package com.jd.blockchain.transaction; +import org.springframework.cglib.core.Block; + import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.ContractInfo; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.KVInfoVO; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.Transaction; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.UserInfo; /** * 区块链查询器; @@ -28,12 +42,20 @@ public interface BlockchainQueryService { */ LedgerInfo getLedger(HashDigest ledgerHash); - /** - * 返回当前账本的参与者信息列表 - * - * @param ledgerHash - * @return - */ + /** + * 获取账本信息; + * + * @param ledgerHash + * @return 账本对象;如果不存在,则返回 null; + */ + LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash); + + /** + * 返回当前账本的参与者信息列表 + * + * @param ledgerHash + * @return + */ ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash); /** @@ -47,10 +69,8 @@ public interface BlockchainQueryService { /** * 返回指定账本序号的区块; * - * @param ledgerHash - * 账本hash; - * @param height - * 高度; + * @param ledgerHash 账本hash; + * @param height 高度; * @return */ LedgerBlock getBlock(HashDigest ledgerHash, long height); @@ -58,10 +78,8 @@ public interface BlockchainQueryService { /** * 返回指定区块hash的区块; * - * @param ledgerHash - * 账本hash; - * @param blockHash - * 区块hash; + * @param ledgerHash 账本hash; + * @param blockHash 区块hash; * @return */ LedgerBlock getBlock(HashDigest ledgerHash, HashDigest blockHash); @@ -84,125 +102,116 @@ public interface BlockchainQueryService { */ long getTransactionCount(HashDigest ledgerHash, HashDigest blockHash); - /** - * 返回当前账本的交易总数 - * - * @param ledgerHash - * @return - */ + /** + * 返回当前账本的交易总数 + * + * @param ledgerHash + * @return + */ long getTransactionTotalCount(HashDigest ledgerHash); - /** - * 返回指定高度的区块中记录的数据账户总数 - * - * @param ledgerHash - * @param height - * @return - */ + /** + * 返回指定高度的区块中记录的数据账户总数 + * + * @param ledgerHash + * @param height + * @return + */ long getDataAccountCount(HashDigest ledgerHash, long height); - /** - * 返回指定的区块中记录的数据账户总数 - * - * @param ledgerHash - * @param blockHash - * @return - */ + /** + * 返回指定的区块中记录的数据账户总数 + * + * @param ledgerHash + * @param blockHash + * @return + */ long getDataAccountCount(HashDigest ledgerHash, HashDigest blockHash); - /** - * 返回当前账本的数据账户总数 - * - * @param ledgerHash - * @return - */ - long getDataAccountTotalCount(HashDigest ledgerHash); - - /** - * 返回指定高度区块中的用户总数 - * - * @param ledgerHash - * @param height - * @return - */ + /** + * 返回当前账本的数据账户总数 + * + * @param ledgerHash + * @return + */ + long getDataAccountTotalCount(HashDigest ledgerHash); + + /** + * 返回指定高度区块中的用户总数 + * + * @param ledgerHash + * @param height + * @return + */ long getUserCount(HashDigest ledgerHash, long height); - /** - * 返回指定区块中的用户总数 - * - * @param ledgerHash - * @param blockHash - * @return - */ + /** + * 返回指定区块中的用户总数 + * + * @param ledgerHash + * @param blockHash + * @return + */ long getUserCount(HashDigest ledgerHash, HashDigest blockHash); - /** - * 返回当前账本的用户总数 - * - * @param ledgerHash - * @return - */ - long getUserTotalCount(HashDigest ledgerHash); - - /** - * 返回指定高度区块中的合约总数 - * - * @param ledgerHash - * @param height - * @return - */ - long getContractCount(HashDigest ledgerHash, long height); - - /** - * 返回指定区块中的合约总数 - * - * @param ledgerHash - * @param blockHash - * @return - */ - long getContractCount(HashDigest ledgerHash, HashDigest blockHash); - - /** - * 返回当前账本的合约总数 - * - * @param ledgerHash - * @return - */ - long getContractTotalCount(HashDigest ledgerHash); - + /** + * 返回当前账本的用户总数 + * + * @param ledgerHash + * @return + */ + long getUserTotalCount(HashDigest ledgerHash); /** - * 分页返回指定账本序号的区块中的交易列表; + * 返回指定高度区块中的合约总数 * * @param ledgerHash - * 账本hash; * @param height - * 账本高度; - * @param fromIndex - * 开始的记录数; - * @param count - * 本次返回的记录数;
        - * 最小为1,最大值受到系统参数的限制;
        - * 注:通过 {@link #getBlock(String, long)} 方法获得的区块信息中可以得到区块的总交易数 - * {@link Block#getTxCount()}; * @return */ - LedgerTransaction[] getTransactions(HashDigest ledgerHash, long height, int fromIndex, int count); + long getContractCount(HashDigest ledgerHash, long height); /** - * 分页返回指定账本序号的区块中的交易列表; + * 返回指定区块中的合约总数 * * @param ledgerHash - * 账本hash; * @param blockHash - * 账本高度; - * @param fromIndex - * 开始的记录数; - * @param count - * 本次返回的记录数;
        - * 如果参数值为 -1,则返回全部的记录;
        - * 注:通过 {@link #getBlock(String, String)} 方法获得的区块信息中可以得到区块的总交易数 - * {@link Block#getTxCount()}; + * @return + */ + long getContractCount(HashDigest ledgerHash, HashDigest blockHash); + + /** + * 返回当前账本的合约总数 + * + * @param ledgerHash + * @return + */ + long getContractTotalCount(HashDigest ledgerHash); + + /** + * 分页返回指定账本序号的区块中的交易列表; + * + * @param ledgerHash 账本hash; + * @param height 账本高度; + * @param fromIndex 开始的记录数; + * @param count 本次返回的记录数;
        + * 最小为1,最大值受到系统参数的限制;
        + * 注:通过 {@link #getBlock(String, long)} 方法获得的区块信息中可以得到区块的总交易数 + * {@link Block#getTxCount()}; + * @return + */ + LedgerTransaction[] getTransactions(HashDigest ledgerHash, long height, int fromIndex, int count); + + /** + * 分页返回指定账本序号的区块中的交易列表; + * + * @param ledgerHash 账本hash; + * @param blockHash 账本高度; + * @param fromIndex 开始的记录数; + * @param count 本次返回的记录数;
        + * 如果参数值为 -1,则返回全部的记录;
        + * 注:通过 {@link #getBlock(String, String)} + * 方法获得的区块信息中可以得到区块的总交易数 {@link Block#getTxCount()}; * @return */ LedgerTransaction[] getTransactions(HashDigest ledgerHash, HashDigest blockHash, int fromIndex, int count); @@ -210,21 +219,17 @@ public interface BlockchainQueryService { /** * 根据交易内容的哈希获取对应的交易记录; * - * @param ledgerHash - * 账本hash; - * @param contentHash - * 交易内容的hash,即交易的 {@link Transaction#getContentHash()} 属性的值; + * @param ledgerHash 账本hash; + * @param contentHash 交易内容的hash,即交易的 {@link Transaction#getContentHash()} 属性的值; * @return */ LedgerTransaction getTransactionByContentHash(HashDigest ledgerHash, HashDigest contentHash); - + /** * 根据交易内容的哈希获取对应的交易状态; * - * @param ledgerHash - * 账本hash; - * @param contentHash - * 交易内容的hash,即交易的 {@link Transaction#getContentHash()} 属性的值; + * @param ledgerHash 账本hash; + * @param contentHash 交易内容的hash,即交易的 {@link Transaction#getContentHash()} 属性的值; * @return */ TransactionState getTransactionStateByContentHash(HashDigest ledgerHash, HashDigest contentHash); @@ -273,18 +278,13 @@ public interface BlockchainQueryService { long getDataEntriesTotalCount(HashDigest ledgerHash, String address); /** - * 返回数据账户中指定序号的最新值; - * 返回结果的顺序与指定的序号的顺序是一致的;
        + * 返回数据账户中指定序号的最新值; 返回结果的顺序与指定的序号的顺序是一致的;
        * - * @param ledgerHash - * 账本hash; - * @param address - * 数据账户地址; - * @param fromIndex - * 开始的记录数; - * @param count - * 本次返回的记录数;
        - * 如果参数值为 -1,则返回全部的记录;
        + * @param ledgerHash 账本hash; + * @param address 数据账户地址; + * @param fromIndex 开始的记录数; + * @param count 本次返回的记录数;
        + * 如果参数值为 -1,则返回全部的记录;
        * @return */ KVDataEntry[] getDataEntries(HashDigest ledgerHash, String address, int fromIndex, int count); @@ -300,6 +300,7 @@ public interface BlockchainQueryService { /** * get users by ledgerHash and its range; + * * @param ledgerHash * @param fromIndex * @param count @@ -309,6 +310,7 @@ public interface BlockchainQueryService { /** * get data accounts by ledgerHash and its range; + * * @param ledgerHash * @param fromIndex * @param count @@ -318,6 +320,7 @@ public interface BlockchainQueryService { /** * get contract accounts by ledgerHash and its range; + * * @param ledgerHash * @param fromIndex * @param count diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java index 341136b0..87eaf11a 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java @@ -17,7 +17,6 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DataAccountSet; -import com.jd.blockchain.ledger.core.LedgerAdministration; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.ParticipantCertData; @@ -57,7 +56,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public ParticipantNode[] getConsensusParticipants(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); - LedgerAdministration ledgerAdministration = ledger.getAdminInfo(); + LedgerAdminInfo ledgerAdministration = ledger.getAdminInfo(); long participantCount = ledgerAdministration.getParticipantCount(); if (participantCount <= 0) { return null; @@ -73,12 +72,20 @@ public class LedgerQueryController implements BlockchainQueryService { } return null; } + + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/admininfo") + @Override + public LedgerAdminInfo getLedgerAdminInfo(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerAdminInfo ledgerAdministration = ledger.getAdminInfo(); + return ledgerAdministration; + } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/metadata") @Override public LedgerMetadata getLedgerMetadata(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); - LedgerAdministration ledgerAdministration = ledger.getAdminInfo(); + LedgerAdminInfo ledgerAdministration = ledger.getAdminInfo(); LedgerMetadata ledgerMetadata = ledgerAdministration.getMetadata(); return ledgerMetadata; } diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index ac95d361..a740f1ca 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -37,6 +37,7 @@ import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.NodeRequest; @@ -121,6 +122,8 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(BftsmartConsensusSettings.class); DataContractRegistry.register(BftsmartNodeSettings.class); + + DataContractRegistry.register(LedgerAdminInfo.class); } @@ -225,10 +228,10 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag // load provider; LedgerAdminAccount ledgerAdminAccount = ledgerRepository.getAdminAccount(); - String consensusProvider = ledgerAdminAccount.getSetting().getConsensusProvider(); + String consensusProvider = ledgerAdminAccount.getSettings().getConsensusProvider(); ConsensusProvider provider = ConsensusProviders.getProvider(consensusProvider); // find current node; - Bytes csSettingBytes = ledgerAdminAccount.getSetting().getConsensusSetting(); + Bytes csSettingBytes = ledgerAdminAccount.getSettings().getConsensusSetting(); ConsensusSettings csSettings = provider.getSettingsFactory().getConsensusSettingsEncoder() .decode(csSettingBytes.toBytes()); NodeSettings currentNode = null; @@ -247,7 +250,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag NodeServer server = provider.getServerFactory().setupServer(serverSettings, consensusMessageHandler, consensusStateManager); ledgerPeers.put(ledgerHash, server); - ledgerCryptoSettings.put(ledgerHash, ledgerAdminAccount.getSetting().getCryptoSetting()); + ledgerCryptoSettings.put(ledgerHash, ledgerAdminAccount.getSettings().getCryptoSetting()); return server; } diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java index c850b57a..ce6175d6 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java @@ -32,6 +32,11 @@ public abstract class BlockchainServiceProxy implements BlockchainService { public LedgerInfo getLedger(HashDigest ledgerHash) { return getQueryService(ledgerHash).getLedger(ledgerHash); } + + @Override + public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { + return getQueryService(ledgerHash).getLedgerAdminInfo(ledgerHash); + } @Override public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 82a79c61..776554cf 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -21,7 +21,35 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.ContractCodeDeployOperation; +import com.jd.blockchain.ledger.ContractEventSendOperation; +import com.jd.blockchain.ledger.ContractInfo; +import com.jd.blockchain.ledger.DataAccountKVSetOperation; +import com.jd.blockchain.ledger.DataAccountRegisterOperation; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.KVInfoVO; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.OperationResult; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionRequestBuilder; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.UserInfo; +import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerDataSet; import com.jd.blockchain.ledger.core.LedgerEditor; @@ -93,6 +121,8 @@ public class MockerNodeContext implements BlockchainQueryService { DataContractRegistry.register(ActionResponse.class); DataContractRegistry.register(ClientIdentifications.class); DataContractRegistry.register(ClientIdentification.class); + + DataContractRegistry.register(LedgerAdminInfo.class); ByteArrayObjectUtil.init(); } @@ -243,6 +273,11 @@ public class MockerNodeContext implements BlockchainQueryService { public LedgerInfo getLedger(HashDigest ledgerHash) { return queryService.getLedger(ledgerHash); } + + @Override + public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { + return queryService.getLedgerAdminInfo(ledgerHash); + } @Override public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { From c4ac70efa88fd3e485e10b9b8e6da0787bc96396 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Sat, 10 Aug 2019 22:58:08 +0700 Subject: [PATCH 037/124] Fixed the bug that : the global standard time zone was not set and time parsing error occurred while running on a different time zone setting server. --- .../com/jd/blockchain/consts/DataCodes.java | 1 + .../java/com/jd/blockchain/consts/Global.java | 19 +++++++++++++++++++ .../jd/blockchain/peer/PeerServerBooter.java | 6 ++++++ .../initializer/LedgerInitProperties.java | 7 +++---- .../initializer/LedgerInitPropertiesTest.java | 15 +++++++++++++-- .../src/test/resources/ledger.init | 2 +- 6 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 source/base/src/main/java/com/jd/blockchain/consts/Global.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index e2f8d65b..4085f8cb 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -152,4 +152,5 @@ public interface DataCodes { public static final int CONSENSUS_MSGQUEUE_BLOCK_SETTINGS = CONSENSUS_MSGQUEUE | 0x05; + } diff --git a/source/base/src/main/java/com/jd/blockchain/consts/Global.java b/source/base/src/main/java/com/jd/blockchain/consts/Global.java new file mode 100644 index 00000000..b508e833 --- /dev/null +++ b/source/base/src/main/java/com/jd/blockchain/consts/Global.java @@ -0,0 +1,19 @@ +package com.jd.blockchain.consts; + +import java.util.TimeZone; + +public class Global { + + public static final String DEFAULT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSZ"; + + public static final String DEFAULT_TIME_ZONE = "GMT+08:00"; + + static { + initialize(); + } + + public static void initialize() { + TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_TIME_ZONE)); + } + +} diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java b/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java index aba98521..056753fe 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/PeerServerBooter.java @@ -1,5 +1,6 @@ package com.jd.blockchain.peer; +import com.jd.blockchain.consts.Global; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.utils.ArgumentSet; @@ -41,6 +42,11 @@ public class PeerServerBooter { private static final String DEBUG_OPT = "-debug"; public static String ledgerBindConfigFile; + + static { + // 加载 Global ,初始化全局设置; + Global.initialize(); + } public static void main(String[] args) { PeerServerBooter peerServerBooter = new PeerServerBooter(); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index 9030eb77..7b48cec6 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -9,8 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; -import org.springframework.util.ResourceUtils; - +import com.jd.blockchain.consts.Global; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; @@ -31,7 +30,7 @@ public class LedgerInitProperties { // 声明的账本建立时间; public static final String CREATED_TIME = "created-time"; // 创建时间的格式; - public static final String CREATED_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSZ"; + public static final String CREATED_TIME_FORMAT = Global.DEFAULT_TIME_FORMAT; // 共识参与方的个数,后续以 part.id 分别标识每一个参与方的配置; public static final String PART_COUNT = "cons_parti.count"; @@ -162,7 +161,7 @@ public class LedgerInitProperties { public static LedgerInitProperties resolve(Properties props) { return resolve(null, props); } - + public static LedgerInitProperties resolve(String dir, Properties props) { String hexLedgerSeed = PropertiesUtils.getRequiredProperty(props, LEDGER_SEED).replace("-", ""); byte[] ledgerSeed = HexUtils.decode(hexLedgerSeed); diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java index 703b86b4..3e0a004e 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java @@ -1,17 +1,18 @@ package test.com.jd.blockchain.tools.initializer; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.TimeZone; -import com.jd.blockchain.crypto.AddressEncoding; import org.junit.Test; import org.springframework.core.io.ClassPathResource; +import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; @@ -22,12 +23,21 @@ public class LedgerInitPropertiesTest { private static String expectedCreatedTimeStr = "2019-08-01 14:26:58.069+0800"; + private static String expectedCreatedTimeStr1 = "2019-08-01 13:26:58.069+0700"; + @Test public void testTimeFormat() throws ParseException { SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); +// timeFormat.setTimeZone(TimeZone.getTimeZone("GMT+08:00")); + TimeZone.setDefault(TimeZone.getTimeZone("GMT+08:00")); + Date time = timeFormat.parse(expectedCreatedTimeStr); String actualTimeStr = timeFormat.format(time); assertEquals(expectedCreatedTimeStr, actualTimeStr); + + Date time1 = timeFormat.parse(expectedCreatedTimeStr1); + String actualTimeStr1 = timeFormat.format(time1); + assertEquals(expectedCreatedTimeStr, actualTimeStr1); } @Test @@ -43,6 +53,7 @@ public class LedgerInitPropertiesTest { assertEquals(expectedLedgerSeed, actualLedgerSeed); SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); + timeFormat.setTimeZone(TimeZone.getTimeZone("GMT+08:00")); long expectedTs = timeFormat.parse(expectedCreatedTimeStr).getTime(); assertEquals(expectedTs, initProps.getCreatedTime()); diff --git a/source/tools/tools-initializer/src/test/resources/ledger.init b/source/tools/tools-initializer/src/test/resources/ledger.init index ad2b5f1f..2d574f93 100644 --- a/source/tools/tools-initializer/src/test/resources/ledger.init +++ b/source/tools/tools-initializer/src/test/resources/ledger.init @@ -3,7 +3,7 @@ ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe #账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; -ledger.name= +ledger.name=test #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 From ce1e79b88da58e70585696d15c385d1170c2cbd1 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Mon, 12 Aug 2019 11:05:16 +0800 Subject: [PATCH 038/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/scripts/jump-start.sh | 2 +- source/manager/ump-booter/pom.xml | 5 +++++ .../ump/controller/UmpKeyController.java | 16 ++++++++++++++++ .../ump/controller/UmpMasterController.java | 8 ++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh b/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh index a31a5b28..6c1f44af 100644 --- a/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh +++ b/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh @@ -5,5 +5,5 @@ UMP=$(ls $HOME/ext | grep ump-booter-) if [ ! -n "UMP" ]; then echo "Unified Management Platform Is Null !!!" else - nohup java -jar -server -Djump.log=$HOME $HOME/ext/$UMP $* >$HOME/bin/jump.out 2>&1 & + nohup java -jar -server -Djump.log=$HOME $HOME/ext/$UMP -p 8000 $* >$HOME/bin/jump.out 2>&1 & fi \ No newline at end of file diff --git a/source/manager/ump-booter/pom.xml b/source/manager/ump-booter/pom.xml index 69ae2ff4..392cb3b9 100644 --- a/source/manager/ump-booter/pom.xml +++ b/source/manager/ump-booter/pom.xml @@ -50,6 +50,11 @@ ${project.version}
        + + com.jd.blockchain + ump-explorer + + org.springframework.boot spring-boot-starter-log4j2 diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java index 3b274530..a7d2aed3 100644 --- a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpKeyController.java @@ -20,6 +20,13 @@ public class UmpKeyController { @Autowired private UmpStateService umpStateService; + + /** + * 创建用户 + * + * @param builder + * @return + */ @RequestMapping(method = RequestMethod.POST, path = "create") public UserKeysVv create(@RequestBody final UserKeyBuilder builder) { @@ -54,6 +61,15 @@ public class UmpKeyController { throw new IllegalStateException(String.format("Can not find UserKeys by %s", pubKey)); } + /** + * 解析UserKeys + * + * @param userId + * 用户ID + * @param pwd + * 密码(非编码后密码) + * @return + */ @RequestMapping(method = RequestMethod.GET, path = "resolve/{user}/{pwd}") public UserKeys resolve(@PathVariable(name = "user") int userId, @PathVariable(name = "pwd") String pwd) { diff --git a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java index 753ab3c0..7caa0584 100644 --- a/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java +++ b/source/manager/ump-web/src/main/java/com/jd/blockchain/ump/controller/UmpMasterController.java @@ -41,6 +41,14 @@ public class UmpMasterController { return umpService.response(sharedConfigs, sharedConfig); } + /** + * 接收其他Peer节点发送的安装信息 + * + * @param installSchedule + * 安装信息 + * + * @return + */ @RequestMapping(method = RequestMethod.POST, path = "receive") public String receive(@RequestBody final InstallSchedule installSchedule) { From ffd5b71e4764baa1bcfc5800f75297f675b48783 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Mon, 12 Aug 2019 14:02:17 +0800 Subject: [PATCH 039/124] =?UTF-8?q?=E5=B1=8F=E8=94=BDump=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/deployment/deployment-peer/pom.xml | 4 ++-- .../deployment-peer/src/main/resources/assembly.xml | 4 ++-- source/manager/pom.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/deployment/deployment-peer/pom.xml b/source/deployment/deployment-peer/pom.xml index 941178c7..7d94202b 100644 --- a/source/deployment/deployment-peer/pom.xml +++ b/source/deployment/deployment-peer/pom.xml @@ -25,11 +25,11 @@ runtime-modular-booter ${project.version} - + com.jd.blockchain storage-composite diff --git a/source/deployment/deployment-peer/src/main/resources/assembly.xml b/source/deployment/deployment-peer/src/main/resources/assembly.xml index 9ad916a2..0fc4ddf5 100644 --- a/source/deployment/deployment-peer/src/main/resources/assembly.xml +++ b/source/deployment/deployment-peer/src/main/resources/assembly.xml @@ -56,14 +56,14 @@ - + diff --git a/source/manager/pom.xml b/source/manager/pom.xml index a56f3423..9972ceaf 100644 --- a/source/manager/pom.xml +++ b/source/manager/pom.xml @@ -2,12 +2,12 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - + com.jd.blockchain jdchain-root From 963debbc9637ac704994e1cf8ab98b4f8bfcfad8 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Mon, 12 Aug 2019 15:27:05 +0800 Subject: [PATCH 040/124] solve miss property problem --- .../src/test/resources/ledger-binding-mem-0.conf | 4 ++++ .../src/test/resources/ledger-binding-mem-1.conf | 4 ++++ .../src/test/resources/ledger-binding-mem-2.conf | 4 ++++ .../src/test/resources/ledger-binding-mem-3.conf | 4 ++++ .../src/test/resources/ledger-binding-redis-0.conf | 4 ++++ .../src/test/resources/ledger-binding-redis-1.conf | 4 ++++ .../src/test/resources/ledger-binding-redis-2.conf | 4 ++++ .../src/test/resources/ledger-binding-redis-3.conf | 4 ++++ .../src/test/resources/ledger-binding-rocksdb-0.conf | 4 ++++ .../src/test/resources/ledger-binding-rocksdb-1.conf | 4 ++++ .../src/test/resources/ledger-binding-rocksdb-2.conf | 4 ++++ .../src/test/resources/ledger-binding-rocksdb-3.conf | 4 ++++ .../src/test/resources/ledger_init_test_web2.init | 2 +- 13 files changed, 49 insertions(+), 1 deletion(-) diff --git a/source/test/test-integration/src/test/resources/ledger-binding-mem-0.conf b/source/test/test-integration/src/test/resources/ledger-binding-mem-0.conf index 1976cb9e..0c79d2ba 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-mem-0.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-mem-0.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=0 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=a.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-mem-1.conf b/source/test/test-integration/src/test/resources/ledger-binding-mem-1.conf index de4e8387..8c0b420f 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-mem-1.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-mem-1.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=1 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=b.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-mem-2.conf b/source/test/test-integration/src/test/resources/ledger-binding-mem-2.conf index 09d8c70a..8605cdb8 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-mem-2.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-mem-2.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=2 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=c.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-mem-3.conf b/source/test/test-integration/src/test/resources/ledger-binding-mem-3.conf index de8e48b3..bfc77145 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-mem-3.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-mem-3.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=3 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=d.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-redis-0.conf b/source/test/test-integration/src/test/resources/ledger-binding-redis-0.conf index 81ebfdfa..958d8846 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-redis-0.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-redis-0.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=0 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=a.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-redis-1.conf b/source/test/test-integration/src/test/resources/ledger-binding-redis-1.conf index 04a378c9..c311a690 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-redis-1.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-redis-1.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=1 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=b.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-redis-2.conf b/source/test/test-integration/src/test/resources/ledger-binding-redis-2.conf index 5054b7bc..d5a8ce58 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-redis-2.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-redis-2.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=2 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=c.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-redis-3.conf b/source/test/test-integration/src/test/resources/ledger-binding-redis-3.conf index 7b7bb525..68ef1bd3 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-redis-3.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-redis-3.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=3 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=d.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-0.conf b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-0.conf index a47d1ddd..73ac4731 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-0.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-0.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=0 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=a.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-1.conf b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-1.conf index ccd3679f..9af1f941 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-1.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-1.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=1 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=b.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-2.conf b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-2.conf index 0d440618..087955da 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-2.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-2.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=2 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=c.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-3.conf b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-3.conf index 6fd34834..2e27c865 100644 --- a/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-3.conf +++ b/source/test/test-integration/src/test/resources/ledger-binding-rocksdb-3.conf @@ -3,8 +3,12 @@ ledger.bindings=6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ #第 1 个账本[6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ]的配置; +#账本的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.name=myledger #账本的当前共识参与方的ID; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.id=3 +#账本的当前共识参与方的名字; +binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.name=d.com #账本的当前共识参与方的私钥文件的保存路径; binding.6BCg5vgU57ykY6g2CpyUnt5ZMgdxfD1b3qXxQrRyfiXTQ.parti.pk-path= #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; diff --git a/source/test/test-integration/src/test/resources/ledger_init_test_web2.init b/source/test/test-integration/src/test/resources/ledger_init_test_web2.init index 90dbedc1..5be0e69d 100644 --- a/source/test/test-integration/src/test/resources/ledger_init_test_web2.init +++ b/source/test/test-integration/src/test/resources/ledger_init_test_web2.init @@ -2,7 +2,7 @@ ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe #账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; -#ledger.name= +ledger.name==myledger #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 From 89bd2d87af62a88db5a1898a9e8688fcb11118a6 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 16 Aug 2019 14:41:31 +0800 Subject: [PATCH 041/124] Completed dataset definitions about roles and user-roles assignment; --- .../com/jd/blockchain/consts/DataCodes.java | 10 +- .../ledger/core/AbstractPrivilege.java | 57 +++--- .../ledger/core/LedgerPermission.java | 2 +- .../ledger/core/LedgerPrivilege.java | 3 + .../ledger/core/LedgerSecurityManager.java | 4 +- .../blockchain/ledger/core/MerkleDataSet.java | 21 +++ .../com/jd/blockchain/ledger/core/Role.java | 27 --- .../blockchain/ledger/core/RoleDataSet.java | 175 ++++++++++++++++++ .../blockchain/ledger/core/RolePrivilege.java | 65 +------ .../core/RolePrivilegeAuthorization.java | 66 +++++++ .../jd/blockchain/ledger/core/RoleSet.java | 23 +++ .../blockchain/ledger/core/RolesPolicy.java | 40 ++++ ...ission.java => TransactionPermission.java} | 10 +- .../ledger/core/TransactionPrivilege.java | 17 ++ .../blockchain/ledger/core/TxPrivilege.java | 14 -- .../ledger/core/UserRoleDataSet.java | 155 ++++++++++++++++ .../ledger/core/UserRolesAuthorization.java | 84 +++++++++ 17 files changed, 636 insertions(+), 137 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{TxPermission.java => TransactionPermission.java} (73%) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 7a49e2ba..9e76d988 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -56,8 +56,14 @@ public interface DataCodes { public static final int TX_OP_RESULT = 0x360; // enum types of permissions; - public static final int ENUM_TX_PERMISSIONS = 0x401; - public static final int ENUM_LEDGER_PERMISSIONS = 0x402; + public static final int ENUM_TX_PERMISSION = 0x401; + public static final int ENUM_LEDGER_PERMISSION = 0x402; + public static final int ENUM_MULTI_ROLES_POLICY = 0x403; + + public static final int ROLE_PRIVILEGE = 0x410; + + public static final int ROLE_SET = 0x411; + // contract types of metadata; public static final int METADATA = 0x600; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java index f4925a3d..01ba5afd 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java @@ -12,52 +12,47 @@ import com.jd.blockchain.utils.io.BytesSerializable; */ public abstract class AbstractPrivilege> implements Privilege, BytesSerializable { - private BitSet permissions; + private BitSet permissionBits; + + public AbstractPrivilege() { + permissionBits = new BitSet(); + } public AbstractPrivilege(byte[] codeBytes) { - permissions = BitSet.valueOf(codeBytes); + permissionBits = BitSet.valueOf(codeBytes); } public boolean isEnable(E permission) { - return permissions.get(getCodeIndex(permission)); + return permissionBits.get(getCodeIndex(permission)); } public void enable(E permission) { - permissions.set(getCodeIndex(permission)); + permissionBits.set(getCodeIndex(permission)); } public void disable(E permission) { - permissions.clear(getCodeIndex(permission)); + permissionBits.clear(getCodeIndex(permission)); + } + + @SuppressWarnings("unchecked") + public void enable(E... permissions) { + for (E p : permissions) { + permissionBits.set(getCodeIndex(p)); + } + } + + @SuppressWarnings("unchecked") + public void disable(E... permissions) { + for (E p : permissions) { + permissionBits.clear(getCodeIndex(p)); + } } - -// private int getCodeIndex(E permission) { -// return permission.CODE & 0xFF; -// } protected abstract int getCodeIndex(E permission); @Override public byte[] toBytes() { - return permissions.toByteArray(); - } - -// public boolean[] getPermissionStates() { -// LedgerPermission[] PMs = LedgerPermission.values(); -// -// LedgerPermission maxPermission = Arrays.stream(PMs).max(new Comparator() { -// @Override -// public int compare(LedgerPermission o1, LedgerPermission o2) { -// return getCodeIndex(o1) - getCodeIndex(o2); -// } -// }).get(); -// -// boolean[] states = new boolean[getCodeIndex(maxPermission) + 1]; -// int idx = -1; -// for (LedgerPermission pm : PMs) { -// idx = getCodeIndex(pm); -// states[idx] = permissions.get(idx); -// } -// -// return states; -// } + return permissionBits.toByteArray(); + } + } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java index e5b6b751..2a5cec59 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java @@ -11,7 +11,7 @@ import com.jd.blockchain.consts.DataCodes; * @author huanghaiquan * */ -@EnumContract(code = DataCodes.ENUM_LEDGER_PERMISSIONS) +@EnumContract(code = DataCodes.ENUM_LEDGER_PERMISSION) public enum LedgerPermission { /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java index f4efbecb..73cdf9ef 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java @@ -8,6 +8,9 @@ package com.jd.blockchain.ledger.core; */ public class LedgerPrivilege extends AbstractPrivilege { + public LedgerPrivilege() { + } + public LedgerPrivilege(byte[] codeBytes) { super(codeBytes); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java index a44dc575..fc8151f3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -22,11 +22,11 @@ public class LedgerSecurityManager { throw new IllegalStateException("Not implemented!"); } - public Role setRole(String role, LedgerPrivilege privilege) { + public RolePrivilegeAuthorization setRole(String role, LedgerPrivilege privilege) { throw new IllegalStateException("Not implemented!"); } - public Role getRole(String role) { + public RolePrivilegeAuthorization getRole(String role) { throw new IllegalStateException("Not implemented!"); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java index b8dd170b..d17d219f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java @@ -149,6 +149,22 @@ public class MerkleDataSet implements Transactional, MerkleProvable { } return values; } + + public VersioningKVEntry[] getLatestDataEntries(int fromIndex, int count) { + if (count > LedgerConsts.MAX_LIST_COUNT) { + throw new IllegalArgumentException("Count exceed the upper limit[" + LedgerConsts.MAX_LIST_COUNT + "]!"); + } + if (fromIndex < 0 || (fromIndex + count) > merkleTree.getDataCount()) { + throw new IllegalArgumentException("Index out of bound!"); + } + VersioningKVEntry[] values = new VersioningKVEntry[count]; + for (int i = 0; i < count; i++) { + MerkleDataNode dataNode = merkleTree.getData(fromIndex + i); + Bytes dataKey = encodeDataKey(dataNode.getKey()); + values[i] = valueStorage.getEntry(dataKey, dataNode.getVersion()); + } + return values; + } /** * get the data at the specific index; @@ -404,6 +420,11 @@ public class MerkleDataSet implements Transactional, MerkleProvable { return getDataEntry(Bytes.fromString(key)); } + /** + * + * @param key + * @return Null if the key doesn't exist! + */ public VersioningKVEntry getDataEntry(Bytes key) { long latestVersion = getMerkleVersion(key); if (latestVersion < 0) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java deleted file mode 100644 index 7ece80cf..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Role.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.jd.blockchain.ledger.core; - -public class Role { - - private String name; - - private long version; - - private LedgerPrivilege privilege; - - - - public String getName() { - return name; - } - - public long getVersion() { - return version; - } - - public LedgerPrivilege getPrivilege() { - return privilege; - } - - - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java new file mode 100644 index 00000000..6fb3ee65 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java @@ -0,0 +1,175 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.storage.service.ExPolicyKVStorage; +import com.jd.blockchain.storage.service.VersioningKVEntry; +import com.jd.blockchain.storage.service.VersioningKVStorage; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.Transactional; + +public class RoleDataSet implements Transactional, MerkleProvable { + + /** + * 角色名称的最大 Unicode 字符数; + */ + public static final int MAX_ROLE_NAME_LENGTH = 20; + + private MerkleDataSet dataset; + + public RoleDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, + VersioningKVStorage verStorage) { + dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); + } + + public RoleDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, + ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { + dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); + } + + @Override + public HashDigest getRootHash() { + return dataset.getRootHash(); + } + + @Override + public MerkleProof getProof(Bytes key) { + return dataset.getProof(key); + } + + @Override + public boolean isUpdated() { + return dataset.isUpdated(); + } + + @Override + public void commit() { + dataset.commit(); + } + + @Override + public void cancel() { + dataset.cancel(); + } + + public long getRoleCount() { + return dataset.getDataCount(); + } + + /** + * 加入新的角色授权;
        + * + * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; + * + * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode 字符; + * @param ledgerPrivilege + * @param txPrivilege + */ + public void addRoleAuthorization(String roleName, LedgerPrivilege ledgerPrivilege, + TransactionPrivilege txPrivilege) { + RolePrivilegeAuthorization roleAuth = new RolePrivilegeAuthorization(roleName, -1, ledgerPrivilege, txPrivilege); + long nv = innerSetRoleAuthorization(roleAuth); + if (nv < 0) { + throw new LedgerException("Role[" + roleName + "] already exist!"); + } + } + + /** + * 设置角色授权;
        + * 如果版本校验不匹配,则返回 -1; + * + * @param roleAuth + * @return + */ + public long innerSetRoleAuthorization(RolePrivilegeAuthorization roleAuth) { + if (roleAuth.getRoleName().length() > MAX_ROLE_NAME_LENGTH) { + throw new LedgerException("Too long role name!"); + } + Bytes key = encodeKey(roleAuth.getRoleName()); + byte[] privilegeBytes = BinaryProtocol.encode(roleAuth, RolePrivilege.class); + return dataset.setValue(key, privilegeBytes, roleAuth.getVersion()); + } + + /** + * 更新角色授权;
        + * 如果指定的角色不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; + * + * @param participant + */ + public void updateRoleAuthorization(RolePrivilegeAuthorization roleAuth) { + long nv = innerSetRoleAuthorization(roleAuth); + if (nv < 0) { + throw new LedgerException("Update to RoleAuthorization[" + roleAuth.getRoleName() + + "] failed due to wrong version[" + roleAuth.getVersion() + "] !"); + } + } + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param participant + */ + public long authorizePermissions(String roleName, LedgerPermission... permissions) { + RolePrivilegeAuthorization roleAuth = getRoleAuthorization(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getLedgerPrivilege().enable(permissions); + return innerSetRoleAuthorization(roleAuth); + } + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param participant + */ + public long authorizePermissions(String roleName, TransactionPermission... permissions) { + RolePrivilegeAuthorization roleAuth = getRoleAuthorization(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getTransactionPrivilege().enable(permissions); + return innerSetRoleAuthorization(roleAuth); + } + + private Bytes encodeKey(String address) { + // return id + ""; + return Bytes.fromString(address); + } + + /** + * 查询角色授权; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + public RolePrivilegeAuthorization getRoleAuthorization(String roleName) { + // 只返回最新版本; + Bytes key = encodeKey(roleName); + VersioningKVEntry kv = dataset.getDataEntry(key); + if (kv == null) { + return null; + } + RolePrivilege privilege = BinaryProtocol.decode(kv.getValue()); + return new RolePrivilegeAuthorization(roleName, kv.getVersion(), privilege); + } + + public RolePrivilegeAuthorization[] getRoleAuthorizations() { + VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(0, (int) dataset.getDataCount()); + RolePrivilegeAuthorization[] pns = new RolePrivilegeAuthorization[kvEntries.length]; + RolePrivilege privilege; + for (int i = 0; i < pns.length; i++) { + privilege = BinaryProtocol.decode(kvEntries[i].getValue()); + pns[i] = new RolePrivilegeAuthorization(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), privilege); + } + return pns; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java index 19949af5..e40649b2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java @@ -1,10 +1,9 @@ package com.jd.blockchain.ledger.core; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - -import com.jd.blockchain.utils.io.BytesEncoding; -import com.jd.blockchain.utils.io.BytesSerializable; +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; /** * 表示赋予角色的特权码; @@ -12,57 +11,13 @@ import com.jd.blockchain.utils.io.BytesSerializable; * @author huanghaiquan * */ -public class RolePrivilege implements BytesSerializable { - - // 权限码的数量;目前有2种:账本权限 + 交易权限; - private static final int SEGMENT_COUNT = 2; - - private LedgerPrivilege ledgerPrivilege; - - private TxPrivilege txPrivilege; - - public Privilege getTxPrivilege() { - return txPrivilege; - } - - public Privilege getLedgerPrivilege() { - return ledgerPrivilege; - } - - public RolePrivilege(byte[] priviledgeCodes) { - byte[][] bytesSegments = decodeBytes(priviledgeCodes); - ledgerPrivilege = new LedgerPrivilege(bytesSegments[0]); - txPrivilege = new TxPrivilege(bytesSegments[1]); - } - - private byte[] encodeBytes(byte[]... bytes) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // write one byte; - out.write(bytes.length); - for (int i = 0; i < bytes.length; i++) { - BytesEncoding.writeInTiny(bytes[i], out); - } - return out.toByteArray(); - } +@DataContract(code = DataCodes.ROLE_PRIVILEGE, name = "ROLE-PRIVILEGE") +public interface RolePrivilege { - private byte[][] decodeBytes(byte[] bytes) { - ByteArrayInputStream in = new ByteArrayInputStream(bytes); - // read one byte; - int len = in.read(); - if (len < 1 || len > SEGMENT_COUNT) { - throw new IllegalStateException("Decoded illegal privilege bytes!"); - } - byte[][] bytesSegments = new byte[len][]; - for (int i = 0; i < bytesSegments.length; i++) { - bytesSegments[i] = BytesEncoding.readInTiny(in); - } - return bytesSegments; - } + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + LedgerPrivilege getLedgerPrivilege(); - @Override - public byte[] toBytes() { - // 保持和解码时一致的顺序; - return encodeBytes(ledgerPrivilege.toBytes(), txPrivilege.toBytes()); - } + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + TransactionPrivilege getTransactionPrivilege(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java new file mode 100644 index 00000000..b73f940c --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java @@ -0,0 +1,66 @@ +package com.jd.blockchain.ledger.core; + +/** + * 对角色的授权; + * + * @author huanghaiquan + * + */ +public class RolePrivilegeAuthorization implements RolePrivilege { + + private String roleName; + + private long version; + + private LedgerPrivilege ledgerPrivilege; + + private TransactionPrivilege txPrivilege; + + public RolePrivilegeAuthorization(String roleName, long version) { + this.roleName = roleName; + this.version = version; + this.ledgerPrivilege = new LedgerPrivilege(); + this.txPrivilege = new TransactionPrivilege(); + } + + public RolePrivilegeAuthorization(String roleName, long version, RolePrivilege privilege) { + this.roleName = roleName; + this.version = version; + this.ledgerPrivilege = privilege.getLedgerPrivilege(); + this.txPrivilege = privilege.getTransactionPrivilege(); + } + + public RolePrivilegeAuthorization(String roleName, long version, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { + this.roleName = roleName; + this.version = version; + this.ledgerPrivilege = ledgerPrivilege; + this.txPrivilege = txPrivilege; + } + + public String getRoleName() { + return roleName; + } + + public long getVersion() { + return version; + } + + @Override + public LedgerPrivilege getLedgerPrivilege() { + return ledgerPrivilege; + } + + public void setLedgerPrivilege(LedgerPrivilege ledgerPrivilege) { + this.ledgerPrivilege = ledgerPrivilege; + } + + @Override + public TransactionPrivilege getTransactionPrivilege() { + return txPrivilege; + } + + public void setTransactionPrivilege(TransactionPrivilege txPrivilege) { + this.txPrivilege = txPrivilege; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java new file mode 100644 index 00000000..da4f5b6a --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java @@ -0,0 +1,23 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +/** + * 角色集; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.ROLE_SET) +public interface RoleSet { + + @DataField(order = 1, refEnum = true) + RolesPolicy getPolicy(); + + @DataField(order = 2, primitiveType = PrimitiveType.TEXT, list = true) + String[] getRoles(); + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java new file mode 100644 index 00000000..42b72bb8 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java @@ -0,0 +1,40 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.EnumContract; +import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +/** + * 多角色策略;
        + * + * 表示如何处理一个对象被赋予多个角色时的综合权限; + * + * @author huanghaiquan + * + */ +@EnumContract(code = DataCodes.ENUM_MULTI_ROLES_POLICY, name = "USER-ROLE-POLICY") +public enum RolesPolicy { + + /** + * 合并权限;
        + * + * 综合权限是所有角色权限的并集,即任何一个角色的权限都被继承; + */ + UNION((byte) 0), + + /** + * 交叉权限;
        + * + * 综合权限是所有角色权限的交集,即只有全部角色共同拥有的权限才会被继承; + */ + INTERSECT((byte) 1); + + @EnumField(type = PrimitiveType.INT8) + public final byte CODE; + + private RolesPolicy(byte code) { + this.CODE = code; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPermission.java similarity index 73% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPermission.java index b73ceaa3..fb513938 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPermission.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPermission.java @@ -11,23 +11,23 @@ import com.jd.blockchain.consts.DataCodes; * @author huanghaiquan * */ -@EnumContract(code = DataCodes.ENUM_TX_PERMISSIONS) -public enum TxPermission { +@EnumContract(code = DataCodes.ENUM_TX_PERMISSION) +public enum TransactionPermission { /** * 交易中包含指令操作; */ - COMMAND((byte) 0x01), + DIRECT_OPERATION((byte) 0x01), /** * 交易中包含合约操作; */ - CONTRACT((byte) 0x02); + CONTRACT_OPERATION((byte) 0x02); @EnumField(type = PrimitiveType.INT8) public final byte CODE; - private TxPermission(byte code) { + private TransactionPermission(byte code) { this.CODE = code; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java new file mode 100644 index 00000000..366e39f5 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java @@ -0,0 +1,17 @@ +package com.jd.blockchain.ledger.core; + +public class TransactionPrivilege extends AbstractPrivilege { + + public TransactionPrivilege() { + } + + public TransactionPrivilege(byte[] codeBytes) { + super(codeBytes); + } + + @Override + protected int getCodeIndex(TransactionPermission permission) { + return permission.CODE & 0xFF; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java deleted file mode 100644 index 30e45a18..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TxPrivilege.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.jd.blockchain.ledger.core; - -public class TxPrivilege extends AbstractPrivilege { - - public TxPrivilege(byte[] codeBytes) { - super(codeBytes); - } - - @Override - protected int getCodeIndex(TxPermission permission) { - return permission.CODE & 0xFF; - } - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java new file mode 100644 index 00000000..e4ddb63e --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java @@ -0,0 +1,155 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.storage.service.ExPolicyKVStorage; +import com.jd.blockchain.storage.service.VersioningKVEntry; +import com.jd.blockchain.storage.service.VersioningKVStorage; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.Transactional; + +public class UserRoleDataSet implements Transactional, MerkleProvable { + + /** + * 角色名称的最大 Unicode 字符数; + */ + public static final int MAX_ROLE_NAME_LENGTH = 20; + + private MerkleDataSet dataset; + + public UserRoleDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, + VersioningKVStorage verStorage) { + dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); + } + + public UserRoleDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, + ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { + dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); + } + + @Override + public HashDigest getRootHash() { + return dataset.getRootHash(); + } + + @Override + public MerkleProof getProof(Bytes key) { + return dataset.getProof(key); + } + + @Override + public boolean isUpdated() { + return dataset.isUpdated(); + } + + @Override + public void commit() { + dataset.commit(); + } + + @Override + public void cancel() { + dataset.cancel(); + } + + public long getRoleCount() { + return dataset.getDataCount(); + } + + /** + * 加入新的用户角色授权;
        + * + * 如果该用户的授权已经存在,则引发 {@link LedgerException} 异常; + * + * @param userAddress + * @param rolesPolicy + * @param roles + */ + public void addUserRoles(Bytes userAddress, RolesPolicy rolesPolicy, String... roles) { + UserRolesAuthorization roleAuth = new UserRolesAuthorization(userAddress, -1, rolesPolicy); + roleAuth.addRoles(roles); + long nv = innerSetUserRolesAuthorization(roleAuth); + if (nv < 0) { + throw new LedgerException("Roles authorization of User[" + userAddress + "] already exists!"); + } + } + + /** + * 设置用户角色授权;
        + * 如果版本校验不匹配,则返回 -1; + * + * @param userRoles + * @return + */ + public long innerSetUserRolesAuthorization(UserRolesAuthorization userRoles) { + byte[] rolesetBytes = BinaryProtocol.encode(userRoles, RoleSet.class); + return dataset.setValue(userRoles.getUserAddress(), rolesetBytes, userRoles.getVersion()); + } + + /** + * 更新用户角色授权;
        + * 如果指定用户的授权不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; + * + * @param userRoles + */ + public void updateUserRolesAuthorization(UserRolesAuthorization userRoles) { + long nv = innerSetUserRolesAuthorization(userRoles); + if (nv < 0) { + throw new LedgerException("Update to roles of user[" + userRoles.getUserAddress() + + "] failed due to wrong version[" + userRoles.getVersion() + "] !"); + } + } + + /** + * 设置用户的角色;
        + * 如果用户的角色授权不存在,则创建新的授权; + * + * @param userAddress 用户; + * @param policy 角色策略; + * @param roles 角色列表; + * @return + */ + public long setRoles(Bytes userAddress, RolesPolicy policy, String... roles) { + UserRolesAuthorization userRoles = getUserRolesAuthorization(userAddress); + if (userRoles == null) { + userRoles = new UserRolesAuthorization(userAddress, -1, policy); + } + userRoles.setPolicy(policy); + userRoles.setRoles(roles); + return innerSetUserRolesAuthorization(userRoles); + } + + /** + * 查询角色授权; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + public UserRolesAuthorization getUserRolesAuthorization(Bytes userAddress) { + // 只返回最新版本; + VersioningKVEntry kv = dataset.getDataEntry(userAddress); + if (kv == null) { + return null; + } + RoleSet roleSet = BinaryProtocol.decode(kv.getValue()); + return new UserRolesAuthorization(userAddress, kv.getVersion(), roleSet); + } + + public RolePrivilegeAuthorization[] getRoleAuthorizations() { + VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(0, (int) dataset.getDataCount()); + RolePrivilegeAuthorization[] pns = new RolePrivilegeAuthorization[kvEntries.length]; + RolePrivilege privilege; + for (int i = 0; i < pns.length; i++) { + privilege = BinaryProtocol.decode(kvEntries[i].getValue()); + pns[i] = new RolePrivilegeAuthorization(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), + privilege); + } + return pns; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java new file mode 100644 index 00000000..bc038827 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java @@ -0,0 +1,84 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Set; +import java.util.TreeSet; + +import com.jd.blockchain.utils.Bytes; + +public class UserRolesAuthorization implements RoleSet { + + private Bytes userAddress; + + private RolesPolicy policy; + + private Set roles; + + private long version; + + public UserRolesAuthorization(Bytes userAddress, long version, RolesPolicy policy) { + this.userAddress = userAddress; + this.version = version; + this.policy = policy; + this.roles = new TreeSet(); + } + + public UserRolesAuthorization(Bytes userAddress, long version, RoleSet roleSet) { + this.userAddress = userAddress; + this.version = version; + this.policy = roleSet.getPolicy(); + this.roles = initRoles(roleSet.getRoles()); + + } + + private Set initRoles(String[] roles) { + TreeSet roleset = new TreeSet(); + if (roles != null) { + for (String r : roles) { + roleset.add(r); + } + } + return roleset; + } + + public Bytes getUserAddress() { + return userAddress; + } + + @Override + public RolesPolicy getPolicy() { + return policy; + } + + public void setPolicy(RolesPolicy policy) { + this.policy = policy; + } + + public int getRoleCount() { + return roles.size(); + } + + @Override + public String[] getRoles() { + return roles.toArray(new String[roles.size()]); + } + + public long getVersion() { + return version; + } + + public void addRoles(String... roles) { + for (String r : roles) { + this.roles.add(r); + } + } + + /** + * 设置角色集合;
        + * 注意,这不是追加;现有的不在参数指定范围的角色将被移除; + * + * @param roles + */ + public void setRoles(String[] roles) { + + } +} From 7bc81950f28ebfd1bfa9d5cd47d46f9d6fb42ec8 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Sun, 18 Aug 2019 16:15:35 +0800 Subject: [PATCH 042/124] Completed user-role authorization model; --- .../com/jd/blockchain/consts/DataCodes.java | 2 +- .../ledger/core/LedgerAdminAccount.java | 78 ++++- .../ledger/core/LedgerSecurityManager.java | 4 +- .../ledger/core/PermissionService.java | 2 - .../ledger/core/PrivilegeDataSet.java | 21 -- .../{RolePrivilege.java => PrivilegeSet.java} | 4 +- .../blockchain/ledger/core/RoleDataSet.java | 175 ------------ .../ledger/core/RolePrivilegeDataSet.java | 270 ++++++++++++++++++ .../ledger/core/RolePrivilegeSettings.java | 107 +++++++ ...Authorization.java => RolePrivileges.java} | 8 +- .../ledger/core/UserRoleDataSet.java | 39 +-- .../ledger/core/UserRoleSettings.java | 53 ++++ ...RolesAuthorization.java => UserRoles.java} | 6 +- .../blockchain/ledger/LedgerMetadata_V2.java | 14 +- 14 files changed, 546 insertions(+), 237 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeDataSet.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{RolePrivilege.java => PrivilegeSet.java} (83%) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{RolePrivilegeAuthorization.java => RolePrivileges.java} (77%) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{UserRolesAuthorization.java => UserRoles.java} (86%) diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 9e76d988..f27c5258 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -60,7 +60,7 @@ public interface DataCodes { public static final int ENUM_LEDGER_PERMISSION = 0x402; public static final int ENUM_MULTI_ROLES_POLICY = 0x403; - public static final int ROLE_PRIVILEGE = 0x410; + public static final int PRIVILEGE_SET = 0x410; public static final int ROLE_SET = 0x411; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java index 1e494766..d1522ea5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java @@ -1,7 +1,5 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.LedgerSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,6 +11,9 @@ import com.jd.blockchain.crypto.HashFunction; import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerMetadata_V2; +import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; @@ -31,11 +32,13 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { public static final String LEDGER_META_PREFIX = "MTA" + LedgerConsts.KEY_SEPERATOR; public static final String LEDGER_PARTICIPANT_PREFIX = "PAR" + LedgerConsts.KEY_SEPERATOR; public static final String LEDGER_SETTING_PREFIX = "SET" + LedgerConsts.KEY_SEPERATOR; - public static final String LEDGER_PRIVILEGE_PREFIX = "PRL" + LedgerConsts.KEY_SEPERATOR; + public static final String ROLE_PRIVILEGE_PREFIX = "RPV" + LedgerConsts.KEY_SEPERATOR; + public static final String USER_ROLE_PREFIX = "URO" + LedgerConsts.KEY_SEPERATOR; private final Bytes metaPrefix; private final Bytes settingPrefix; - private final Bytes privilegePrefix; + private final Bytes rolePrivilegePrefix; + private final Bytes userRolePrefix; private LedgerMetadata origMetadata; @@ -56,6 +59,10 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { */ private ParticipantDataSet participants; + private RolePrivilegeDataSet rolePrivileges; + + private UserRoleDataSet userRoles; + /** * 账本参数配置; */ @@ -82,6 +89,14 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return readonly; } + public RolePrivilegeSettings getRolePrivileges() { + return rolePrivileges; + } + + public UserRoleSettings getUserRoles() { + return userRoles; + } + /** * 初始化账本的管理账户; * @@ -99,7 +114,8 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { VersioningKVStorage versioningStorage) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); - this.privilegePrefix = Bytes.fromString(keyPrefix + LEDGER_PRIVILEGE_PREFIX); + this.rolePrivilegePrefix = Bytes.fromString(keyPrefix + ROLE_PRIVILEGE_PREFIX); + this.userRolePrefix = Bytes.fromString(keyPrefix + USER_ROLE_PREFIX); ParticipantNode[] parties = initSetting.getConsensusParticipants(); if (parties.length == 0) { @@ -134,6 +150,14 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { this.participants.addConsensusParticipant(p); } + String rolePrivilegePrefix = keyPrefix + ROLE_PRIVILEGE_PREFIX; + this.rolePrivileges = new RolePrivilegeDataSet(this.settings.getCryptoSetting(), rolePrivilegePrefix, + exPolicyStorage, versioningStorage); + + String userRolePrefix = keyPrefix + USER_ROLE_PREFIX; + this.userRoles = new UserRoleDataSet(this.settings.getCryptoSetting(), userRolePrefix, exPolicyStorage, + versioningStorage); + // 初始化其它属性; this.storage = exPolicyStorage; this.readonly = false; @@ -143,7 +167,8 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { VersioningKVStorage versioningKVStorage, boolean readonly) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); - this.privilegePrefix = Bytes.fromString(keyPrefix + LEDGER_PRIVILEGE_PREFIX); + this.rolePrivilegePrefix = Bytes.fromString(keyPrefix + ROLE_PRIVILEGE_PREFIX); + this.userRolePrefix = Bytes.fromString(keyPrefix + USER_ROLE_PREFIX); this.storage = kvStorage; this.readonly = readonly; this.origMetadata = loadAndVerifyMetadata(adminAccountHash); @@ -167,6 +192,14 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), partiPrefix, kvStorage, versioningKVStorage, readonly); + + String rolePrivilegePrefix = keyPrefix + ROLE_PRIVILEGE_PREFIX; + this.rolePrivileges = new RolePrivilegeDataSet(metadata.getRolePrivilegesHash(), + previousSettings.getCryptoSetting(), rolePrivilegePrefix, kvStorage, versioningKVStorage, readonly); + + String userRolePrefix = keyPrefix + USER_ROLE_PREFIX; + this.userRoles = new UserRoleDataSet(metadata.getUserRolesHash(), previousSettings.getCryptoSetting(), + userRolePrefix, kvStorage, versioningKVStorage, readonly); } private LedgerSettings loadAndVerifySettings(HashDigest settingsHash) { @@ -304,6 +337,15 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { participants.commit(); metadata.setParticipantsHash(participants.getRootHash()); + // 计算并更新角色权限集合的根哈希; + rolePrivileges.commit(); + metadata.setRolePrivilegesHash(rolePrivileges.getRootHash()); + + // 计算并更新用户角色授权集合的根哈希; + userRoles.commit(); + metadata.setUserRolesHash(userRoles.getRootHash()); + + // 当前区块上下文的密码参数设置的哈希函数; HashFunction hashFunc = Crypto.getHashFunction(previousSettings.getCryptoSetting().getHashAlgorithm()); // 计算并更新参数配置的哈希; @@ -367,7 +409,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { metadata = new LedgerMetadataImpl(origMetadata); } - public static class LedgerMetadataImpl implements LedgerMetadata { + public static class LedgerMetadataImpl implements LedgerMetadata_V2 { private byte[] seed; @@ -377,6 +419,10 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { private HashDigest settingsHash; + private HashDigest rolePrivilegesHash; + + private HashDigest userRolesHash; + public LedgerMetadataImpl() { } @@ -402,6 +448,16 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return participantsHash; } + @Override + public HashDigest getRolePrivilegesHash() { + return rolePrivilegesHash; + } + + @Override + public HashDigest getUserRolesHash() { + return userRolesHash; + } + public void setSeed(byte[] seed) { this.seed = seed; } @@ -413,6 +469,14 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { public void setParticipantsHash(HashDigest participantsHash) { this.participantsHash = participantsHash; } + + public void setRolePrivilegesHash(HashDigest rolePrivilegesHash) { + this.rolePrivilegesHash = rolePrivilegesHash; + } + + public void setUserRolesHash(HashDigest userRolesHash) { + this.userRolesHash = userRolesHash; + } } } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java index fc8151f3..c9cbf359 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -22,11 +22,11 @@ public class LedgerSecurityManager { throw new IllegalStateException("Not implemented!"); } - public RolePrivilegeAuthorization setRole(String role, LedgerPrivilege privilege) { + public RolePrivileges setRole(String role, LedgerPrivilege privilege) { throw new IllegalStateException("Not implemented!"); } - public RolePrivilegeAuthorization getRole(String role) { + public RolePrivileges getRole(String role) { throw new IllegalStateException("Not implemented!"); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PermissionService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PermissionService.java index b3b9a7c2..78be086b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PermissionService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PermissionService.java @@ -1,7 +1,5 @@ package com.jd.blockchain.ledger.core; -import java.util.SortedSet; - public interface PermissionService { boolean checkLedgerPermission(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeDataSet.java deleted file mode 100644 index 9bdc3f3b..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeDataSet.java +++ /dev/null @@ -1,21 +0,0 @@ -//package com.jd.blockchain.ledger.core; -// -//import com.jd.blockchain.crypto.hash.HashDigest; -//import com.jd.blockchain.ledger.data.DigitalSignatureBlob; -// -//import my.utils.io.ExistentialKVStorage; -//import my.utils.io.VersioningKVStorage; -// -//public class PrivilegeDataSet extends GenericMerkleDataSet { -// -// public PrivilegeDataSet(CryptoSetting setting, ExistentialKVStorage merkleTreeStorage, VersioningKVStorage dataStorage) { -// this(null, setting, merkleTreeStorage, dataStorage, false); -// } -// -// public PrivilegeDataSet(HashDigest rootHash, CryptoSetting setting, ExistentialKVStorage merkleTreeStorage, -// VersioningKVStorage dataStorage, boolean readonly) { -// super(rootHash, setting, merkleTreeStorage, dataStorage, readonly, Authorization.class, AuthorizationVO.class, -// DigitalSignatureBlob.class); -// } -// -//} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java similarity index 83% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java index e40649b2..aa22efd2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilege.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java @@ -11,8 +11,8 @@ import com.jd.blockchain.consts.DataCodes; * @author huanghaiquan * */ -@DataContract(code = DataCodes.ROLE_PRIVILEGE, name = "ROLE-PRIVILEGE") -public interface RolePrivilege { +@DataContract(code = DataCodes.PRIVILEGE_SET, name = "PRIVILEGE-SET") +public interface PrivilegeSet { @DataField(order = 1, primitiveType = PrimitiveType.BYTES) LedgerPrivilege getLedgerPrivilege(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java deleted file mode 100644 index 6fb3ee65..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleDataSet.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.storage.service.ExPolicyKVStorage; -import com.jd.blockchain.storage.service.VersioningKVEntry; -import com.jd.blockchain.storage.service.VersioningKVStorage; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.Transactional; - -public class RoleDataSet implements Transactional, MerkleProvable { - - /** - * 角色名称的最大 Unicode 字符数; - */ - public static final int MAX_ROLE_NAME_LENGTH = 20; - - private MerkleDataSet dataset; - - public RoleDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, - VersioningKVStorage verStorage) { - dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); - } - - public RoleDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, - ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { - dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); - } - - @Override - public HashDigest getRootHash() { - return dataset.getRootHash(); - } - - @Override - public MerkleProof getProof(Bytes key) { - return dataset.getProof(key); - } - - @Override - public boolean isUpdated() { - return dataset.isUpdated(); - } - - @Override - public void commit() { - dataset.commit(); - } - - @Override - public void cancel() { - dataset.cancel(); - } - - public long getRoleCount() { - return dataset.getDataCount(); - } - - /** - * 加入新的角色授权;
        - * - * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; - * - * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode 字符; - * @param ledgerPrivilege - * @param txPrivilege - */ - public void addRoleAuthorization(String roleName, LedgerPrivilege ledgerPrivilege, - TransactionPrivilege txPrivilege) { - RolePrivilegeAuthorization roleAuth = new RolePrivilegeAuthorization(roleName, -1, ledgerPrivilege, txPrivilege); - long nv = innerSetRoleAuthorization(roleAuth); - if (nv < 0) { - throw new LedgerException("Role[" + roleName + "] already exist!"); - } - } - - /** - * 设置角色授权;
        - * 如果版本校验不匹配,则返回 -1; - * - * @param roleAuth - * @return - */ - public long innerSetRoleAuthorization(RolePrivilegeAuthorization roleAuth) { - if (roleAuth.getRoleName().length() > MAX_ROLE_NAME_LENGTH) { - throw new LedgerException("Too long role name!"); - } - Bytes key = encodeKey(roleAuth.getRoleName()); - byte[] privilegeBytes = BinaryProtocol.encode(roleAuth, RolePrivilege.class); - return dataset.setValue(key, privilegeBytes, roleAuth.getVersion()); - } - - /** - * 更新角色授权;
        - * 如果指定的角色不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; - * - * @param participant - */ - public void updateRoleAuthorization(RolePrivilegeAuthorization roleAuth) { - long nv = innerSetRoleAuthorization(roleAuth); - if (nv < 0) { - throw new LedgerException("Update to RoleAuthorization[" + roleAuth.getRoleName() - + "] failed due to wrong version[" + roleAuth.getVersion() + "] !"); - } - } - - /** - * 授权角色指定的权限;
        - * 如果角色不存在,则返回 -1; - * - * @param participant - */ - public long authorizePermissions(String roleName, LedgerPermission... permissions) { - RolePrivilegeAuthorization roleAuth = getRoleAuthorization(roleName); - if (roleAuth == null) { - return -1; - } - roleAuth.getLedgerPrivilege().enable(permissions); - return innerSetRoleAuthorization(roleAuth); - } - - /** - * 授权角色指定的权限;
        - * 如果角色不存在,则返回 -1; - * - * @param participant - */ - public long authorizePermissions(String roleName, TransactionPermission... permissions) { - RolePrivilegeAuthorization roleAuth = getRoleAuthorization(roleName); - if (roleAuth == null) { - return -1; - } - roleAuth.getTransactionPrivilege().enable(permissions); - return innerSetRoleAuthorization(roleAuth); - } - - private Bytes encodeKey(String address) { - // return id + ""; - return Bytes.fromString(address); - } - - /** - * 查询角色授权; - * - *
        - * 如果不存在,则返回 null; - * - * @param address - * @return - */ - public RolePrivilegeAuthorization getRoleAuthorization(String roleName) { - // 只返回最新版本; - Bytes key = encodeKey(roleName); - VersioningKVEntry kv = dataset.getDataEntry(key); - if (kv == null) { - return null; - } - RolePrivilege privilege = BinaryProtocol.decode(kv.getValue()); - return new RolePrivilegeAuthorization(roleName, kv.getVersion(), privilege); - } - - public RolePrivilegeAuthorization[] getRoleAuthorizations() { - VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(0, (int) dataset.getDataCount()); - RolePrivilegeAuthorization[] pns = new RolePrivilegeAuthorization[kvEntries.length]; - RolePrivilege privilege; - for (int i = 0; i < pns.length; i++) { - privilege = BinaryProtocol.decode(kvEntries[i].getValue()); - pns[i] = new RolePrivilegeAuthorization(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), privilege); - } - return pns; - } - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java new file mode 100644 index 00000000..327a4d3f --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java @@ -0,0 +1,270 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.storage.service.ExPolicyKVStorage; +import com.jd.blockchain.storage.service.VersioningKVEntry; +import com.jd.blockchain.storage.service.VersioningKVStorage; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.Transactional; + +public class RolePrivilegeDataSet implements Transactional, MerkleProvable, RolePrivilegeSettings { + + /** + * 角色名称的最大 Unicode 字符数; + */ + public static final int MAX_ROLE_NAME_LENGTH = 20; + + private MerkleDataSet dataset; + + public RolePrivilegeDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, + VersioningKVStorage verStorage) { + dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); + } + + public RolePrivilegeDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, + ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { + dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); + } + + @Override + public HashDigest getRootHash() { + return dataset.getRootHash(); + } + + @Override + public MerkleProof getProof(Bytes key) { + return dataset.getProof(key); + } + + @Override + public boolean isUpdated() { + return dataset.isUpdated(); + } + + @Override + public void commit() { + dataset.commit(); + } + + @Override + public void cancel() { + dataset.cancel(); + } + + @Override + public long getRoleCount() { + return dataset.getDataCount(); + } + + /** + * 加入新的角色授权;
        + * + * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; + * + * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode 字符; + * @param ledgerPrivilege + * @param txPrivilege + */ + @Override + public void addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { + RolePrivileges roleAuth = new RolePrivileges(roleName, -1, ledgerPrivilege, + txPrivilege); + long nv = setRolePrivilege(roleAuth); + if (nv < 0) { + throw new LedgerException("Role[" + roleName + "] already exist!"); + } + } + + /** + * 设置角色授权;
        + * 如果版本校验不匹配,则返回 -1; + * + * @param roleAuth + * @return + */ + private long setRolePrivilege(RolePrivileges roleAuth) { + if (roleAuth.getRoleName().length() > MAX_ROLE_NAME_LENGTH) { + throw new LedgerException("Too long role name!"); + } + Bytes key = encodeKey(roleAuth.getRoleName()); + byte[] privilegeBytes = BinaryProtocol.encode(roleAuth, PrivilegeSet.class); + return dataset.setValue(key, privilegeBytes, roleAuth.getVersion()); + } + + /** + * 更新角色授权;
        + * 如果指定的角色不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; + * + * @param participant + */ + @Override + public void updateRolePrivilege(RolePrivileges roleAuth) { + long nv = setRolePrivilege(roleAuth); + if (nv < 0) { + throw new LedgerException("Update to RoleAuthorization[" + roleAuth.getRoleName() + + "] failed due to wrong version[" + roleAuth.getVersion() + "] !"); + } + } + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + @Override + public long enablePermissions(String roleName, LedgerPermission... permissions) { + RolePrivileges roleAuth = getRolePrivilege(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getLedgerPrivilege().enable(permissions); + return setRolePrivilege(roleAuth); + } + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + @Override + public long enablePermissions(String roleName, TransactionPermission... permissions) { + RolePrivileges roleAuth = getRolePrivilege(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getTransactionPrivilege().enable(permissions); + return setRolePrivilege(roleAuth); + } + + /** + * 禁止角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + @Override + public long disablePermissions(String roleName, LedgerPermission... permissions) { + RolePrivileges roleAuth = getRolePrivilege(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getLedgerPrivilege().disable(permissions); + return setRolePrivilege(roleAuth); + } + + /** + * 禁止角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + @Override + public long disablePermissions(String roleName, TransactionPermission... permissions) { + RolePrivileges roleAuth = getRolePrivilege(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getTransactionPrivilege().disable(permissions); + return setRolePrivilege(roleAuth); + } + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName + * @param ledgerPermissions + * @param txPermissions + * @return + */ + @Override + public long enablePermissions(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] txPermissions) { + RolePrivileges roleAuth = getRolePrivilege(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getLedgerPrivilege().enable(ledgerPermissions); + roleAuth.getTransactionPrivilege().enable(txPermissions); + return setRolePrivilege(roleAuth); + } + + /** + * 禁用角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName + * @param ledgerPermissions + * @param txPermissions + * @return + */ + @Override + public long disablePermissions(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] txPermissions) { + RolePrivileges roleAuth = getRolePrivilege(roleName); + if (roleAuth == null) { + return -1; + } + roleAuth.getLedgerPrivilege().disable(ledgerPermissions); + roleAuth.getTransactionPrivilege().disable(txPermissions); + return setRolePrivilege(roleAuth); + } + + private Bytes encodeKey(String address) { + // return id + ""; + return Bytes.fromString(address); + } + + /** + * 查询角色授权; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + @Override + public RolePrivileges getRolePrivilege(String roleName) { + // 只返回最新版本; + Bytes key = encodeKey(roleName); + VersioningKVEntry kv = dataset.getDataEntry(key); + if (kv == null) { + return null; + } + PrivilegeSet privilege = BinaryProtocol.decode(kv.getValue()); + return new RolePrivileges(roleName, kv.getVersion(), privilege); + } + + @Override + public RolePrivileges[] getRolePrivileges(int index, int count) { + VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(index, count); + RolePrivileges[] pns = new RolePrivileges[kvEntries.length]; + PrivilegeSet privilege; + for (int i = 0; i < pns.length; i++) { + privilege = BinaryProtocol.decode(kvEntries[i].getValue()); + pns[i] = new RolePrivileges(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), + privilege); + } + return pns; + } + + @Override + public RolePrivileges[] getRolePrivileges() { + return getRolePrivileges(0, (int) getRoleCount()); + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java new file mode 100644 index 00000000..d900e002 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java @@ -0,0 +1,107 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.ledger.LedgerException; + +public interface RolePrivilegeSettings { + + long getRoleCount(); + + /** + * 加入新的角色授权;
        + * + * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; + * + * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode 字符; + * @param ledgerPrivilege + * @param txPrivilege + */ + void addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege); + + /** + * 更新角色授权;
        + * 如果指定的角色不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; + * + * @param participant + */ + void updateRolePrivilege(RolePrivileges roleAuth); + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + long enablePermissions(String roleName, LedgerPermission... permissions); + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + long enablePermissions(String roleName, TransactionPermission... permissions); + + /** + * 禁止角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + long disablePermissions(String roleName, LedgerPermission... permissions); + + /** + * 禁止角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName 角色; + * @param permissions 权限列表; + * @return + */ + long disablePermissions(String roleName, TransactionPermission... permissions); + + /** + * 授权角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName + * @param ledgerPermissions + * @param txPermissions + * @return + */ + long enablePermissions(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] txPermissions); + + /** + * 禁用角色指定的权限;
        + * 如果角色不存在,则返回 -1; + * + * @param roleName + * @param ledgerPermissions + * @param txPermissions + * @return + */ + long disablePermissions(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] txPermissions); + + /** + * 查询角色授权; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + RolePrivileges getRolePrivilege(String roleName); + + RolePrivileges[] getRolePrivileges(int index, int count); + + RolePrivileges[] getRolePrivileges(); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivileges.java similarity index 77% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivileges.java index b73f940c..e2c4f8a5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeAuthorization.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivileges.java @@ -6,7 +6,7 @@ package com.jd.blockchain.ledger.core; * @author huanghaiquan * */ -public class RolePrivilegeAuthorization implements RolePrivilege { +public class RolePrivileges implements PrivilegeSet { private String roleName; @@ -16,21 +16,21 @@ public class RolePrivilegeAuthorization implements RolePrivilege { private TransactionPrivilege txPrivilege; - public RolePrivilegeAuthorization(String roleName, long version) { + public RolePrivileges(String roleName, long version) { this.roleName = roleName; this.version = version; this.ledgerPrivilege = new LedgerPrivilege(); this.txPrivilege = new TransactionPrivilege(); } - public RolePrivilegeAuthorization(String roleName, long version, RolePrivilege privilege) { + public RolePrivileges(String roleName, long version, PrivilegeSet privilege) { this.roleName = roleName; this.version = version; this.ledgerPrivilege = privilege.getLedgerPrivilege(); this.txPrivilege = privilege.getTransactionPrivilege(); } - public RolePrivilegeAuthorization(String roleName, long version, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { + public RolePrivileges(String roleName, long version, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { this.roleName = roleName; this.version = version; this.ledgerPrivilege = ledgerPrivilege; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java index e4ddb63e..36b6b633 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java @@ -10,7 +10,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class UserRoleDataSet implements Transactional, MerkleProvable { +public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleSettings { /** * 角色名称的最大 Unicode 字符数; @@ -54,6 +54,7 @@ public class UserRoleDataSet implements Transactional, MerkleProvable { dataset.cancel(); } + @Override public long getRoleCount() { return dataset.getDataCount(); } @@ -67,10 +68,11 @@ public class UserRoleDataSet implements Transactional, MerkleProvable { * @param rolesPolicy * @param roles */ + @Override public void addUserRoles(Bytes userAddress, RolesPolicy rolesPolicy, String... roles) { - UserRolesAuthorization roleAuth = new UserRolesAuthorization(userAddress, -1, rolesPolicy); + UserRoles roleAuth = new UserRoles(userAddress, -1, rolesPolicy); roleAuth.addRoles(roles); - long nv = innerSetUserRolesAuthorization(roleAuth); + long nv = setUserRolesAuthorization(roleAuth); if (nv < 0) { throw new LedgerException("Roles authorization of User[" + userAddress + "] already exists!"); } @@ -83,7 +85,7 @@ public class UserRoleDataSet implements Transactional, MerkleProvable { * @param userRoles * @return */ - public long innerSetUserRolesAuthorization(UserRolesAuthorization userRoles) { + private long setUserRolesAuthorization(UserRoles userRoles) { byte[] rolesetBytes = BinaryProtocol.encode(userRoles, RoleSet.class); return dataset.setValue(userRoles.getUserAddress(), rolesetBytes, userRoles.getVersion()); } @@ -94,8 +96,9 @@ public class UserRoleDataSet implements Transactional, MerkleProvable { * * @param userRoles */ - public void updateUserRolesAuthorization(UserRolesAuthorization userRoles) { - long nv = innerSetUserRolesAuthorization(userRoles); + @Override + public void updateUserRoles(UserRoles userRoles) { + long nv = setUserRolesAuthorization(userRoles); if (nv < 0) { throw new LedgerException("Update to roles of user[" + userRoles.getUserAddress() + "] failed due to wrong version[" + userRoles.getVersion() + "] !"); @@ -111,14 +114,15 @@ public class UserRoleDataSet implements Transactional, MerkleProvable { * @param roles 角色列表; * @return */ + @Override public long setRoles(Bytes userAddress, RolesPolicy policy, String... roles) { - UserRolesAuthorization userRoles = getUserRolesAuthorization(userAddress); + UserRoles userRoles = getUserRoles(userAddress); if (userRoles == null) { - userRoles = new UserRolesAuthorization(userAddress, -1, policy); + userRoles = new UserRoles(userAddress, -1, policy); } userRoles.setPolicy(policy); userRoles.setRoles(roles); - return innerSetUserRolesAuthorization(userRoles); + return setUserRolesAuthorization(userRoles); } /** @@ -130,24 +134,25 @@ public class UserRoleDataSet implements Transactional, MerkleProvable { * @param address * @return */ - public UserRolesAuthorization getUserRolesAuthorization(Bytes userAddress) { + @Override + public UserRoles getUserRoles(Bytes userAddress) { // 只返回最新版本; VersioningKVEntry kv = dataset.getDataEntry(userAddress); if (kv == null) { return null; } RoleSet roleSet = BinaryProtocol.decode(kv.getValue()); - return new UserRolesAuthorization(userAddress, kv.getVersion(), roleSet); + return new UserRoles(userAddress, kv.getVersion(), roleSet); } - public RolePrivilegeAuthorization[] getRoleAuthorizations() { + @Override + public UserRoles[] getRoleAuthorizations() { VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(0, (int) dataset.getDataCount()); - RolePrivilegeAuthorization[] pns = new RolePrivilegeAuthorization[kvEntries.length]; - RolePrivilege privilege; + UserRoles[] pns = new UserRoles[kvEntries.length]; + RoleSet roleset; for (int i = 0; i < pns.length; i++) { - privilege = BinaryProtocol.decode(kvEntries[i].getValue()); - pns[i] = new RolePrivilegeAuthorization(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), - privilege); + roleset = BinaryProtocol.decode(kvEntries[i].getValue()); + pns[i] = new UserRoles(kvEntries[i].getKey(), kvEntries[i].getVersion(), roleset); } return pns; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java new file mode 100644 index 00000000..d720aa59 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java @@ -0,0 +1,53 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.utils.Bytes; + +public interface UserRoleSettings { + + long getRoleCount(); + + /** + * 加入新的用户角色授权;
        + * + * 如果该用户的授权已经存在,则引发 {@link LedgerException} 异常; + * + * @param userAddress + * @param rolesPolicy + * @param roles + */ + void addUserRoles(Bytes userAddress, RolesPolicy rolesPolicy, String... roles); + + /** + * 更新用户角色授权;
        + * 如果指定用户的授权不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; + * + * @param userRoles + */ + void updateUserRoles(UserRoles userRoles); + + /** + * 设置用户的角色;
        + * 如果用户的角色授权不存在,则创建新的授权; + * + * @param userAddress 用户; + * @param policy 角色策略; + * @param roles 角色列表; + * @return + */ + long setRoles(Bytes userAddress, RolesPolicy policy, String... roles); + + /** + * 查询角色授权; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + UserRoles getUserRoles(Bytes userAddress); + + UserRoles[] getRoleAuthorizations(); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java similarity index 86% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java index bc038827..57ec2110 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesAuthorization.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java @@ -5,7 +5,7 @@ import java.util.TreeSet; import com.jd.blockchain.utils.Bytes; -public class UserRolesAuthorization implements RoleSet { +public class UserRoles implements RoleSet { private Bytes userAddress; @@ -15,14 +15,14 @@ public class UserRolesAuthorization implements RoleSet { private long version; - public UserRolesAuthorization(Bytes userAddress, long version, RolesPolicy policy) { + public UserRoles(Bytes userAddress, long version, RolesPolicy policy) { this.userAddress = userAddress; this.version = version; this.policy = policy; this.roles = new TreeSet(); } - public UserRolesAuthorization(Bytes userAddress, long version, RoleSet roleSet) { + public UserRoles(Bytes userAddress, long version, RoleSet roleSet) { this.userAddress = userAddress; this.version = version; this.policy = roleSet.getPolicy(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java index 1fbe075e..01ce182f 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java @@ -8,7 +8,7 @@ import com.jd.blockchain.crypto.HashDigest; /** * {@link LedgerMetadata_V2} 是 {@link LedgerMetadata} 的升级版本,新增加了 - * {@link #getPrivilegeHash()} 属性; + * {@link #getRolePrivilegesHash()} 属性; * * @author huanghaiquan * @@ -17,11 +17,19 @@ import com.jd.blockchain.crypto.HashDigest; public interface LedgerMetadata_V2 extends LedgerMetadata { /** - * 加入新的版本; + * 角色权限集合的根哈希;; * * @return */ @DataField(order = 4, primitiveType = PrimitiveType.BYTES) - HashDigest getPrivilegeHash(); + HashDigest getRolePrivilegesHash(); + + /** + * 用户角色授权集合的根哈希; + * + * @return + */ + @DataField(order = 5, primitiveType = PrimitiveType.BYTES) + HashDigest getUserRolesHash(); } From cab12e90833b33c29b99a9482099827720b5372d Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 21 Aug 2019 23:00:01 +0800 Subject: [PATCH 043/124] Passed verification of the testcases for RolePrivilegeDataSet and UserRolesDataSet; --- .../com/jd/blockchain/consts/DataCodes.java | 1 + .../ledger/core/LedgerAdminAccount.java | 18 +- .../ledger/core/LedgerPermission.java | 4 +- .../blockchain/ledger/core/MerkleDataSet.java | 53 +- .../ledger/core/ParticipantCertData.java | 7 +- .../ledger/core/ParticipantDataSet.java | 13 +- .../blockchain/ledger/core/PrivilegeSet.java | 2 +- .../ledger/core/RolePrivilegeDataSet.java | 42 +- .../ledger/core/RolePrivilegeSettings.java | 20 +- .../ledger/core/UserRoleDataSet.java | 19 +- .../ledger/core/UserRoleSettings.java | 19 +- .../jd/blockchain/ledger/core/UserRoles.java | 6 +- .../ledger/LedgerAdminAccountTest.java | 196 +++-- .../ledger/LedgerInitOperationTest.java | 4 +- .../LedgerInitSettingSerializeTest.java | 6 +- .../blockchain/ledger/LedgerManagerTest.java | 8 +- .../blockchain/ledger/LedgerMetaDataTest.java | 4 +- .../jd/blockchain/ledger/LedgerTestUtils.java | 27 +- .../blockchain/ledger/MerkleDataSetTest.java | 12 + .../ledger/RolePrivilegeDataSetTest.java | 70 ++ .../ledger/UserRoleDataSetTest.java | 62 ++ .../ledger/AuthorizationException.java | 17 + .../jd/blockchain/ledger/LedgerAdminInfo.java | 2 +- .../blockchain/ledger/LedgerMetadata_V2.java | 2 +- .../jd/blockchain/ledger/ParticipantNode.java | 5 +- .../transaction/ConsensusParticipantData.java | 7 +- .../peer/consensus/ConsensusRealmImpl.java | 2 +- .../sdk/converters/ClientResolveUtil.java | 719 +++++++++--------- .../service/utils/MemoryKVStorage.java | 15 - .../service/utils/VersioningKVData.java | 35 + .../service/utils/VersioningKVStorageMap.java | 32 - .../com/jd/blockchain/intgr/perf/Utils.java | 25 +- .../tools/initializer/LedgerInitCommand.java | 3 +- .../initializer/LedgerInitProperties.java | 9 +- 34 files changed, 889 insertions(+), 577 deletions(-) create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AuthorizationException.java create mode 100644 source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVData.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index f27c5258..d936c788 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -67,6 +67,7 @@ public interface DataCodes { // contract types of metadata; public static final int METADATA = 0x600; + public static final int METADATA_V2 = 0x601; public static final int METADATA_INIT_SETTING = 0x610; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java index d1522ea5..e120525c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java @@ -25,6 +25,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { static { DataContractRegistry.register(LedgerMetadata.class); + DataContractRegistry.register(LedgerMetadata_V2.class); } private static Logger LOGGER = LoggerFactory.getLogger(LedgerAdminAccount.class); @@ -40,7 +41,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { private final Bytes rolePrivilegePrefix; private final Bytes userRolePrefix; - private LedgerMetadata origMetadata; + private LedgerMetadata_V2 origMetadata; private LedgerMetadataImpl metadata; @@ -225,7 +226,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return BinaryProtocol.encode(setting, LedgerSettings.class); } - private LedgerMetadata loadAndVerifyMetadata(HashDigest adminAccountHash) { + private LedgerMetadata_V2 loadAndVerifyMetadata(HashDigest adminAccountHash) { Bytes key = encodeMetadataKey(adminAccountHash); byte[] bytes = storage.get(key); HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); @@ -253,7 +254,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { * @see com.jd.blockchain.ledger.core.LedgerAdministration#getMetadata() */ @Override - public LedgerMetadata getMetadata() { + public LedgerMetadata_V2 getMetadata() { return metadata; } @@ -325,7 +326,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { @Override public boolean isUpdated() { - return updated || participants.isUpdated(); + return updated || participants.isUpdated() || rolePrivileges.isUpdated() || userRoles.isUpdated(); } @Override @@ -392,12 +393,12 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { updated = false; } - private LedgerMetadata deserializeMetadata(byte[] bytes) { + private LedgerMetadata_V2 deserializeMetadata(byte[] bytes) { return BinaryProtocol.decode(bytes); } private byte[] serializeMetadata(LedgerMetadataImpl config) { - return BinaryProtocol.encode(config, LedgerMetadata.class); + return BinaryProtocol.encode(config, LedgerMetadata_V2.class); } @Override @@ -426,11 +427,12 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { public LedgerMetadataImpl() { } - public LedgerMetadataImpl(LedgerMetadata metadata) { + public LedgerMetadataImpl(LedgerMetadata_V2 metadata) { this.seed = metadata.getSeed(); -// this.setting = metadata.getSetting(); this.participantsHash = metadata.getParticipantsHash(); this.settingsHash = metadata.getSettingsHash(); + this.rolePrivilegesHash = metadata.getRolePrivilegesHash(); + this.userRolesHash = metadata.getUserRolesHash(); } @Override diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java index 2a5cec59..04111ef6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java @@ -15,10 +15,10 @@ import com.jd.blockchain.consts.DataCodes; public enum LedgerPermission { /** - * 设置角色权限;
        + * 授权角色权限;
        * 包括:创建角色、设置角色的权限代码、分配用户角色; */ - SET_ROLE_PERMISSION((byte) 0x01), + AUTHORIZE_ROLES((byte) 0x01), /** * 设置共识协议;
        diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java index d17d219f..30846867 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java @@ -8,6 +8,7 @@ import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; import com.jd.blockchain.storage.service.VersioningKVEntry; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.storage.service.utils.BufferedKVStorage; +import com.jd.blockchain.storage.service.utils.VersioningKVData; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; import com.jd.blockchain.utils.io.BytesUtils; @@ -62,12 +63,9 @@ public class MerkleDataSet implements Transactional, MerkleProvable { /** * 创建一个新的 MerkleDataSet; * - * @param setting - * 密码设置; - * @param exPolicyStorage - * 默克尔树的存储; - * @param versioningStorage - * 数据的存储; + * @param setting 密码设置; + * @param exPolicyStorage 默克尔树的存储; + * @param versioningStorage 数据的存储; */ public MerkleDataSet(CryptoSetting setting, String keyPrefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage versioningStorage) { @@ -149,7 +147,7 @@ public class MerkleDataSet implements Transactional, MerkleProvable { } return values; } - + public VersioningKVEntry[] getLatestDataEntries(int fromIndex, int count) { if (count > LedgerConsts.MAX_LIST_COUNT) { throw new IllegalArgumentException("Count exceed the upper limit[" + LedgerConsts.MAX_LIST_COUNT + "]!"); @@ -158,16 +156,19 @@ public class MerkleDataSet implements Transactional, MerkleProvable { throw new IllegalArgumentException("Index out of bound!"); } VersioningKVEntry[] values = new VersioningKVEntry[count]; + byte[] bytesValue; for (int i = 0; i < count; i++) { MerkleDataNode dataNode = merkleTree.getData(fromIndex + i); Bytes dataKey = encodeDataKey(dataNode.getKey()); - values[i] = valueStorage.getEntry(dataKey, dataNode.getVersion()); + bytesValue = valueStorage.get(dataKey, dataNode.getVersion()); + values[i] = new VersioningKVData(dataNode.getKey(), dataNode.getVersion(), bytesValue); } return values; } /** * get the data at the specific index; + * * @param fromIndex * @return */ @@ -179,15 +180,15 @@ public class MerkleDataSet implements Transactional, MerkleProvable { /** * get the key at the specific index; + * * @param fromIndex * @return */ public String getKeyAtIndex(int fromIndex) { MerkleDataNode dataNode = merkleTree.getData(fromIndex); - return new String(dataNode.getKey().toBytes()); + return new String(dataNode.getKey().toBytes()); } - /** * Create or update the value associated the specified key if the version * checking is passed.
        @@ -199,12 +200,9 @@ public class MerkleDataSet implements Transactional, MerkleProvable { * If updating is performed, the version of the key increase by 1.
        * If creating is performed, the version of the key initialize by 0.
        * - * @param key - * The key of data; - * @param value - * The value of data; - * @param version - * The expected latest version of the key. + * @param key The key of data; + * @param value The value of data; + * @param version The expected latest version of the key. * @return The new version of the key.
        * If the key is new created success, then return 0;
        * If the key is updated success, then return the new version;
        @@ -226,12 +224,9 @@ public class MerkleDataSet implements Transactional, MerkleProvable { * If updating is performed, the version of the key increase by 1.
        * If creating is performed, the version of the key initialize by 0.
        * - * @param key - * The key of data; - * @param value - * The value of data; - * @param version - * The expected latest version of the key. + * @param key The key of data; + * @param value The value of data; + * @param version The expected latest version of the key. * @return The new version of the key.
        * If the key is new created success, then return 0;
        * If the key is updated success, then return the new version;
        @@ -430,7 +425,12 @@ public class MerkleDataSet implements Transactional, MerkleProvable { if (latestVersion < 0) { return null; } - return valueStorage.getEntry(key, latestVersion); + Bytes dataKey = encodeDataKey(key); + byte[] value = valueStorage.get(dataKey, latestVersion); + if (value == null) { + return null; + } + return new VersioningKVData(key, latestVersion, value); } public VersioningKVEntry getDataEntry(Bytes key, long version) { @@ -441,7 +441,12 @@ public class MerkleDataSet implements Transactional, MerkleProvable { return null; } version = version < 0 ? latestVersion : version; - return valueStorage.getEntry(key, version); + Bytes dataKey = encodeDataKey(key); + byte[] value = valueStorage.get(dataKey, version); + if (value == null) { + return null; + } + return new VersioningKVData(key, version, value); } public MerkleDataEntry getMerkleEntry(Bytes key, long version) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java index 05fd0611..82d066e2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.utils.Bytes; /** * 参与方证书数据对象; @@ -12,7 +13,7 @@ import com.jd.blockchain.ledger.ParticipantNode; public class ParticipantCertData implements ParticipantNode { private int id; - private String address; + private Bytes address; private String name; private PubKey pubKey; @@ -26,14 +27,14 @@ public class ParticipantCertData implements ParticipantNode { this.pubKey = participantNode.getPubKey(); } - public ParticipantCertData(String address, String name, PubKey pubKey) { + public ParticipantCertData(Bytes address, String name, PubKey pubKey) { this.address = address; this.name = name; this.pubKey = pubKey; } @Override - public String getAddress() { + public Bytes getAddress() { return address; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java index bd84185d..9c015a6f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java @@ -73,9 +73,8 @@ public class ParticipantDataSet implements Transactional, MerkleProvable { } } - private Bytes encodeKey(String address) { - // return id + ""; - return Bytes.fromString(address); + private Bytes encodeKey(Bytes address) { + return address; } /** @@ -87,7 +86,7 @@ public class ParticipantDataSet implements Transactional, MerkleProvable { * @param address * @return */ - public ParticipantNode getParticipant(String address) { + public ParticipantNode getParticipant(Bytes address) { Bytes key = encodeKey(address); byte[] bytes = dataset.getValue(key); if (bytes == null) { @@ -95,11 +94,11 @@ public class ParticipantDataSet implements Transactional, MerkleProvable { } return BinaryProtocol.decode(bytes); } - + public ParticipantNode[] getParticipants() { - byte[][] bytes = dataset.getLatestValues(0, (int)dataset.getDataCount()); + byte[][] bytes = dataset.getLatestValues(0, (int) dataset.getDataCount()); ParticipantNode[] pns = new ParticipantNode[bytes.length]; - + for (int i = 0; i < pns.length; i++) { pns[i] = BinaryProtocol.decode(bytes[i]); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java index aa22efd2..ca4b2914 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java @@ -17,7 +17,7 @@ public interface PrivilegeSet { @DataField(order = 1, primitiveType = PrimitiveType.BYTES) LedgerPrivilege getLedgerPrivilege(); - @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + @DataField(order = 2, primitiveType = PrimitiveType.BYTES) TransactionPrivilege getTransactionPrivilege(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java index 327a4d3f..e221bb06 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java @@ -12,11 +12,6 @@ import com.jd.blockchain.utils.Transactional; public class RolePrivilegeDataSet implements Transactional, MerkleProvable, RolePrivilegeSettings { - /** - * 角色名称的最大 Unicode 字符数; - */ - public static final int MAX_ROLE_NAME_LENGTH = 20; - private MerkleDataSet dataset; public RolePrivilegeDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, @@ -60,22 +55,30 @@ public class RolePrivilegeDataSet implements Transactional, MerkleProvable, Role } /** - * 加入新的角色授权;
        - * - * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; - * - * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode 字符; - * @param ledgerPrivilege - * @param txPrivilege + * */ @Override - public void addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { - RolePrivileges roleAuth = new RolePrivileges(roleName, -1, ledgerPrivilege, - txPrivilege); + public long addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { + RolePrivileges roleAuth = new RolePrivileges(roleName, -1, ledgerPrivilege, txPrivilege); long nv = setRolePrivilege(roleAuth); if (nv < 0) { throw new LedgerException("Role[" + roleName + "] already exist!"); } + return nv; + } + + @Override + public long addRolePrivilege(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] txPermissions) { + LedgerPrivilege ledgerPrivilege = new LedgerPrivilege(); + for (LedgerPermission lp : ledgerPermissions) { + ledgerPrivilege.enable(lp); + } + TransactionPrivilege txPrivilege = new TransactionPrivilege(); + for (TransactionPermission tp : txPermissions) { + txPrivilege.enable(tp); + } + return addRolePrivilege(roleName, ledgerPrivilege, txPrivilege); } /** @@ -144,7 +147,7 @@ public class RolePrivilegeDataSet implements Transactional, MerkleProvable, Role roleAuth.getTransactionPrivilege().enable(permissions); return setRolePrivilege(roleAuth); } - + /** * 禁止角色指定的权限;
        * 如果角色不存在,则返回 -1; @@ -162,7 +165,7 @@ public class RolePrivilegeDataSet implements Transactional, MerkleProvable, Role roleAuth.getLedgerPrivilege().disable(permissions); return setRolePrivilege(roleAuth); } - + /** * 禁止角色指定的权限;
        * 如果角色不存在,则返回 -1; @@ -248,7 +251,7 @@ public class RolePrivilegeDataSet implements Transactional, MerkleProvable, Role PrivilegeSet privilege = BinaryProtocol.decode(kv.getValue()); return new RolePrivileges(roleName, kv.getVersion(), privilege); } - + @Override public RolePrivileges[] getRolePrivileges(int index, int count) { VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(index, count); @@ -256,8 +259,7 @@ public class RolePrivilegeDataSet implements Transactional, MerkleProvable, Role PrivilegeSet privilege; for (int i = 0; i < pns.length; i++) { privilege = BinaryProtocol.decode(kvEntries[i].getValue()); - pns[i] = new RolePrivileges(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), - privilege); + pns[i] = new RolePrivileges(kvEntries[i].getKey().toUTF8String(), kvEntries[i].getVersion(), privilege); } return pns; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java index d900e002..47cdca37 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java @@ -3,6 +3,11 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.LedgerException; public interface RolePrivilegeSettings { + + /** + * 角色名称的最大 Unicode 字符数; + */ + public static final int MAX_ROLE_NAME_LENGTH = 20; long getRoleCount(); @@ -15,7 +20,20 @@ public interface RolePrivilegeSettings { * @param ledgerPrivilege * @param txPrivilege */ - void addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege); + long addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege); + + /** + * 加入新的角色授权;
        + * + * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; + * + * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode + * 字符; + * @param ledgerPermissions 给角色授予的账本权限列表; + * @param txPermissions 给角色授予的交易权限列表; + * @return + */ + long addRolePrivilege(String roleName, LedgerPermission[] ledgerPermissions, TransactionPermission[] txPermissions); /** * 更新角色授权;
        diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java index 36b6b633..9fd9b834 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.AuthorizationException; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.storage.service.ExPolicyKVStorage; @@ -11,12 +12,7 @@ import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleSettings { - - /** - * 角色名称的最大 Unicode 字符数; - */ - public static final int MAX_ROLE_NAME_LENGTH = 20; - + private MerkleDataSet dataset; public UserRoleDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, @@ -55,7 +51,7 @@ public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleS } @Override - public long getRoleCount() { + public long getUserCount() { return dataset.getDataCount(); } @@ -74,7 +70,7 @@ public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleS roleAuth.addRoles(roles); long nv = setUserRolesAuthorization(roleAuth); if (nv < 0) { - throw new LedgerException("Roles authorization of User[" + userAddress + "] already exists!"); + throw new AuthorizationException("Roles authorization of User[" + userAddress + "] already exists!"); } } @@ -86,6 +82,9 @@ public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleS * @return */ private long setUserRolesAuthorization(UserRoles userRoles) { + if (userRoles.getRoleCount() > MAX_ROLES_PER_USER) { + throw new AuthorizationException("The number of roles exceeds the maximum range!"); + } byte[] rolesetBytes = BinaryProtocol.encode(userRoles, RoleSet.class); return dataset.setValue(userRoles.getUserAddress(), rolesetBytes, userRoles.getVersion()); } @@ -100,7 +99,7 @@ public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleS public void updateUserRoles(UserRoles userRoles) { long nv = setUserRolesAuthorization(userRoles); if (nv < 0) { - throw new LedgerException("Update to roles of user[" + userRoles.getUserAddress() + throw new AuthorizationException("Update to roles of user[" + userRoles.getUserAddress() + "] failed due to wrong version[" + userRoles.getVersion() + "] !"); } } @@ -146,7 +145,7 @@ public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleS } @Override - public UserRoles[] getRoleAuthorizations() { + public UserRoles[] getUserRoles() { VersioningKVEntry[] kvEntries = dataset.getLatestDataEntries(0, (int) dataset.getDataCount()); UserRoles[] pns = new UserRoles[kvEntries.length]; RoleSet roleset; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java index d720aa59..7c35a267 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java @@ -5,7 +5,17 @@ import com.jd.blockchain.utils.Bytes; public interface UserRoleSettings { - long getRoleCount(); + /** + * 单一用户可被授权的角色数量的最大值; + */ + public static final int MAX_ROLES_PER_USER = 20; + + /** + * 进行了授权的用户的数量; + * + * @return + */ + long getUserCount(); /** * 加入新的用户角色授权;
        @@ -48,6 +58,11 @@ public interface UserRoleSettings { */ UserRoles getUserRoles(Bytes userAddress); - UserRoles[] getRoleAuthorizations(); + /** + * 返回全部的用户授权; + * + * @return + */ + UserRoles[] getUserRoles(); } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java index 57ec2110..9233953a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java @@ -79,6 +79,10 @@ public class UserRoles implements RoleSet { * @param roles */ public void setRoles(String[] roles) { - + TreeSet rs = new TreeSet(); + for (String r : roles) { + rs.add(r); + } + this.roles = rs; } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java index fe65af65..d7d820c4 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java @@ -1,5 +1,6 @@ package test.com.jd.blockchain.ledger; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -9,7 +10,6 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.Random; -import com.jd.blockchain.ledger.LedgerMetadata; import org.junit.Test; import com.jd.blockchain.crypto.AddressEncoding; @@ -21,15 +21,23 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.LedgerMetadata_V2; +import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminAccount; import com.jd.blockchain.ledger.core.LedgerConfiguration; +import com.jd.blockchain.ledger.core.LedgerPermission; +import com.jd.blockchain.ledger.core.RolePrivilegeSettings; +import com.jd.blockchain.ledger.core.RolePrivileges; +import com.jd.blockchain.ledger.core.RolesPolicy; +import com.jd.blockchain.ledger.core.TransactionPermission; +import com.jd.blockchain.ledger.core.UserRoleSettings; +import com.jd.blockchain.ledger.core.UserRoles; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitSettingData; import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.net.NetworkAddress; public class LedgerAdminAccountTest { @@ -40,7 +48,7 @@ public class LedgerAdminAccountTest { private Random rand = new Random(); @Test - public void test() { + public void testSerialization() { String keyPrefix = ""; LedgerInitSettingData initSetting = new LedgerInitSettingData(); ConsensusParticipantData[] parties = new ConsensusParticipantData[5]; @@ -49,7 +57,7 @@ public class LedgerAdminAccountTest { bckeys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ConsensusParticipantData(); parties[i].setId(i); - parties[i].setAddress(AddressEncoding.generateAddress(bckeys[i].getPubKey()).toBase58()); + parties[i].setAddress(AddressEncoding.generateAddress(bckeys[i].getPubKey())); parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(bckeys[i].getPubKey()); @@ -66,7 +74,6 @@ public class LedgerAdminAccountTest { for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); } - CryptoConfig cryptoSetting = new CryptoConfig(); cryptoSetting.setSupportedProviders(supportedProviders); cryptoSetting.setAutoVerifyHash(true); @@ -83,12 +90,20 @@ public class LedgerAdminAccountTest { LedgerAdminAccount ledgerAdminAccount = new LedgerAdminAccount(initSetting, keyPrefix, testStorage, testStorage); + ledgerAdminAccount.getRolePrivileges().addRolePrivilege("DEFAULT", + new LedgerPermission[] { LedgerPermission.AUTHORIZE_ROLES, LedgerPermission.REGISTER_USER, + LedgerPermission.APPROVE_TX }, + new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }); + + ledgerAdminAccount.getUserRoles().addUserRoles(parties[0].getAddress(), RolesPolicy.UNION, "DEFAULT"); + // New created instance is updated until being committed; assertTrue(ledgerAdminAccount.isUpdated()); // Hash of account is null until being committed; assertNull(ledgerAdminAccount.getHash()); - LedgerMetadata meta = ledgerAdminAccount.getMetadata(); + LedgerMetadata_V2 meta = ledgerAdminAccount.getMetadata(); assertNull(meta.getParticipantsHash()); // Commit, and check the storage keys; @@ -101,83 +116,145 @@ public class LedgerAdminAccountTest { meta = ledgerAdminAccount.getMetadata(); assertNotNull(meta.getParticipantsHash()); + assertNotNull(meta.getSettingsHash()); + assertNotNull(meta.getRolePrivilegesHash()); + assertNotNull(meta.getUserRolesHash()); + + assertNotNull(ledgerAdminAccount.getRolePrivileges().getRolePrivilege("DEFAULT")); // ---------------------- // Reload account from storage with readonly mode, and check the integrity of // data; HashDigest adminAccHash = ledgerAdminAccount.getHash(); - LedgerAdminAccount reloadAdminAccount = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, + LedgerAdminAccount reloadAdminAccount1 = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, testStorage, true); - + + LedgerMetadata_V2 meta2 = reloadAdminAccount1.getMetadata(); + assertNotNull(meta2.getParticipantsHash()); + assertNotNull(meta2.getSettingsHash()); + assertNotNull(meta2.getRolePrivilegesHash()); + assertNotNull(meta2.getUserRolesHash()); + // verify realod settings of admin account; - verifyRealoadingSettings(reloadAdminAccount, adminAccHash, ledgerAdminAccount.getMetadata()); - + verifyRealoadingSettings(reloadAdminAccount1, adminAccHash, ledgerAdminAccount.getMetadata(), + ledgerAdminAccount.getSettings()); // verify the consensus participant list; - verifyReadlingParities(reloadAdminAccount, parties1); - + verifyRealoadingParities(reloadAdminAccount1, parties1); // It will throw exeception because of this account is readonly; - verifyReadonlyState(reloadAdminAccount); + verifyReadonlyState(reloadAdminAccount1); + + verifyRealoadingRoleAuthorizations(reloadAdminAccount1, ledgerAdminAccount.getRolePrivileges(), + ledgerAdminAccount.getUserRoles()); // -------------- - // reload again with writing mode; - reloadAdminAccount = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, testStorage, false); - LedgerConfiguration newSetting = new LedgerConfiguration(reloadAdminAccount.getPreviousSetting()); + // 重新加载,并进行修改; + LedgerAdminAccount reloadAdminAccount2 = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, testStorage, false); + LedgerConfiguration newSetting = new LedgerConfiguration(reloadAdminAccount2.getPreviousSetting()); byte[] newCsSettingBytes = new byte[64]; rand.nextBytes(newCsSettingBytes); newSetting.setConsensusSetting(new Bytes(newCsSettingBytes)); newSetting.getCryptoSetting().setAutoVerifyHash(false); - reloadAdminAccount.setLedgerSetting(newSetting); + reloadAdminAccount2.setLedgerSetting(newSetting); + + reloadAdminAccount2.addParticipant(parties[4]); + + reloadAdminAccount2.getRolePrivileges().addRolePrivilege("ADMIN", + new LedgerPermission[] { LedgerPermission.APPROVE_TX }, + new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION }); + + reloadAdminAccount2.getRolePrivileges().disablePermissions("DEFAULT", TransactionPermission.CONTRACT_OPERATION); + + reloadAdminAccount2.getUserRoles().addUserRoles(parties[1].getAddress(), RolesPolicy.UNION, "DEFAULT", "ADMIN"); - reloadAdminAccount.addParticipant(parties[4]); - reloadAdminAccount.commit(); + reloadAdminAccount2.commit(); + + LedgerSettings newlyLedgerSettings = reloadAdminAccount2.getSettings(); // record the new account hash; - HashDigest newAccHash = reloadAdminAccount.getHash(); - LedgerMetadata newMeta = reloadAdminAccount.getMetadata(); + HashDigest newAccHash = reloadAdminAccount2.getHash(); + LedgerMetadata_V2 newMeta = reloadAdminAccount2.getMetadata(); // load the last version of account and verify again; - reloadAdminAccount = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, testStorage, true); - verifyRealoadingSettings(reloadAdminAccount, adminAccHash, ledgerAdminAccount.getMetadata()); - verifyReadlingParities(reloadAdminAccount, parties1); - verifyReadonlyState(reloadAdminAccount); + LedgerAdminAccount previousAdminAccount = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, + testStorage, true); + verifyRealoadingSettings(previousAdminAccount, adminAccHash, ledgerAdminAccount.getMetadata(), + ledgerAdminAccount.getSettings()); + verifyRealoadingParities(previousAdminAccount, parties1); + verifyReadonlyState(previousAdminAccount); // load the hash of new committing; - reloadAdminAccount = new LedgerAdminAccount(newAccHash, keyPrefix, testStorage, testStorage, true); - verifyRealoadingSettings(reloadAdminAccount, newAccHash, newMeta); - verifyReadlingParities(reloadAdminAccount, parties); - verifyReadonlyState(reloadAdminAccount); - - // System.out.println("========= [LedgerAdminAccount Test] Show generated - // storage keys... ======="); - // testStorage.printStoragedKeys(); + LedgerAdminAccount newlyAdminAccount = new LedgerAdminAccount(newAccHash, keyPrefix, testStorage, testStorage, + true); + verifyRealoadingSettings(newlyAdminAccount, newAccHash, newMeta, newlyLedgerSettings); + verifyRealoadingParities(newlyAdminAccount, parties); + verifyReadonlyState(newlyAdminAccount); + +// System.out.println("========= [LedgerAdminAccount Test] Show generated storage keys... ======="); +// testStorage.printStoragedKeys(); } - private void verifyRealoadingSettings(LedgerAdminAccount actualAccount, HashDigest expHash, - LedgerMetadata expMeta) { + private void verifyRealoadingSettings(LedgerAdminAccount actualAccount, HashDigest expAccRootHash, + LedgerMetadata_V2 expMeta, LedgerSettings expLedgerSettings) { // 验证基本信息; assertFalse(actualAccount.isUpdated()); assertTrue(actualAccount.isReadonly()); - assertEquals(expHash, actualAccount.getHash()); + assertEquals(expAccRootHash, actualAccount.getHash()); // verify metadata; - LedgerMetadata rlmeta = actualAccount.getMetadata(); - assertEquals(expMeta.getParticipantsHash(), rlmeta.getParticipantsHash()); - - assertTrue(BytesUtils.equals(expMeta.getSeed(), rlmeta.getSeed())); - - assertNotNull(rlmeta.getSettingsHash()); - assertEquals(expMeta.getSettingsHash(), rlmeta.getSettingsHash()); -// assertTrue(expMeta.getSettings().getConsensusSetting().equals(rlmeta.getSettings().getConsensusSetting())); -// assertEquals(expMeta.getSettings().getConsensusProvider(), rlmeta.getSettings().getConsensusProvider()); -// -// assertEquals(expMeta.getSettings().getCryptoSetting().getAutoVerifyHash(), -// rlmeta.getSettings().getCryptoSetting().getAutoVerifyHash()); -// assertEquals(expMeta.getSettings().getCryptoSetting().getHashAlgorithm(), -// rlmeta.getSettings().getCryptoSetting().getHashAlgorithm()); + LedgerMetadata_V2 actualMeta = actualAccount.getMetadata(); + assertArrayEquals(expMeta.getSeed(), actualMeta.getSeed()); + assertEquals(expMeta.getParticipantsHash(), actualMeta.getParticipantsHash()); + assertNotNull(actualMeta.getSettingsHash()); + assertEquals(expMeta.getSettingsHash(), actualMeta.getSettingsHash()); + assertNotNull(actualMeta.getRolePrivilegesHash()); + assertEquals(expMeta.getRolePrivilegesHash(), actualMeta.getRolePrivilegesHash()); + assertNotNull(actualMeta.getUserRolesHash()); + assertEquals(expMeta.getUserRolesHash(), actualMeta.getUserRolesHash()); + + LedgerSettings actualLedgerSettings = actualAccount.getSettings(); + + assertEquals(expLedgerSettings.getConsensusSetting(), actualLedgerSettings.getConsensusSetting()); + assertEquals(expLedgerSettings.getConsensusProvider(), actualLedgerSettings.getConsensusProvider()); + + assertEquals(expLedgerSettings.getCryptoSetting().getAutoVerifyHash(), + actualLedgerSettings.getCryptoSetting().getAutoVerifyHash()); + assertEquals(expLedgerSettings.getCryptoSetting().getHashAlgorithm(), + actualLedgerSettings.getCryptoSetting().getHashAlgorithm()); + } + + private void verifyRealoadingRoleAuthorizations(LedgerAdminAccount actualAccount, + RolePrivilegeSettings expRolePrivilegeSettings, UserRoleSettings expUserRoleSettings) { + // 验证基本信息; + RolePrivilegeSettings actualRolePrivileges = actualAccount.getRolePrivileges(); + RolePrivileges[] expRPs = expRolePrivilegeSettings.getRolePrivileges(); + + assertEquals(expRPs.length, actualRolePrivileges.getRoleCount()); + + for (RolePrivileges expRP : expRPs) { + RolePrivileges actualRP = actualRolePrivileges.getRolePrivilege(expRP.getRoleName()); + assertNotNull(actualRP); + assertArrayEquals(expRP.getLedgerPrivilege().toBytes(), actualRP.getLedgerPrivilege().toBytes()); + assertArrayEquals(expRP.getTransactionPrivilege().toBytes(), actualRP.getTransactionPrivilege().toBytes()); + } + + UserRoleSettings actualUserRoleSettings = actualAccount.getUserRoles(); + UserRoles[] expUserRoles = expUserRoleSettings.getUserRoles(); + assertEquals(expUserRoles.length, actualUserRoleSettings.getUserCount()); + + for (UserRoles expUR : expUserRoles) { + UserRoles actualUR = actualAccount.getUserRoles().getUserRoles(expUR.getUserAddress()); + assertNotNull(actualUR); + assertEquals(expUR.getPolicy(), actualUR.getPolicy()); + String[] expRoles = expUR.getRoles(); + Arrays.sort(expRoles); + String[] actualRoles = actualUR.getRoles(); + Arrays.sort(actualRoles); + assertArrayEquals(expRoles, actualRoles); + } } - private void verifyReadlingParities(LedgerAdminAccount actualAccount, ParticipantNode[] expParties) { + private void verifyRealoadingParities(LedgerAdminAccount actualAccount, ParticipantNode[] expParties) { assertEquals(expParties.length, actualAccount.getParticipantCount()); ParticipantNode[] actualPaticipants = actualAccount.getParticipants(); assertEquals(expParties.length, actualPaticipants.length); @@ -191,11 +268,16 @@ public class LedgerAdminAccountTest { } } - private void verifyReadonlyState(LedgerAdminAccount actualAccount) { + /** + * 验证指定账户是否只读; + * + * @param readonlyAccount + */ + private void verifyReadonlyState(LedgerAdminAccount readonlyAccount) { ConsensusParticipantData newParti = new ConsensusParticipantData(); - newParti.setId((int) actualAccount.getParticipantCount()); + newParti.setId((int) readonlyAccount.getParticipantCount()); newParti.setHostAddress( - new NetworkAddress("192.168.10." + (10 + newParti.getAddress()), 10010 + 10 * newParti.getId())); + new NetworkAddress("192.168.10." + (10 + newParti.getId()), 10010 + 10 * newParti.getId())); newParti.setName("Participant[" + newParti.getAddress() + "]"); BlockchainKeypair newKey = BlockchainKeyGenerator.getInstance().generate(); @@ -203,7 +285,7 @@ public class LedgerAdminAccountTest { Throwable ex = null; try { - actualAccount.addParticipant(newParti); + readonlyAccount.addParticipant(newParti); } catch (Exception e) { ex = e; } @@ -211,8 +293,8 @@ public class LedgerAdminAccountTest { ex = null; try { - LedgerConfiguration newLedgerSetting = new LedgerConfiguration(actualAccount.getSettings()); - actualAccount.setLedgerSetting(newLedgerSetting); + LedgerConfiguration newLedgerSetting = new LedgerConfiguration(readonlyAccount.getSettings()); + readonlyAccount.setLedgerSetting(newLedgerSetting); } catch (Exception e) { ex = e; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java index 9cee71df..483f7019 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java @@ -76,7 +76,7 @@ public class LedgerInitOperationTest { keys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ConsensusParticipantData(); // parties[i].setId(i); - parties[i].setAddress(AddressEncoding.generateAddress(keys[i].getPubKey()).toBase58()); + parties[i].setAddress(AddressEncoding.generateAddress(keys[i].getPubKey())); parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(keys[i].getPubKey()); @@ -117,7 +117,7 @@ public class LedgerInitOperationTest { for (int i = 0; i < parties.length; i++) { keys[i] = BlockchainKeyGenerator.getInstance().generate(); - parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()).toBase58(), + parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()), "Participant[" + i + "]", keys[i].getPubKey()); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java index f867ae9b..a06b3ed1 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java @@ -76,7 +76,7 @@ public class LedgerInitSettingSerializeTest { keys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ConsensusParticipantData(); // parties[i].setId(i); - parties[i].setAddress(AddressEncoding.generateAddress(keys[i].getPubKey()).toBase58()); + parties[i].setAddress(AddressEncoding.generateAddress(keys[i].getPubKey())); parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(keys[i].getPubKey()); @@ -84,7 +84,7 @@ public class LedgerInitSettingSerializeTest { ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); ledgerInitSettingData.setConsensusParticipants(parties1); - + byte[] encode = BinaryProtocol.encode(ledgerInitSettingData, LedgerInitSetting.class); LedgerInitSetting decode = BinaryProtocol.decode(encode); @@ -121,7 +121,7 @@ public class LedgerInitSettingSerializeTest { for (int i = 0; i < parties.length; i++) { keys[i] = BlockchainKeyGenerator.getInstance().generate(); - parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()).toBase58(), + parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()), "Participant[" + i + "]", keys[i].getPubKey()); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java index dfc17f24..f2021d99 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java @@ -209,7 +209,7 @@ public class LedgerManagerTest { parties[0].setName("John"); AsymmetricKeypair kp0 = signatureFunction.generateKeypair(); parties[0].setPubKey(kp0.getPubKey()); - parties[0].setAddress(AddressEncoding.generateAddress(kp0.getPubKey()).toBase58()); + parties[0].setAddress(AddressEncoding.generateAddress(kp0.getPubKey())); parties[0].setHostAddress(new NetworkAddress("127.0.0.1", 9000)); parties[1] = new ConsensusParticipantData(); @@ -217,7 +217,7 @@ public class LedgerManagerTest { parties[1].setName("Mary"); AsymmetricKeypair kp1 = signatureFunction.generateKeypair(); parties[1].setPubKey(kp1.getPubKey()); - parties[1].setAddress(AddressEncoding.generateAddress(kp1.getPubKey()).toBase58()); + parties[1].setAddress(AddressEncoding.generateAddress(kp1.getPubKey())); parties[1].setHostAddress(new NetworkAddress("127.0.0.1", 9010)); parties[2] = new ConsensusParticipantData(); @@ -225,7 +225,7 @@ public class LedgerManagerTest { parties[2].setName("Jerry"); AsymmetricKeypair kp2 = signatureFunction.generateKeypair(); parties[2].setPubKey(kp2.getPubKey()); - parties[2].setAddress(AddressEncoding.generateAddress(kp2.getPubKey()).toBase58()); + parties[2].setAddress(AddressEncoding.generateAddress(kp2.getPubKey())); parties[2].setHostAddress(new NetworkAddress("127.0.0.1", 9020)); parties[3] = new ConsensusParticipantData(); @@ -233,7 +233,7 @@ public class LedgerManagerTest { parties[3].setName("Tom"); AsymmetricKeypair kp3 = signatureFunction.generateKeypair(); parties[3].setPubKey(kp3.getPubKey()); - parties[3].setAddress(AddressEncoding.generateAddress(kp3.getPubKey()).toBase58()); + parties[3].setAddress(AddressEncoding.generateAddress(kp3.getPubKey())); parties[3].setHostAddress(new NetworkAddress("127.0.0.1", 9030)); initSetting.setConsensusParticipants(parties); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java index d6a16e5d..54eeab93 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java @@ -78,7 +78,7 @@ public class LedgerMetaDataTest { // LedgerConfiguration ledgerConfiguration = new LedgerConfiguration(consensusProvider, // new Bytes(consensusSettingBytes), cryptoConfig); HashDigest settingsHash = Crypto.getHashFunction("SHA256").hash(consensusSettingBytes); - + LedgerAdminAccount.LedgerMetadataImpl ledgerMetadata = new LedgerAdminAccount.LedgerMetadataImpl(); ledgerMetadata.setSeed(seed); @@ -188,7 +188,7 @@ public class LedgerMetaDataTest { String name = "John"; // NetworkAddress consensusAddress = new NetworkAddress("192.168.1.1", 9001, // false); - String address = AddressEncoding.generateAddress(pubKey).toBase58(); + Bytes address = AddressEncoding.generateAddress(pubKey); ParticipantCertData participantCertData = new ParticipantCertData(address, name, pubKey); // encode and decode diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java index 6101cdba..1625411b 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java @@ -72,7 +72,7 @@ public class LedgerTestUtils { parties[i].setId(0); parties[i].setName("Parti-" + i); parties[i].setPubKey(partiKeys[i].getPubKey()); - parties[i].setAddress(AddressEncoding.generateAddress(partiKeys[i].getPubKey()).toBase58()); + parties[i].setAddress(AddressEncoding.generateAddress(partiKeys[i].getPubKey())); parties[i].setHostAddress(new NetworkAddress("192.168.1." + (10 + i), 9000)); } @@ -125,13 +125,13 @@ public class LedgerTestUtils { return txReqBuilder.buildRequest(); } - - public static TransactionRequest createTxRequest_DataAccountReg(BlockchainKeypair dataAccountID, HashDigest ledgerHash, - BlockchainKeypair nodeKeypair, BlockchainKeypair... signers) { + + public static TransactionRequest createTxRequest_DataAccountReg(BlockchainKeypair dataAccountID, + HashDigest ledgerHash, BlockchainKeypair nodeKeypair, BlockchainKeypair... signers) { TxBuilder txBuilder = new TxBuilder(ledgerHash); - + txBuilder.dataAccounts().register(dataAccountID.getIdentity()); - + TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); if (signers != null) { for (BlockchainKeypair signer : signers) { @@ -141,16 +141,17 @@ public class LedgerTestUtils { if (nodeKeypair != null) { txReqBuilder.signAsNode(nodeKeypair); } - + return txReqBuilder.buildRequest(); } - - public static TransactionRequest createTxRequest_DataAccountWrite(Bytes dataAccountAddress, String key, String value, long version, HashDigest ledgerHash, - BlockchainKeypair nodeKeypair, BlockchainKeypair... signers) { + + public static TransactionRequest createTxRequest_DataAccountWrite(Bytes dataAccountAddress, String key, + String value, long version, HashDigest ledgerHash, BlockchainKeypair nodeKeypair, + BlockchainKeypair... signers) { TxBuilder txBuilder = new TxBuilder(ledgerHash); - + txBuilder.dataAccount(dataAccountAddress).setText(key, value, version); - + TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); if (signers != null) { for (BlockchainKeypair signer : signers) { @@ -160,7 +161,7 @@ public class LedgerTestUtils { if (nodeKeypair != null) { txReqBuilder.signAsNode(nodeKeypair); } - + return txReqBuilder.buildRequest(); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java index ce571d71..0570fee9 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java @@ -58,6 +58,18 @@ public class MerkleDataSetTest { mds.setValue("C", "C".getBytes(), -1); mds.commit(); + + byte[] va = mds.getValue("A"); + assertNotNull(va); + assertEquals("A", new String(va)); + + byte[] vc = mds.getValue("C"); + VersioningKVEntry ventry = mds.getDataEntry("C"); + assertNotNull(vc); + assertNotNull(ventry); + assertEquals("C", new String(vc)); + assertEquals("C", ventry.getKey().toUTF8String()); + HashDigest root1 = mds.getRootHash(); // 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java new file mode 100644 index 00000000..6763162c --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java @@ -0,0 +1,70 @@ +package test.com.jd.blockchain.ledger; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoProvider; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +import com.jd.blockchain.crypto.service.sm.SMCryptoService; +import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.LedgerPermission; +import com.jd.blockchain.ledger.core.RolePrivilegeDataSet; +import com.jd.blockchain.ledger.core.RolePrivileges; +import com.jd.blockchain.ledger.core.TransactionPermission; +import com.jd.blockchain.storage.service.utils.MemoryKVStorage; + +public class RolePrivilegeDataSetTest { + + private static final String[] SUPPORTED_PROVIDER_NAMES = { ClassicCryptoService.class.getName(), + SMCryptoService.class.getName() }; + + private static final CryptoAlgorithm HASH_ALGORITHM = Crypto.getAlgorithm("SHA256"); + + private static final CryptoProvider[] SUPPORTED_PROVIDERS = new CryptoProvider[SUPPORTED_PROVIDER_NAMES.length]; + static { + for (int i = 0; i < SUPPORTED_PROVIDER_NAMES.length; i++) { + SUPPORTED_PROVIDERS[i] = Crypto.getProvider(SUPPORTED_PROVIDER_NAMES[i]); + } + } + + @Test + public void testAddRolePrivilege() { + + CryptoConfig cryptoConfig = new CryptoConfig(); + cryptoConfig.setAutoVerifyHash(true); + cryptoConfig.setSupportedProviders(SUPPORTED_PROVIDERS); + cryptoConfig.setHashAlgorithm(HASH_ALGORITHM); + + MemoryKVStorage testStorage = new MemoryKVStorage(); + + String roleName = "DEFAULT"; + String prefix = "role-privilege/"; + RolePrivilegeDataSet rolePrivilegeDataset = new RolePrivilegeDataSet(cryptoConfig, prefix, testStorage, + testStorage); + rolePrivilegeDataset.addRolePrivilege(roleName, new LedgerPermission[] { LedgerPermission.REGISTER_USER }, + new TransactionPermission[] { TransactionPermission.CONTRACT_OPERATION }); + + rolePrivilegeDataset.commit(); + + RolePrivileges rolePrivilege = rolePrivilegeDataset.getRolePrivilege(roleName); + assertNotNull(rolePrivilege); + + HashDigest rootHash = rolePrivilegeDataset.getRootHash(); + RolePrivilegeDataSet newRolePrivilegeDataset = new RolePrivilegeDataSet(rootHash, cryptoConfig, prefix, + testStorage, testStorage, true); + rolePrivilege = newRolePrivilegeDataset.getRolePrivilege(roleName); + assertNotNull(rolePrivilege); + + assertTrue(rolePrivilege.getLedgerPrivilege().isEnable(LedgerPermission.REGISTER_USER)); + assertTrue(rolePrivilege.getTransactionPrivilege().isEnable(TransactionPermission.CONTRACT_OPERATION)); + + + } + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java new file mode 100644 index 00000000..41a59be1 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java @@ -0,0 +1,62 @@ +package test.com.jd.blockchain.ledger; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoProvider; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +import com.jd.blockchain.crypto.service.sm.SMCryptoService; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.RolesPolicy; +import com.jd.blockchain.ledger.core.UserRoleDataSet; +import com.jd.blockchain.ledger.core.UserRoles; +import com.jd.blockchain.storage.service.utils.MemoryKVStorage; + +public class UserRoleDataSetTest { + + private static final String[] SUPPORTED_PROVIDER_NAMES = { ClassicCryptoService.class.getName(), + SMCryptoService.class.getName() }; + + private static final CryptoAlgorithm HASH_ALGORITHM = Crypto.getAlgorithm("SHA256"); + + private static final CryptoProvider[] SUPPORTED_PROVIDERS = new CryptoProvider[SUPPORTED_PROVIDER_NAMES.length]; + static { + for (int i = 0; i < SUPPORTED_PROVIDER_NAMES.length; i++) { + SUPPORTED_PROVIDERS[i] = Crypto.getProvider(SUPPORTED_PROVIDER_NAMES[i]); + } + } + + @Test + public void testAddUserRoles() { + CryptoConfig cryptoConfig = new CryptoConfig(); + cryptoConfig.setAutoVerifyHash(true); + cryptoConfig.setSupportedProviders(SUPPORTED_PROVIDERS); + cryptoConfig.setHashAlgorithm(HASH_ALGORITHM); + + MemoryKVStorage testStorage = new MemoryKVStorage(); + String prefix = "user-roles/"; + UserRoleDataSet userRolesDataset = new UserRoleDataSet(cryptoConfig, prefix, testStorage, testStorage); + + BlockchainKeypair bckp = BlockchainKeyGenerator.getInstance().generate(); + String[] authRoles = { "DEFAULT", "MANAGER" }; + userRolesDataset.addUserRoles(bckp.getAddress(), RolesPolicy.UNION, authRoles); + + userRolesDataset.commit(); + + assertEquals(1, userRolesDataset.getUserCount()); + UserRoles userRoles = userRolesDataset.getUserRoles(bckp.getAddress()); + assertNotNull(userRoles); + String[] roles = userRoles.getRoles(); + assertEquals(2, roles.length); + assertArrayEquals(authRoles, roles); + assertEquals(RolesPolicy.UNION, userRoles.getPolicy()); + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AuthorizationException.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AuthorizationException.java new file mode 100644 index 00000000..94a374a8 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AuthorizationException.java @@ -0,0 +1,17 @@ +package com.jd.blockchain.ledger; + +public class AuthorizationException extends LedgerException { + + private static final long serialVersionUID = -4418553411943356320L; + + + + public AuthorizationException(String message) { + super(message); + } + + public AuthorizationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java index 93ef4234..4e1b5105 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java @@ -9,7 +9,7 @@ import com.jd.blockchain.consts.DataCodes; public interface LedgerAdminInfo { @DataField(order = 1, refContract = true) - LedgerMetadata getMetadata(); + LedgerMetadata_V2 getMetadata(); @DataField(order = 2, refContract = true) LedgerSettings getSettings(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java index 01ce182f..228019b7 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerMetadata_V2.java @@ -13,7 +13,7 @@ import com.jd.blockchain.crypto.HashDigest; * @author huanghaiquan * */ -@DataContract(code = DataCodes.METADATA, name = "LEDGER-METADATA-V2") +@DataContract(code = DataCodes.METADATA_V2, name = "LEDGER-METADATA-V2") public interface LedgerMetadata_V2 extends LedgerMetadata { /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java index dd2c62fa..8d4075cb 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java @@ -5,6 +5,7 @@ import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.utils.Bytes; /** * 参与方节点; @@ -30,8 +31,8 @@ public interface ParticipantNode {// extends ConsensusNode, ParticipantInfo { * * @return */ - @DataField(order = 1, primitiveType = PrimitiveType.TEXT) - String getAddress(); + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + Bytes getAddress(); /** * 参与者名称; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java index b4e64744..ed0130d1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java @@ -2,13 +2,14 @@ package com.jd.blockchain.transaction; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.net.NetworkAddress; public class ConsensusParticipantData implements ParticipantNode { private int id; - private String address; + private Bytes address; private String name; @@ -48,11 +49,11 @@ public class ConsensusParticipantData implements ParticipantNode { this.pubKey = pubKey; } - public String getAddress() { + public Bytes getAddress() { return address; } - public void setAddress(String address) { + public void setAddress(Bytes address) { this.address = address; } diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/consensus/ConsensusRealmImpl.java b/source/peer/src/main/java/com/jd/blockchain/peer/consensus/ConsensusRealmImpl.java index 108365a6..d5d5fca9 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/consensus/ConsensusRealmImpl.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/consensus/ConsensusRealmImpl.java @@ -16,7 +16,7 @@ public class ConsensusRealmImpl implements ConsensusRealm { public ConsensusRealmImpl(ParticipantNode[] nodeList) { this.nodes = nodeList; - String[] addrs = new String[nodes.length]; + Bytes[] addrs = new Bytes[nodes.length]; int i = 0; for (ParticipantNode n : nodes) { addrs[i++] = n.getAddress(); diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java index f0f904ad..86011bd7 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java @@ -8,19 +8,41 @@ */ package com.jd.blockchain.sdk.converters; +import java.lang.reflect.Field; + +import org.apache.commons.codec.binary.Base64; + import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.transaction.*; +import com.jd.blockchain.ledger.BlockchainIdentityData; +import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.BytesValueEncoding; +import com.jd.blockchain.ledger.ContractCodeDeployOperation; +import com.jd.blockchain.ledger.ContractEventSendOperation; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.DataAccountKVSetOperation; +import com.jd.blockchain.ledger.DataAccountRegisterOperation; +import com.jd.blockchain.ledger.DataType; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.transaction.ContractCodeDeployOpTemplate; +import com.jd.blockchain.transaction.ContractEventSendOpTemplate; +import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; +import com.jd.blockchain.transaction.DataAccountRegisterOpTemplate; +import com.jd.blockchain.transaction.KVData; +import com.jd.blockchain.transaction.LedgerInitOpTemplate; +import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.UserRegisterOpTemplate; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.Base58Utils; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.io.BytesUtils; -import org.apache.commons.codec.binary.Base64; - -import java.lang.reflect.Field; /** * @@ -31,347 +53,348 @@ import java.lang.reflect.Field; public class ClientResolveUtil { - public static KVDataEntry[] read(KVDataEntry[] kvDataEntries) { - if (kvDataEntries == null || kvDataEntries.length == 0) { - return kvDataEntries; - } - KVDataEntry[] resolveKvDataEntries = new KVDataEntry[kvDataEntries.length]; - // kvDataEntries是代理对象,需要处理 - for (int i = 0; i < kvDataEntries.length; i++) { - KVDataEntry kvDataEntry = kvDataEntries[i]; - String key = kvDataEntry.getKey(); - long version = kvDataEntry.getVersion(); - DataType dataType = kvDataEntry.getType(); - KvData innerKvData = new KvData(key, version, dataType); - Object valueObj = kvDataEntry.getValue(); - switch (dataType) { - case NIL: - break; - case BYTES: - case TEXT: - case JSON: - innerKvData.setValue(valueObj.toString()); - break; - case INT32: - innerKvData.setValue(Integer.parseInt(valueObj.toString())); - break; - case INT64: - innerKvData.setValue(Long.parseLong(valueObj.toString())); - break; - default: - throw new IllegalStateException("Unsupported value type[" + dataType + "] to resolve!"); - } - resolveKvDataEntries[i] = innerKvData; - } - return resolveKvDataEntries; - } - - public static Operation read(Operation operation) { - - try { - // Class - Class clazz = operation.getClass(); - Field field = clazz.getSuperclass().getDeclaredField("h"); - field.setAccessible(true); - Object object = field.get(operation); - if (object instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) object; - if (jsonObject.containsKey("accountID")) { - return convertDataAccountRegisterOperation(jsonObject); - } else if (jsonObject.containsKey("userID")) { - return convertUserRegisterOperation(jsonObject); - } else if (jsonObject.containsKey("contractID")) { - return convertContractCodeDeployOperation(jsonObject); - } else if (jsonObject.containsKey("writeSet")) { - return convertDataAccountKVSetOperation(jsonObject); - } else if (jsonObject.containsKey("initSetting")) { - return convertLedgerInitOperation(jsonObject); - } else if (jsonObject.containsKey("contractAddress")) { - return convertContractEventSendOperation(jsonObject); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - return null; - } - - public static Object readValueByBytesValue(BytesValue bytesValue) { - DataType dataType = bytesValue.getType(); - Bytes saveVal = bytesValue.getValue(); - Object showVal; - switch (dataType) { - case BYTES: - // return hex - showVal = HexUtils.encode(saveVal.toBytes()); - break; - case TEXT: - case JSON: - showVal = saveVal.toUTF8String(); - break; - case INT64: - showVal = BytesUtils.toLong(saveVal.toBytes()); - break; - default: - showVal = HexUtils.encode(saveVal.toBytes()); - break; - } - return showVal; - } - - public static DataAccountRegisterOperation convertDataAccountRegisterOperation(JSONObject jsonObject) { - JSONObject account = jsonObject.getJSONObject("accountID"); - return new DataAccountRegisterOpTemplate(blockchainIdentity(account)); - } - - public static DataAccountKVSetOperation convertDataAccountKVSetOperation(JSONObject jsonObject) { - // 写入集合处理 - JSONArray writeSetObj = jsonObject.getJSONArray("writeSet"); - JSONObject accountAddrObj = jsonObject.getJSONObject("accountAddress"); - String addressBase58 = accountAddrObj.getString("value"); - Bytes address = Bytes.fromBase58(addressBase58); - - DataAccountKVSetOpTemplate kvOperation = new DataAccountKVSetOpTemplate(address); - for (int i = 0; i clazz = operation.getClass(); + Field field = clazz.getSuperclass().getDeclaredField("h"); + field.setAccessible(true); + Object object = field.get(operation); + if (object instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) object; + if (jsonObject.containsKey("accountID")) { + return convertDataAccountRegisterOperation(jsonObject); + } else if (jsonObject.containsKey("userID")) { + return convertUserRegisterOperation(jsonObject); + } else if (jsonObject.containsKey("contractID")) { + return convertContractCodeDeployOperation(jsonObject); + } else if (jsonObject.containsKey("writeSet")) { + return convertDataAccountKVSetOperation(jsonObject); + } else if (jsonObject.containsKey("initSetting")) { + return convertLedgerInitOperation(jsonObject); + } else if (jsonObject.containsKey("contractAddress")) { + return convertContractEventSendOperation(jsonObject); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + return null; + } + + public static Object readValueByBytesValue(BytesValue bytesValue) { + DataType dataType = bytesValue.getType(); + Bytes saveVal = bytesValue.getValue(); + Object showVal; + switch (dataType) { + case BYTES: + // return hex + showVal = HexUtils.encode(saveVal.toBytes()); + break; + case TEXT: + case JSON: + showVal = saveVal.toUTF8String(); + break; + case INT64: + showVal = BytesUtils.toLong(saveVal.toBytes()); + break; + default: + showVal = HexUtils.encode(saveVal.toBytes()); + break; + } + return showVal; + } + + public static DataAccountRegisterOperation convertDataAccountRegisterOperation(JSONObject jsonObject) { + JSONObject account = jsonObject.getJSONObject("accountID"); + return new DataAccountRegisterOpTemplate(blockchainIdentity(account)); + } + + public static DataAccountKVSetOperation convertDataAccountKVSetOperation(JSONObject jsonObject) { + // 写入集合处理 + JSONArray writeSetObj = jsonObject.getJSONArray("writeSet"); + JSONObject accountAddrObj = jsonObject.getJSONObject("accountAddress"); + String addressBase58 = accountAddrObj.getString("value"); + Bytes address = Bytes.fromBase58(addressBase58); + + DataAccountKVSetOpTemplate kvOperation = new DataAccountKVSetOpTemplate(address); + for (int i = 0; i < writeSetObj.size(); i++) { + JSONObject currWriteSetObj = writeSetObj.getJSONObject(i); + long expectedVersion = currWriteSetObj.getLong("expectedVersion"); + JSONObject valueObj = currWriteSetObj.getJSONObject("value"); + String typeStr = valueObj.getString("type"); + String realValBase58 = valueObj.getString("value"); + String key = currWriteSetObj.getString("key"); + DataType dataType = DataType.valueOf(typeStr); + BytesValue bytesValue = BytesData.fromType(dataType, Base58Utils.decode(realValBase58)); + KVData kvData = new KVData(key, bytesValue, expectedVersion); + kvOperation.set(kvData); + } + + return kvOperation; + } + + public static LedgerInitOperation convertLedgerInitOperation(JSONObject jsonObject) { + JSONObject legerInitObj = jsonObject.getJSONObject("initSetting"); + LedgerInitSettingData ledgerInitSettingData = new LedgerInitSettingData(); + String ledgerSeedStr = legerInitObj.getString("ledgerSeed"); + + // 种子需要做Base64转换 + ledgerInitSettingData.setLedgerSeed(Base64.decodeBase64(BytesUtils.toBytes(ledgerSeedStr))); + + String consensusProvider = legerInitObj.getString("consensusProvider"); + + ledgerInitSettingData.setConsensusProvider(consensusProvider); + + JSONObject cryptoSettingObj = legerInitObj.getJSONObject("cryptoSetting"); + boolean autoVerifyHash = cryptoSettingObj.getBoolean("autoVerifyHash"); + short hashAlgorithm = cryptoSettingObj.getShort("hashAlgorithm"); + + CryptoConfig cryptoConfig = new CryptoConfig(); + + cryptoConfig.setAutoVerifyHash(autoVerifyHash); + + cryptoConfig.setHashAlgorithm(hashAlgorithm); + + ledgerInitSettingData.setCryptoSetting(cryptoConfig); + + JSONObject consensusSettingsObj = legerInitObj.getJSONObject("consensusSettings"); + Bytes consensusSettings = Bytes.fromBase58(consensusSettingsObj.getString("value")); + + ledgerInitSettingData.setConsensusSettings(consensusSettings); + + JSONArray consensusParticipantsArray = legerInitObj.getJSONArray("consensusParticipants"); + + if (!consensusParticipantsArray.isEmpty()) { + ParticipantNode[] participantNodes = new ParticipantNode[consensusParticipantsArray.size()]; + for (int i = 0; i < consensusParticipantsArray.size(); i++) { + JSONObject currConsensusParticipant = consensusParticipantsArray.getJSONObject(i); + String addressBase58 = currConsensusParticipant.getString("address"); + String name = currConsensusParticipant.getString("name"); + int id = currConsensusParticipant.getInteger("id"); + JSONObject pubKeyObj = currConsensusParticipant.getJSONObject("pubKey"); + String pubKeyBase58 = pubKeyObj.getString("value"); + // 生成ParticipantNode对象 + ParticipantCertData participantCertData = new ParticipantCertData(id, Bytes.fromBase58(addressBase58), + name, new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes())); + participantNodes[i] = participantCertData; + } + ledgerInitSettingData.setConsensusParticipants(participantNodes); + } + + return new LedgerInitOpTemplate(ledgerInitSettingData); + } + + public static UserRegisterOperation convertUserRegisterOperation(JSONObject jsonObject) { + JSONObject user = jsonObject.getJSONObject("userID"); + return new UserRegisterOpTemplate(blockchainIdentity(user)); + } + + public static ContractCodeDeployOperation convertContractCodeDeployOperation(JSONObject jsonObject) { + JSONObject contract = jsonObject.getJSONObject("contractID"); + BlockchainIdentityData blockchainIdentity = blockchainIdentity(contract); + + String chainCodeStr = jsonObject.getString("chainCode"); + ContractCodeDeployOpTemplate contractCodeDeployOpTemplate = new ContractCodeDeployOpTemplate(blockchainIdentity, + BytesUtils.toBytes(chainCodeStr)); + return contractCodeDeployOpTemplate; + } + + public static ContractEventSendOperation convertContractEventSendOperation(JSONObject jsonObject) { + JSONObject contractAddressObj = jsonObject.getJSONObject("contractAddress"); + String contractAddress = contractAddressObj.getString("value"); + String argsStr = jsonObject.getString("args"); + String event = jsonObject.getString("event"); + return new ContractEventSendOpTemplate(Bytes.fromBase58(contractAddress), event, + BytesValueEncoding.encodeArray(new Object[] { argsStr }, null)); + } + + private static BlockchainIdentityData blockchainIdentity(JSONObject jsonObject) { + JSONObject addressObj = jsonObject.getJSONObject("address"); + // base58值 + String addressBase58 = addressObj.getString("value"); + Bytes address = Bytes.fromBase58(addressBase58); + + JSONObject pubKeyObj = jsonObject.getJSONObject("pubKey"); + // base58值 + String pubKeyBase58 = pubKeyObj.getString("value"); + PubKey pubKey = new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes()); + + // 生成对应的对象 + return new BlockchainIdentityData(address, pubKey); + } + + public static class CryptoConfig implements CryptoSetting { + + private short hashAlgorithm; + + private boolean autoVerifyHash; + + @Override + public CryptoProvider[] getSupportedProviders() { + return new CryptoProvider[0]; + } + + @Override + public short getHashAlgorithm() { + return hashAlgorithm; + } + + @Override + public boolean getAutoVerifyHash() { + return autoVerifyHash; + } + + public void setHashAlgorithm(short hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + } + + public void setAutoVerifyHash(boolean autoVerifyHash) { + this.autoVerifyHash = autoVerifyHash; + } + } + + public static class ParticipantCertData implements ParticipantNode { + private int id; + private Bytes address; + private String name; + private PubKey pubKey; + + public ParticipantCertData() { + } + + public ParticipantCertData(ParticipantNode participantNode) { + this.address = participantNode.getAddress(); + this.name = participantNode.getName(); + this.pubKey = participantNode.getPubKey(); + } + + public ParticipantCertData(int id, Bytes address, String name, PubKey pubKey) { + this.id = id; + this.address = address; + this.name = name; + this.pubKey = pubKey; + } + + @Override + public Bytes getAddress() { + return address; + } + + @Override + public String getName() { + return name; + } + + @Override + public PubKey getPubKey() { + return pubKey; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + } + + public static class KvData implements KVDataEntry { + + private String key; + + private long version; + + private DataType dataType; + + private Object value; + + public KvData() { + } + + public KvData(String key, long version, DataType dataType) { + this(key, version, dataType, null); + } + + public KvData(String key, long version, DataType dataType, Object value) { + this.key = key; + this.version = version; + this.dataType = dataType; + this.value = value; + } + + public void setKey(String key) { + this.key = key; + } + + public void setVersion(long version) { + this.version = version; + } + + public void setDataType(DataType dataType) { + this.dataType = dataType; + } + + public void setValue(Object value) { + this.value = value; + } + + @Override + public String getKey() { + return key; + } + + @Override + public long getVersion() { + return version; + } + + @Override + public DataType getType() { + return dataType; + } + + @Override + public Object getValue() { + return value; + } + } } \ No newline at end of file diff --git a/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/MemoryKVStorage.java b/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/MemoryKVStorage.java index 65920967..0c2192a3 100644 --- a/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/MemoryKVStorage.java +++ b/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/MemoryKVStorage.java @@ -12,11 +12,6 @@ import com.jd.blockchain.utils.io.BytesMap; public class MemoryKVStorage implements ExPolicyKVStorage, VersioningKVStorage, KVStorageService, BytesMap { -// private Set keys = new HashSet<>(); - -// private Set keys = Collections.synchronizedSet(new HashSet<>()); - -// private Map storageMap = new ConcurrentHashMap<>(); private ExistancePolicyKVStorageMap exStorage = new ExistancePolicyKVStorageMap(); private VersioningKVStorageMap verStorage = new VersioningKVStorageMap(); @@ -38,10 +33,6 @@ public class MemoryKVStorage implements ExPolicyKVStorage, VersioningKVStorage, @Override public long set(Bytes key, byte[] value, long version) { return verStorage.set(key, value, version); -// if (v > -1) { -// keys.add(key); -// } -// return v; } @Override @@ -57,10 +48,6 @@ public class MemoryKVStorage implements ExPolicyKVStorage, VersioningKVStorage, @Override public boolean set(Bytes key, byte[] value, ExPolicy ex) { return exStorage.set(key, value, ex); -// if (ok) { -// keys.add(key); -// } -// return ok; } @Override @@ -81,12 +68,10 @@ public class MemoryKVStorage implements ExPolicyKVStorage, VersioningKVStorage, HashSet keySet = new HashSet<>(exStorage.keySet()); keySet.addAll(verStorage.keySet()); return keySet; -// return storageMap.keySet(); } public int getStorageCount() { return exStorage.getCount() + verStorage.getCount(); -// return storageMap.size(); } // public void printStoragedKeys() { diff --git a/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVData.java b/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVData.java new file mode 100644 index 00000000..684c75fe --- /dev/null +++ b/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVData.java @@ -0,0 +1,35 @@ +package com.jd.blockchain.storage.service.utils; + +import com.jd.blockchain.storage.service.VersioningKVEntry; +import com.jd.blockchain.utils.Bytes; + +public class VersioningKVData implements VersioningKVEntry { + + private Bytes key; + + private long version; + + private byte[] value; + + public VersioningKVData(Bytes key, long version, byte[] value) { + this.key = key; + this.version = version; + this.value = value; + } + + @Override + public Bytes getKey() { + return key; + } + + @Override + public long getVersion() { + return version; + } + + @Override + public byte[] getValue() { + return value; + } + + } \ No newline at end of file diff --git a/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVStorageMap.java b/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVStorageMap.java index 2569007f..3c4855e3 100644 --- a/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVStorageMap.java +++ b/source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/utils/VersioningKVStorageMap.java @@ -215,38 +215,6 @@ public class VersioningKVStorageMap implements VersioningKVStorage, BytesMap Date: Wed, 21 Aug 2019 23:52:15 +0800 Subject: [PATCH 044/124] rename; --- .../ledger/core/AccountPrivilege.java | 26 ------ .../blockchain/ledger/core/Authorization.java | 13 --- .../jd/blockchain/ledger/core/Gateway.java | 22 ----- .../ledger/core/LedgerAdminAccount.java | 85 ++++++------------- ...ntDataSet.java => ParticipantDataset.java} | 6 +- ...DataSet.java => RolePrivilegeDataset.java} | 6 +- ...rRoleDataSet.java => UserRoleDataset.java} | 14 ++- .../blockchain/ledger/LedgerMetaDataTest.java | 2 +- ...est.java => RolePrivilegeDatasetTest.java} | 9 +- ...aSetTest.java => UserRoleDatasetTest.java} | 6 +- .../jd/blockchain/ledger/ParticipantNode.java | 2 +- 11 files changed, 49 insertions(+), 142 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountPrivilege.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Gateway.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{ParticipantDataSet.java => ParticipantDataset.java} (93%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{RolePrivilegeDataSet.java => RolePrivilegeDataset.java} (97%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{UserRoleDataSet.java => UserRoleDataset.java} (94%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{RolePrivilegeDataSetTest.java => RolePrivilegeDatasetTest.java} (87%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{UserRoleDataSetTest.java => UserRoleDatasetTest.java} (93%) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountPrivilege.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountPrivilege.java deleted file mode 100644 index c57debff..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountPrivilege.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.jd.blockchain.ledger.core; - -public interface AccountPrivilege { - - /** - * 数据“读”的操作码; - * - * @return - */ - byte getReadingOpCode(); - - /** - * “写”的操作码; - * - * @return - */ - byte getWrittingOpCode(); - - /** - * 其它的扩展操作码; - * - * @return - */ - byte[] getExtOpCodes(); - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java deleted file mode 100644 index 44c86d57..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Authorization.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.jd.blockchain.ledger.core; - -/** - * {@link Authorization} 抽象了对特定用户/角色的授权信息; - * - * @author huanghaiquan - * - */ -public class Authorization { - - - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Gateway.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Gateway.java deleted file mode 100644 index 6874d8aa..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Gateway.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.ledger.ParticipantNode; - -/** - * @author hhq - * @version 1.0 - * @created 14-6��-2018 12:13:32 - */ -public class Gateway extends Node { - - public ParticipantNode m_Participant; - - public Gateway(){ - - } - - public void finalize() throws Throwable { - super.finalize(); - } - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java index e120525c..0bb3b75a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java @@ -38,12 +38,10 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { private final Bytes metaPrefix; private final Bytes settingPrefix; - private final Bytes rolePrivilegePrefix; - private final Bytes userRolePrefix; private LedgerMetadata_V2 origMetadata; - private LedgerMetadataImpl metadata; + private LedgerMetadataInfo metadata; /** * 原来的账本设置; @@ -58,22 +56,23 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { /** * 账本的参与节点; */ - private ParticipantDataSet participants; + private ParticipantDataset participants; - private RolePrivilegeDataSet rolePrivileges; + /** + * “角色-权限”数据集; + */ + private RolePrivilegeDataset rolePrivileges; - private UserRoleDataSet userRoles; + /** + * “用户-角色”数据集; + */ + private UserRoleDataset userRoles; /** * 账本参数配置; */ private LedgerSettings settings; - // /** - // * 账本的全局权限设置; - // */ - // private PrivilegeDataSet privileges; - private ExPolicyKVStorage storage; private HashDigest adminAccountHash; @@ -115,25 +114,14 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { VersioningKVStorage versioningStorage) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); - this.rolePrivilegePrefix = Bytes.fromString(keyPrefix + ROLE_PRIVILEGE_PREFIX); - this.userRolePrefix = Bytes.fromString(keyPrefix + USER_ROLE_PREFIX); ParticipantNode[] parties = initSetting.getConsensusParticipants(); if (parties.length == 0) { throw new LedgerException("No participant!"); } - // 检查参与者列表是否已经按照 id 升序排列,并且 id 不冲突; - // 注:参与者的 id 要求从 0 开始编号,顺序依次递增,不允许跳空; - for (int i = 0; i < parties.length; i++) { - // if (parties[i].getAddress() != i) { - // throw new LedgerException("The id of participant isn't match the order of the - // participant list!"); - // } - } - // 初始化元数据; - this.metadata = new LedgerMetadataImpl(); + this.metadata = new LedgerMetadataInfo(); this.metadata.setSeed(initSetting.getLedgerSeed()); // 新配置; this.settings = new LedgerConfiguration(initSetting.getConsensusProvider(), initSetting.getConsensusSettings(), @@ -144,7 +132,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { // 基于原配置初始化参与者列表; String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataSet(previousSettings.getCryptoSetting(), partiPrefix, exPolicyStorage, + this.participants = new ParticipantDataset(previousSettings.getCryptoSetting(), partiPrefix, exPolicyStorage, versioningStorage); for (ParticipantNode p : parties) { @@ -152,11 +140,11 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { } String rolePrivilegePrefix = keyPrefix + ROLE_PRIVILEGE_PREFIX; - this.rolePrivileges = new RolePrivilegeDataSet(this.settings.getCryptoSetting(), rolePrivilegePrefix, + this.rolePrivileges = new RolePrivilegeDataset(this.settings.getCryptoSetting(), rolePrivilegePrefix, exPolicyStorage, versioningStorage); String userRolePrefix = keyPrefix + USER_ROLE_PREFIX; - this.userRoles = new UserRoleDataSet(this.settings.getCryptoSetting(), userRolePrefix, exPolicyStorage, + this.userRoles = new UserRoleDataset(this.settings.getCryptoSetting(), userRolePrefix, exPolicyStorage, versioningStorage); // 初始化其它属性; @@ -168,38 +156,26 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { VersioningKVStorage versioningKVStorage, boolean readonly) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); - this.rolePrivilegePrefix = Bytes.fromString(keyPrefix + ROLE_PRIVILEGE_PREFIX); - this.userRolePrefix = Bytes.fromString(keyPrefix + USER_ROLE_PREFIX); this.storage = kvStorage; this.readonly = readonly; this.origMetadata = loadAndVerifyMetadata(adminAccountHash); - this.metadata = new LedgerMetadataImpl(origMetadata); + this.metadata = new LedgerMetadataInfo(origMetadata); this.settings = loadAndVerifySettings(metadata.getSettingsHash()); // 复制记录一份配置作为上一个区块的原始配置,该实例仅供读取,不做修改,也不会回写到存储; this.previousSettings = new LedgerConfiguration(settings); this.previousSettingHash = metadata.getSettingsHash(); this.adminAccountHash = adminAccountHash; - // this.privileges = new PrivilegeDataSet(metadata.getPrivilegesHash(), - // metadata.getSetting().getCryptoSetting(), - // PrefixAppender.prefix(LEDGER_PRIVILEGE_PREFIX, kvStorage), - // PrefixAppender.prefix(LEDGER_PRIVILEGE_PREFIX, versioningKVStorage), - // readonly); - - // this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), - // previousSetting.getCryptoSetting(), - // PrefixAppender.prefix(LEDGER_PARTICIPANT_PREFIX, kvStorage), - // PrefixAppender.prefix(LEDGER_PARTICIPANT_PREFIX, versioningKVStorage), - // readonly); + String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), + this.participants = new ParticipantDataset(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), partiPrefix, kvStorage, versioningKVStorage, readonly); String rolePrivilegePrefix = keyPrefix + ROLE_PRIVILEGE_PREFIX; - this.rolePrivileges = new RolePrivilegeDataSet(metadata.getRolePrivilegesHash(), + this.rolePrivileges = new RolePrivilegeDataset(metadata.getRolePrivilegesHash(), previousSettings.getCryptoSetting(), rolePrivilegePrefix, kvStorage, versioningKVStorage, readonly); String userRolePrefix = keyPrefix + USER_ROLE_PREFIX; - this.userRoles = new UserRoleDataSet(metadata.getUserRolesHash(), previousSettings.getCryptoSetting(), + this.userRoles = new UserRoleDataset(metadata.getUserRolesHash(), previousSettings.getCryptoSetting(), userRolePrefix, kvStorage, versioningKVStorage, readonly); } @@ -297,19 +273,6 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return participants.getParticipantCount(); } - // /* - // * (non-Javadoc) - // * - // * @see - // * - // com.jd.blockchain.ledger.core.LedgerAdministration#getParticipant(java.lang. - // * String) - // */ - // @Override - // public ParticipantNode getParticipant(int id) { - // return participants.getParticipant(id); - // } - @Override public ParticipantNode[] getParticipants() { return participants.getParticipants(); @@ -397,7 +360,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return BinaryProtocol.decode(bytes); } - private byte[] serializeMetadata(LedgerMetadataImpl config) { + private byte[] serializeMetadata(LedgerMetadataInfo config) { return BinaryProtocol.encode(config, LedgerMetadata_V2.class); } @@ -407,10 +370,10 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return; } participants.cancel(); - metadata = new LedgerMetadataImpl(origMetadata); + metadata = new LedgerMetadataInfo(origMetadata); } - public static class LedgerMetadataImpl implements LedgerMetadata_V2 { + public static class LedgerMetadataInfo implements LedgerMetadata_V2 { private byte[] seed; @@ -424,10 +387,10 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { private HashDigest userRolesHash; - public LedgerMetadataImpl() { + public LedgerMetadataInfo() { } - public LedgerMetadataImpl(LedgerMetadata_V2 metadata) { + public LedgerMetadataInfo(LedgerMetadata_V2 metadata) { this.seed = metadata.getSeed(); this.participantsHash = metadata.getParticipantsHash(); this.settingsHash = metadata.getSettingsHash(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java similarity index 93% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java index 9c015a6f..05267228 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java @@ -11,7 +11,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class ParticipantDataSet implements Transactional, MerkleProvable { +public class ParticipantDataset implements Transactional, MerkleProvable { static { DataContractRegistry.register(ParticipantNode.class); @@ -19,12 +19,12 @@ public class ParticipantDataSet implements Transactional, MerkleProvable { private MerkleDataSet dataset; - public ParticipantDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, + public ParticipantDataset(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage) { dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); } - public ParticipantDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, + public ParticipantDataset(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java index e221bb06..f012e8db 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java @@ -10,16 +10,16 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class RolePrivilegeDataSet implements Transactional, MerkleProvable, RolePrivilegeSettings { +public class RolePrivilegeDataset implements Transactional, MerkleProvable, RolePrivilegeSettings { private MerkleDataSet dataset; - public RolePrivilegeDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, + public RolePrivilegeDataset(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage) { dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); } - public RolePrivilegeDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, + public RolePrivilegeDataset(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java index 9fd9b834..ec7345c6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java @@ -11,16 +11,22 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class UserRoleDataSet implements Transactional, MerkleProvable, UserRoleSettings { - +/** + * User-Role authorization data set; + * + * @author huanghaiquan + * + */ +public class UserRoleDataset implements Transactional, MerkleProvable, UserRoleSettings { + private MerkleDataSet dataset; - public UserRoleDataSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, + public UserRoleDataset(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage) { dataset = new MerkleDataSet(cryptoSetting, prefix, exPolicyStorage, verStorage); } - public UserRoleDataSet(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, + public UserRoleDataset(HashDigest merkleRootHash, CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage verStorage, boolean readonly) { dataset = new MerkleDataSet(merkleRootHash, cryptoSetting, prefix, exPolicyStorage, verStorage, readonly); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java index 54eeab93..22dbef11 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java @@ -79,7 +79,7 @@ public class LedgerMetaDataTest { // new Bytes(consensusSettingBytes), cryptoConfig); HashDigest settingsHash = Crypto.getHashFunction("SHA256").hash(consensusSettingBytes); - LedgerAdminAccount.LedgerMetadataImpl ledgerMetadata = new LedgerAdminAccount.LedgerMetadataImpl(); + LedgerAdminAccount.LedgerMetadataInfo ledgerMetadata = new LedgerAdminAccount.LedgerMetadataInfo(); ledgerMetadata.setSeed(seed); ledgerMetadata.setSettingsHash(settingsHash); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java similarity index 87% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java index 6763162c..47031e56 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java @@ -2,7 +2,6 @@ package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import org.junit.Test; @@ -14,12 +13,12 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerPermission; -import com.jd.blockchain.ledger.core.RolePrivilegeDataSet; +import com.jd.blockchain.ledger.core.RolePrivilegeDataset; import com.jd.blockchain.ledger.core.RolePrivileges; import com.jd.blockchain.ledger.core.TransactionPermission; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; -public class RolePrivilegeDataSetTest { +public class RolePrivilegeDatasetTest { private static final String[] SUPPORTED_PROVIDER_NAMES = { ClassicCryptoService.class.getName(), SMCryptoService.class.getName() }; @@ -45,7 +44,7 @@ public class RolePrivilegeDataSetTest { String roleName = "DEFAULT"; String prefix = "role-privilege/"; - RolePrivilegeDataSet rolePrivilegeDataset = new RolePrivilegeDataSet(cryptoConfig, prefix, testStorage, + RolePrivilegeDataset rolePrivilegeDataset = new RolePrivilegeDataset(cryptoConfig, prefix, testStorage, testStorage); rolePrivilegeDataset.addRolePrivilege(roleName, new LedgerPermission[] { LedgerPermission.REGISTER_USER }, new TransactionPermission[] { TransactionPermission.CONTRACT_OPERATION }); @@ -56,7 +55,7 @@ public class RolePrivilegeDataSetTest { assertNotNull(rolePrivilege); HashDigest rootHash = rolePrivilegeDataset.getRootHash(); - RolePrivilegeDataSet newRolePrivilegeDataset = new RolePrivilegeDataSet(rootHash, cryptoConfig, prefix, + RolePrivilegeDataset newRolePrivilegeDataset = new RolePrivilegeDataset(rootHash, cryptoConfig, prefix, testStorage, testStorage, true); rolePrivilege = newRolePrivilegeDataset.getRolePrivilege(roleName); assertNotNull(rolePrivilege); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java similarity index 93% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java index 41a59be1..2dd586d3 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java @@ -15,11 +15,11 @@ import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.RolesPolicy; -import com.jd.blockchain.ledger.core.UserRoleDataSet; +import com.jd.blockchain.ledger.core.UserRoleDataset; import com.jd.blockchain.ledger.core.UserRoles; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; -public class UserRoleDataSetTest { +public class UserRoleDatasetTest { private static final String[] SUPPORTED_PROVIDER_NAMES = { ClassicCryptoService.class.getName(), SMCryptoService.class.getName() }; @@ -42,7 +42,7 @@ public class UserRoleDataSetTest { MemoryKVStorage testStorage = new MemoryKVStorage(); String prefix = "user-roles/"; - UserRoleDataSet userRolesDataset = new UserRoleDataSet(cryptoConfig, prefix, testStorage, testStorage); + UserRoleDataset userRolesDataset = new UserRoleDataset(cryptoConfig, prefix, testStorage, testStorage); BlockchainKeypair bckp = BlockchainKeyGenerator.getInstance().generate(); String[] authRoles = { "DEFAULT", "MANAGER" }; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java index 8d4075cb..efc55c12 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java @@ -14,7 +14,7 @@ import com.jd.blockchain.utils.Bytes; * */ @DataContract(code = DataCodes.METADATA_CONSENSUS_PARTICIPANT) -public interface ParticipantNode {// extends ConsensusNode, ParticipantInfo { +public interface ParticipantNode { /** * 节点的顺序编号;
        From 21772b13b23203322e9f2fb8d36a2b3786839d3a Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 22 Aug 2019 00:41:48 +0800 Subject: [PATCH 045/124] Renamed; --- .../ledger/core/ContractAccount.java | 3 +- .../blockchain/ledger/core/DataAccount.java | 3 +- ...inAccount.java => LedgerAdminDataset.java} | 12 ++- .../blockchain/ledger/core/LedgerDataSet.java | 4 +- .../ledger/core/LedgerInitProposalData.java | 1 + .../ledger/core/LedgerRepository.java | 4 +- .../ledger/core/LedgerSecurityManager.java | 3 + .../ledger/core/RolePrivilegeDataset.java | 7 ++ .../ledger/core/SettingContext.java | 102 +++++++++--------- .../ledger/core/UserRoleDataset.java | 4 + .../ledger/core/impl/LedgerDataSetImpl.java | 7 +- .../core/impl/LedgerRepositoryImpl.java | 50 +++++---- .../ledger/LedgerAdminAccountTest.java | 35 +++--- .../blockchain/ledger/LedgerMetaDataTest.java | 4 +- .../ledger/RolePrivilegeDatasetTest.java | 6 +- .../ledger/UserRoleDatasetTest.java | 4 +- .../blockchain/ledger}/AbstractPrivilege.java | 2 +- .../jd/blockchain/ledger/LedgerAdminInfo.java | 6 ++ .../blockchain/ledger}/LedgerPermission.java | 2 +- .../blockchain/ledger}/LedgerPrivilege.java | 2 +- .../jd/blockchain/ledger/LedgerSettings.java | 2 - .../com/jd/blockchain/ledger}/Privilege.java | 2 +- .../jd/blockchain/ledger}/PrivilegeSet.java | 2 +- .../ledger}/RolePrivilegeSettings.java | 4 +- .../jd/blockchain/ledger}/RolePrivileges.java | 2 +- .../com/jd/blockchain/ledger}/RoleSet.java | 2 +- .../jd/blockchain/ledger}/RolesPolicy.java | 2 +- .../ledger}/TransactionPermission.java | 2 +- .../ledger}/TransactionPrivilege.java | 2 +- .../blockchain/ledger}/UserRoleSettings.java | 3 +- .../com/jd/blockchain/ledger}/UserRoles.java | 2 +- .../peer/web/ManagementController.java | 3 +- 32 files changed, 161 insertions(+), 128 deletions(-) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{LedgerAdminAccount.java => LedgerAdminDataset.java} (94%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/AbstractPrivilege.java (96%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/LedgerPermission.java (98%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/LedgerPrivilege.java (91%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/Privilege.java (57%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/PrivilegeSet.java (93%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/RolePrivilegeSettings.java (97%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/RolePrivileges.java (97%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/RoleSet.java (92%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/RolesPolicy.java (95%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/TransactionPermission.java (94%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/TransactionPrivilege.java (88%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/UserRoleSettings.java (93%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/UserRoles.java (97%) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java index c4a94ee6..23354356 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java @@ -2,9 +2,8 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.ContractInfo; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java index 792ca704..328ac05c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java @@ -1,12 +1,11 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.KVDataObject; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index 0bb3b75a..07d91d34 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -15,20 +15,22 @@ import com.jd.blockchain.ledger.LedgerMetadata; import com.jd.blockchain.ledger.LedgerMetadata_V2; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.UserRoleSettings; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { +public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { static { DataContractRegistry.register(LedgerMetadata.class); DataContractRegistry.register(LedgerMetadata_V2.class); } - private static Logger LOGGER = LoggerFactory.getLogger(LedgerAdminAccount.class); + private static Logger LOGGER = LoggerFactory.getLogger(LedgerAdminDataset.class); public static final String LEDGER_META_PREFIX = "MTA" + LedgerConsts.KEY_SEPERATOR; public static final String LEDGER_PARTICIPANT_PREFIX = "PAR" + LedgerConsts.KEY_SEPERATOR; @@ -89,10 +91,12 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { return readonly; } + @Override public RolePrivilegeSettings getRolePrivileges() { return rolePrivileges; } + @Override public UserRoleSettings getUserRoles() { return userRoles; } @@ -110,7 +114,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { * @param exPolicyStorage * @param versioningStorage */ - public LedgerAdminAccount(LedgerInitSetting initSetting, String keyPrefix, ExPolicyKVStorage exPolicyStorage, + public LedgerAdminDataset(LedgerInitSetting initSetting, String keyPrefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage versioningStorage) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); @@ -152,7 +156,7 @@ public class LedgerAdminAccount implements Transactional, LedgerAdminInfo { this.readonly = false; } - public LedgerAdminAccount(HashDigest adminAccountHash, String keyPrefix, ExPolicyKVStorage kvStorage, + public LedgerAdminDataset(HashDigest adminAccountHash, String keyPrefix, ExPolicyKVStorage kvStorage, VersioningKVStorage versioningKVStorage, boolean readonly) { this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java index 40f63da6..0736101b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java @@ -1,5 +1,7 @@ package com.jd.blockchain.ledger.core; +import com.jd.blockchain.ledger.LedgerAdminInfo; + /** * {@link LedgerDataSet} 表示账本在某一个区块上的数据集合; * @@ -10,7 +12,7 @@ public interface LedgerDataSet{ boolean isReadonly(); - LedgerAdminAccount getAdminAccount(); + LedgerAdminInfo getAdminAccount(); UserAccountSet getUserAccountSet(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java index 6b6c79aa..4fa95cb7 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitProposalData.java @@ -11,6 +11,7 @@ public class LedgerInitProposalData implements LedgerInitProposal { /** * a private contructor for deserialize; */ + @SuppressWarnings("unused") private LedgerInitProposalData() { } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java index c548e531..137d15a2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java @@ -60,7 +60,7 @@ public interface LedgerRepository extends Closeable { TransactionSet getTransactionSet(LedgerBlock block); - LedgerAdminAccount getAdminAccount(LedgerBlock block); + LedgerAdminInfo getAdminAccount(LedgerBlock block); UserAccountSet getUserAccountSet(LedgerBlock block); @@ -76,7 +76,7 @@ public interface LedgerRepository extends Closeable { return getTransactionSet(getLatestBlock()); } - default LedgerAdminAccount getAdminAccount() { + default LedgerAdminInfo getAdminAccount() { return getAdminAccount(getLatestBlock()); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java index c9cbf359..aa0692a2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -2,6 +2,9 @@ package com.jd.blockchain.ledger.core; import java.util.Set; +import com.jd.blockchain.ledger.LedgerPrivilege; +import com.jd.blockchain.ledger.RolePrivileges; + /** * * {@link LedgerSecurityManager} implements the functions of security diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java index f012e8db..56c98bf4 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java @@ -4,6 +4,13 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerPrivilege; +import com.jd.blockchain.ledger.PrivilegeSet; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.TransactionPrivilege; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVEntry; import com.jd.blockchain.storage.service.VersioningKVStorage; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SettingContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SettingContext.java index 516b2acf..f464db7f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SettingContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SettingContext.java @@ -1,51 +1,51 @@ -package com.jd.blockchain.ledger.core; - -public class SettingContext { - - private static final TxSettingContext txSettings = new TxSettingContext(); - - private static final QueryingSettingContext queryingSettings = new QueryingSettingContext(); - - public static TxSettingContext txSettings() { - return txSettings; - } - - public static QueryingSettingContext queryingSettings() { - return queryingSettings; - } - - /** - * 与交易处理相关的设置; - * @author huanghaiquan - * - */ - public static class TxSettingContext { - - public boolean verifyLedger() { - return true; - } - - public boolean verifySignature() { - return true; - } - - } - - /** - * 与账本查询相关的设置; - * @author huanghaiquan - * - */ - public static class QueryingSettingContext { - - /** - * 查询区块等具有 hash 标识符的对象时是否重新校验哈希; - * @return - */ - public boolean verifyHash() { - return false; - } - - } - -} +//package com.jd.blockchain.ledger.core; +// +//public class SettingContext { +// +// private static final TxSettingContext txSettings = new TxSettingContext(); +// +// private static final QueryingSettingContext queryingSettings = new QueryingSettingContext(); +// +// public static TxSettingContext txSettings() { +// return txSettings; +// } +// +// public static QueryingSettingContext queryingSettings() { +// return queryingSettings; +// } +// +// /** +// * 与交易处理相关的设置; +// * @author huanghaiquan +// * +// */ +// public static class TxSettingContext { +// +// public boolean verifyLedger() { +// return true; +// } +// +// public boolean verifySignature() { +// return true; +// } +// +// } +// +// /** +// * 与账本查询相关的设置; +// * @author huanghaiquan +// * +// */ +// public static class QueryingSettingContext { +// +// /** +// * 查询区块等具有 hash 标识符的对象时是否重新校验哈希; +// * @return +// */ +// public boolean verifyHash() { +// return false; +// } +// +// } +// +//} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java index ec7345c6..0c6d72d1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java @@ -5,6 +5,10 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.AuthorizationException; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.RoleSet; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVEntry; import com.jd.blockchain.storage.service.VersioningKVStorage; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java index 4ec9d657..cfcaa50d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java @@ -1,11 +1,12 @@ package com.jd.blockchain.ledger.core.impl; +import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.core.*; import com.jd.blockchain.utils.Transactional; public class LedgerDataSetImpl implements LedgerDataSet, Transactional { - private LedgerAdminAccount adminAccount; + private LedgerAdminDataset adminAccount; private UserAccountSet userAccountSet; @@ -24,7 +25,7 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { * @param contractAccountSet * @param readonly */ - public LedgerDataSetImpl(LedgerAdminAccount adminAccount, + public LedgerDataSetImpl(LedgerAdminDataset adminAccount, UserAccountSet userAccountSet, DataAccountSet dataAccountSet, ContractAccountSet contractAccountSet, boolean readonly) { this.adminAccount = adminAccount; @@ -36,7 +37,7 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { } @Override - public LedgerAdminAccount getAdminAccount() { + public LedgerAdminInfo getAdminAccount() { return adminAccount; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java index 4dff3f06..8a14f014 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java @@ -4,11 +4,18 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.HashFunction; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.BlockBody; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerDataSnapshot; +import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerSettings; +import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.core.AccountAccessPolicy; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.DataAccountSet; -import com.jd.blockchain.ledger.core.LedgerAdminAccount; +import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConsts; import com.jd.blockchain.ledger.core.LedgerDataSet; import com.jd.blockchain.ledger.core.LedgerEditor; @@ -50,6 +57,11 @@ public class LedgerRepositoryImpl implements LedgerRepository { private static final Bytes TRANSACTION_SET_PREFIX = Bytes.fromString("TXS" + LedgerConsts.KEY_SEPERATOR); private static final AccountAccessPolicy DEFAULT_ACCESS_POLICY = new OpeningAccessPolicy(); + + /** + * 经过上一轮共识确认的账本管理配置; + */ + private LedgerAdminInfo approvedAdminInfo; private HashDigest ledgerHash; @@ -66,7 +78,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { private volatile LedgerEditor nextBlockEditor; private volatile boolean closed = false; - + public LedgerRepositoryImpl(HashDigest ledgerHash, String keyPrefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage versioningStorage) { this.keyPrefix = keyPrefix; @@ -260,7 +272,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { LedgerState state = getLatestState(); transactionSet = state.transactionSet; if (transactionSet == null) { - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminInfo adminAccount = getAdminAccount(block); transactionSet = loadTransactionSet(block.getTransactionSetHash(), adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); @@ -268,7 +280,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { } return transactionSet; } - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminInfo adminAccount = getAdminAccount(block); // All of existing block is readonly; return loadTransactionSet(block.getTransactionSetHash(), adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, @@ -276,22 +288,22 @@ public class LedgerRepositoryImpl implements LedgerRepository { } @Override - public LedgerAdminAccount getAdminAccount(LedgerBlock block) { + public LedgerAdminDataset getAdminAccount(LedgerBlock block) { long height = getLatestBlockHeight(); - LedgerAdminAccount adminAccount = null; + LedgerAdminDataset adminAccount = null; if (height == block.getHeight()) { // 缓存读; LedgerState state = getLatestState(); adminAccount = state.adminAccount; if (adminAccount == null) { - adminAccount = new LedgerAdminAccount(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, + adminAccount = new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); state.adminAccount = adminAccount; } return adminAccount; } - return new LedgerAdminAccount(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); + return new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); } @Override @@ -303,7 +315,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { LedgerState state = getLatestState(); userAccountSet = state.userAccountSet; if (userAccountSet == null) { - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); userAccountSet = loadUserAccountSet(block.getUserAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); @@ -311,7 +323,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { } return userAccountSet; } - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); return loadUserAccountSet(block.getUserAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); } @@ -325,7 +337,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { LedgerState state = getLatestState(); dataAccountSet = state.dataAccountSet; if (dataAccountSet == null) { - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); dataAccountSet = loadDataAccountSet(block.getDataAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); @@ -334,7 +346,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { return dataAccountSet; } - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); return loadDataAccountSet(block.getDataAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); } @@ -348,7 +360,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { LedgerState state = getLatestState(); contractAccountSet = state.contractAccountSet; if (contractAccountSet == null) { - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); contractAccountSet = loadContractAccountSet(block.getContractAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); @@ -357,7 +369,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { return contractAccountSet; } - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); return loadContractAccountSet(block.getContractAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); @@ -383,7 +395,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { } private LedgerDataSet innerDataSet(LedgerBlock block) { - LedgerAdminAccount adminAccount = getAdminAccount(block); + LedgerAdminDataset adminAccount = getAdminAccount(block); UserAccountSet userAccountSet = getUserAccountSet(block); DataAccountSet dataAccountSet = getDataAccountSet(block); ContractAccountSet contractAccountSet = getContractAccountSet(block); @@ -439,7 +451,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { static LedgerDataSetImpl newDataSet(LedgerInitSetting initSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) { - LedgerAdminAccount adminAccount = new LedgerAdminAccount(initSetting, keyPrefix, ledgerExStorage, + LedgerAdminDataset adminAccount = new LedgerAdminDataset(initSetting, keyPrefix, ledgerExStorage, ledgerVerStorage); String usersetKeyPrefix = keyPrefix + USER_SET_PREFIX; @@ -493,7 +505,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { static LedgerDataSetImpl loadDataSet(LedgerDataSnapshot dataSnapshot, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { - LedgerAdminAccount adminAccount = new LedgerAdminAccount(dataSnapshot.getAdminAccountHash(), keyPrefix, + LedgerAdminDataset adminAccount = new LedgerAdminDataset(dataSnapshot.getAdminAccountHash(), keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); CryptoSetting cryptoSetting = adminAccount.getPreviousSetting().getCryptoSetting(); @@ -627,7 +639,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { private final LedgerBlock block; - private volatile LedgerAdminAccount adminAccount; + private volatile LedgerAdminDataset adminAccount; private volatile UserAccountSet userAccountSet; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java index d7d820c4..9979809d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java @@ -21,19 +21,20 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerMetadata_V2; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.LedgerAdminAccount; +import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; -import com.jd.blockchain.ledger.core.LedgerPermission; -import com.jd.blockchain.ledger.core.RolePrivilegeSettings; -import com.jd.blockchain.ledger.core.RolePrivileges; -import com.jd.blockchain.ledger.core.RolesPolicy; -import com.jd.blockchain.ledger.core.TransactionPermission; -import com.jd.blockchain.ledger.core.UserRoleSettings; -import com.jd.blockchain.ledger.core.UserRoles; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitSettingData; @@ -87,7 +88,7 @@ public class LedgerAdminAccountTest { MemoryKVStorage testStorage = new MemoryKVStorage(); // Create intance with init setting; - LedgerAdminAccount ledgerAdminAccount = new LedgerAdminAccount(initSetting, keyPrefix, testStorage, + LedgerAdminDataset ledgerAdminAccount = new LedgerAdminDataset(initSetting, keyPrefix, testStorage, testStorage); ledgerAdminAccount.getRolePrivileges().addRolePrivilege("DEFAULT", @@ -126,7 +127,7 @@ public class LedgerAdminAccountTest { // Reload account from storage with readonly mode, and check the integrity of // data; HashDigest adminAccHash = ledgerAdminAccount.getHash(); - LedgerAdminAccount reloadAdminAccount1 = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, + LedgerAdminDataset reloadAdminAccount1 = new LedgerAdminDataset(adminAccHash, keyPrefix, testStorage, testStorage, true); LedgerMetadata_V2 meta2 = reloadAdminAccount1.getMetadata(); @@ -148,7 +149,7 @@ public class LedgerAdminAccountTest { // -------------- // 重新加载,并进行修改; - LedgerAdminAccount reloadAdminAccount2 = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, testStorage, false); + LedgerAdminDataset reloadAdminAccount2 = new LedgerAdminDataset(adminAccHash, keyPrefix, testStorage, testStorage, false); LedgerConfiguration newSetting = new LedgerConfiguration(reloadAdminAccount2.getPreviousSetting()); byte[] newCsSettingBytes = new byte[64]; rand.nextBytes(newCsSettingBytes); @@ -175,7 +176,7 @@ public class LedgerAdminAccountTest { LedgerMetadata_V2 newMeta = reloadAdminAccount2.getMetadata(); // load the last version of account and verify again; - LedgerAdminAccount previousAdminAccount = new LedgerAdminAccount(adminAccHash, keyPrefix, testStorage, + LedgerAdminDataset previousAdminAccount = new LedgerAdminDataset(adminAccHash, keyPrefix, testStorage, testStorage, true); verifyRealoadingSettings(previousAdminAccount, adminAccHash, ledgerAdminAccount.getMetadata(), ledgerAdminAccount.getSettings()); @@ -183,7 +184,7 @@ public class LedgerAdminAccountTest { verifyReadonlyState(previousAdminAccount); // load the hash of new committing; - LedgerAdminAccount newlyAdminAccount = new LedgerAdminAccount(newAccHash, keyPrefix, testStorage, testStorage, + LedgerAdminDataset newlyAdminAccount = new LedgerAdminDataset(newAccHash, keyPrefix, testStorage, testStorage, true); verifyRealoadingSettings(newlyAdminAccount, newAccHash, newMeta, newlyLedgerSettings); verifyRealoadingParities(newlyAdminAccount, parties); @@ -193,7 +194,7 @@ public class LedgerAdminAccountTest { // testStorage.printStoragedKeys(); } - private void verifyRealoadingSettings(LedgerAdminAccount actualAccount, HashDigest expAccRootHash, + private void verifyRealoadingSettings(LedgerAdminDataset actualAccount, HashDigest expAccRootHash, LedgerMetadata_V2 expMeta, LedgerSettings expLedgerSettings) { // 验证基本信息; assertFalse(actualAccount.isUpdated()); @@ -223,7 +224,7 @@ public class LedgerAdminAccountTest { actualLedgerSettings.getCryptoSetting().getHashAlgorithm()); } - private void verifyRealoadingRoleAuthorizations(LedgerAdminAccount actualAccount, + private void verifyRealoadingRoleAuthorizations(LedgerAdminInfo actualAccount, RolePrivilegeSettings expRolePrivilegeSettings, UserRoleSettings expUserRoleSettings) { // 验证基本信息; RolePrivilegeSettings actualRolePrivileges = actualAccount.getRolePrivileges(); @@ -254,7 +255,7 @@ public class LedgerAdminAccountTest { } } - private void verifyRealoadingParities(LedgerAdminAccount actualAccount, ParticipantNode[] expParties) { + private void verifyRealoadingParities(LedgerAdminInfo actualAccount, ParticipantNode[] expParties) { assertEquals(expParties.length, actualAccount.getParticipantCount()); ParticipantNode[] actualPaticipants = actualAccount.getParticipants(); assertEquals(expParties.length, actualPaticipants.length); @@ -273,7 +274,7 @@ public class LedgerAdminAccountTest { * * @param readonlyAccount */ - private void verifyReadonlyState(LedgerAdminAccount readonlyAccount) { + private void verifyReadonlyState(LedgerAdminDataset readonlyAccount) { ConsensusParticipantData newParti = new ConsensusParticipantData(); newParti.setId((int) readonlyAccount.getParticipantCount()); newParti.setHostAddress( diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java index 22dbef11..2629461a 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java @@ -26,7 +26,7 @@ import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.LedgerAdminAccount; +import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; import com.jd.blockchain.ledger.core.ParticipantCertData; import com.jd.blockchain.utils.Bytes; @@ -79,7 +79,7 @@ public class LedgerMetaDataTest { // new Bytes(consensusSettingBytes), cryptoConfig); HashDigest settingsHash = Crypto.getHashFunction("SHA256").hash(consensusSettingBytes); - LedgerAdminAccount.LedgerMetadataInfo ledgerMetadata = new LedgerAdminAccount.LedgerMetadataInfo(); + LedgerAdminDataset.LedgerMetadataInfo ledgerMetadata = new LedgerAdminDataset.LedgerMetadataInfo(); ledgerMetadata.setSeed(seed); ledgerMetadata.setSettingsHash(settingsHash); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java index 47031e56..3c18ab32 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java @@ -11,11 +11,11 @@ import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.LedgerPermission; import com.jd.blockchain.ledger.core.RolePrivilegeDataset; -import com.jd.blockchain.ledger.core.RolePrivileges; -import com.jd.blockchain.ledger.core.TransactionPermission; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; public class RolePrivilegeDatasetTest { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java index 2dd586d3..d9b89047 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java @@ -13,10 +13,10 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.RolesPolicy; import com.jd.blockchain.ledger.core.UserRoleDataset; -import com.jd.blockchain.ledger.core.UserRoles; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; public class UserRoleDatasetTest { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java similarity index 96% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java index 01ba5afd..df65c93c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AbstractPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import java.util.BitSet; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java index 4e1b5105..ca09e1f5 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java @@ -20,4 +20,10 @@ public interface LedgerAdminInfo { @DataField(order = 4, refContract = true, list = true) ParticipantNode[] getParticipants(); + @DataField(order = 5, refContract = true) + UserRoleSettings getUserRoles(); + + @DataField(order = 6, refContract = true) + RolePrivilegeSettings getRolePrivileges(); + } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java similarity index 98% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java index 04111ef6..dba568ab 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPermission.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.EnumContract; import com.jd.blockchain.binaryproto.EnumField; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java similarity index 91% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java index 73cdf9ef..5b5ebf20 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; /** * LedgerPrivilege 账本特权是授权给特定角色的权限代码序列; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java index 76e2ad2c..fd77685a 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSettings.java @@ -19,6 +19,4 @@ public interface LedgerSettings { @DataField(order=2, refContract=true) CryptoSetting getCryptoSetting(); -// PrivilegeModelSetting getPrivilegesModelSetting(); - } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privilege.java similarity index 57% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privilege.java index 25f6c9eb..4a77e0b9 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/Privilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privilege.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; public interface Privilege> { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeSet.java similarity index 93% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeSet.java index ca4b2914..bf0d3da7 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/PrivilegeSet.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeSet.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java index 47cdca37..159b6a48 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java @@ -1,6 +1,4 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.ledger.LedgerException; +package com.jd.blockchain.ledger; public interface RolePrivilegeSettings { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivileges.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivileges.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java index e2c4f8a5..76db4d01 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivileges.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; /** * 对角色的授权; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleSet.java similarity index 92% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleSet.java index da4f5b6a..a026c23f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RoleSet.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleSet.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesPolicy.java similarity index 95% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesPolicy.java index 42b72bb8..690d0be5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolesPolicy.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesPolicy.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.EnumContract; import com.jd.blockchain.binaryproto.EnumField; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPermission.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPermission.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java index fb513938..b197820e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPermission.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.EnumContract; import com.jd.blockchain.binaryproto.EnumField; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java similarity index 88% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java index 366e39f5..08408326 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; public class TransactionPrivilege extends AbstractPrivilege { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java similarity index 93% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java index 7c35a267..15ef546c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java @@ -1,6 +1,5 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; -import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.utils.Bytes; public interface UserRoleSettings { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java index 9233953a..42160fe3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoles.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core; +package com.jd.blockchain.ledger; import java.util.Set; import java.util.TreeSet; diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index a740f1ca..a7e07ace 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -47,7 +47,6 @@ import com.jd.blockchain.ledger.TransactionContentBody; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.ledger.core.LedgerAdminAccount; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.peer.ConsensusRealm; @@ -227,7 +226,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag LedgerRepository ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); // load provider; - LedgerAdminAccount ledgerAdminAccount = ledgerRepository.getAdminAccount(); + LedgerAdminInfo ledgerAdminAccount = ledgerRepository.getAdminAccount(); String consensusProvider = ledgerAdminAccount.getSettings().getConsensusProvider(); ConsensusProvider provider = ConsensusProviders.getProvider(consensusProvider); // find current node; From 3542ca2ec26df5d63e9b8e66c78630940c0336a4 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 23 Aug 2019 04:33:27 +0800 Subject: [PATCH 046/124] Refactored LedgerRepository; --- .../com/jd/blockchain/consts/DataCodes.java | 2 +- .../jd/blockchain/ledger/core/AccountSet.java | 4 + .../ledger/core/ContractAccountSet.java | 4 + .../ledger/core/DataAccountSet.java | 4 + .../ledger/core/LedgerAdminDataset.java | 42 +- .../blockchain/ledger/core/LedgerDataSet.java | 23 -- .../core/{impl => }/LedgerDataSetImpl.java | 39 +- .../blockchain/ledger/core/LedgerDataset.java | 21 + .../blockchain/ledger/core/LedgerEditor.java | 21 +- .../ledger/core/LedgerRepository.java | 12 +- .../core/{impl => }/LedgerRepositoryImpl.java | 378 ++++++++++-------- .../ledger/core/LedgerTransactionContext.java | 11 +- .../{impl => }/LedgerTransactionalEditor.java | 94 ++--- .../blockchain/ledger/core/MerkleDataSet.java | 4 + .../ledger/core/OperationHandle.java | 4 +- .../ledger/core/TransactionSet.java | 4 + .../ledger/core/UserAccountSet.java | 4 + .../ledger/core/impl/LedgerManager.java | 4 +- .../ledger/core/impl/LedgerQueryService.java | 2 +- .../impl/LedgerTransactionContextImpl.java | 9 - .../core/impl/TransactionBatchProcessor.java | 8 +- .../core/impl/TransactionEngineImpl.java | 6 +- .../handles/AbtractContractEventHandle.java | 8 +- .../ContractCodeDeployOperationHandle.java | 8 +- .../DataAccountKVSetOperationHandle.java | 6 +- .../DataAccountRegisterOperationHandle.java | 6 +- .../handles/UserRegisterOperationHandle.java | 6 +- .../ledger/ContractInvokingTest.java | 7 +- .../blockchain/ledger/LedgerEditorTest.java | 8 +- .../blockchain/ledger/LedgerManagerTest.java | 9 +- .../ledger/TransactionBatchProcessorTest.java | 18 +- .../jd/blockchain/ledger/LedgerAdminInfo.java | 12 - .../peer/web/ManagementController.java | 2 +- .../sdk/proxy/BlockchainServiceProxy.java | 14 +- .../intgr/perf/LedgerPerformanceTest.java | 6 +- .../ledger/LedgerBlockGeneratingTest.java | 4 +- .../web/LedgerInitializeWebController.java | 2 +- .../mocker/MockerLedgerInitializer.java | 2 +- .../blockchain/mocker/MockerNodeContext.java | 6 +- .../handler/MockerContractExeHandle.java | 6 +- 40 files changed, 470 insertions(+), 360 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerDataSetImpl.java (62%) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerRepositoryImpl.java (66%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerTransactionalEditor.java (91%) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionContextImpl.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index d936c788..d83a311e 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -22,7 +22,7 @@ public interface DataCodes { public static final int DATA_SNAPSHOT = 0x130; - public static final int LEDGER_ADMIN_INFO = 0x131; +// public static final int LEDGER_ADMIN_DATA = 0x131; public static final int TX = 0x200; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java index bb57cd3c..f0ec9748 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java @@ -48,6 +48,10 @@ public class AccountSet implements Transactional, MerkleProvable { public boolean isReadonly() { return merkleDataset.isReadonly(); } + + void setReadonly() { + merkleDataset.setReadonly(); + } public AccountSet(CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, AccountAccessPolicy accessPolicy) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java index 039be73d..ad51fd63 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java @@ -33,6 +33,10 @@ public class ContractAccountSet implements MerkleProvable, Transactional { return accountSet.isReadonly(); } + void setReadonly() { + accountSet.setReadonly(); + } + @Override public HashDigest getRootHash() { return accountSet.getRootHash(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java index d44e9dbf..97bdb107 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java @@ -33,6 +33,10 @@ public class DataAccountSet implements MerkleProvable, Transactional { return accountSet.isReadonly(); } + void setReadonly() { + accountSet.setReadonly(); + } + @Override public HashDigest getRootHash() { return accountSet.getRootHash(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index 07d91d34..cbc0af77 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -77,20 +77,28 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { private ExPolicyKVStorage storage; - private HashDigest adminAccountHash; + private HashDigest adminDataHash; private boolean readonly; private boolean updated; public HashDigest getHash() { - return adminAccountHash; + return adminDataHash; } public boolean isReadonly() { return readonly; } + void setReadonly() { + this.readonly = true; + } + + public LedgerSettings getPreviousSetting() { + return previousSettings; + } + @Override public RolePrivilegeSettings getRolePrivileges() { return rolePrivileges; @@ -132,7 +140,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { initSetting.getCryptoSetting()); this.previousSettings = new LedgerConfiguration(settings); this.previousSettingHash = null; - this.adminAccountHash = null; + this.adminDataHash = null; // 基于原配置初始化参与者列表; String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; @@ -168,7 +176,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { // 复制记录一份配置作为上一个区块的原始配置,该实例仅供读取,不做修改,也不会回写到存储; this.previousSettings = new LedgerConfiguration(settings); this.previousSettingHash = metadata.getSettingsHash(); - this.adminAccountHash = adminAccountHash; + this.adminDataHash = adminAccountHash; String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; this.participants = new ParticipantDataset(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), @@ -238,17 +246,17 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { return metadata; } - /** - * 返回原来的账本配置; - * - *
        - * 此方法总是返回从上一个区块加载的账本配置,即时调用 {@link #setLedgerSetting(LedgerSettings)} 做出了新的更改; - * - * @return - */ - public LedgerSettings getPreviousSetting() { - return previousSettings; - } +// /** +// * 返回原来的账本配置; +// * +// *
        +// * 此方法总是返回从上一个区块加载的账本配置,即时调用 {@link #setLedgerSetting(LedgerSettings)} 做出了新的更改; +// * +// * @return +// */ +// public LedgerSettings getPreviousSetting() { +// return previousSettings; +// } /** * 返回当前设置的账本配置; @@ -339,7 +347,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { byte[] metadataBytes = serializeMetadata(metadata); HashDigest metadataHash = hashFunc.hash(metadataBytes); - if (adminAccountHash == null || !adminAccountHash.equals(metadataHash)) { + if (adminDataHash == null || !adminDataHash.equals(metadataHash)) { // update modify; // String base58MetadataHash = metadataHash.toBase58(); // String metadataKey = encodeMetadataKey(base58MetadataHash); @@ -354,7 +362,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { throw new LedgerException(errMsg); } - adminAccountHash = metadataHash; + adminDataHash = metadataHash; } updated = false; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java deleted file mode 100644 index 0736101b..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSet.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.ledger.LedgerAdminInfo; - -/** - * {@link LedgerDataSet} 表示账本在某一个区块上的数据集合; - * - * @author huanghaiquan - * - */ -public interface LedgerDataSet{ - - boolean isReadonly(); - - LedgerAdminInfo getAdminAccount(); - - UserAccountSet getUserAccountSet(); - - DataAccountSet getDataAccountSet(); - - ContractAccountSet getContractAccountSet(); - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java similarity index 62% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java index cfcaa50d..613990aa 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerDataSetImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java @@ -1,34 +1,31 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; -import com.jd.blockchain.ledger.LedgerAdminInfo; -import com.jd.blockchain.ledger.core.*; import com.jd.blockchain.utils.Transactional; -public class LedgerDataSetImpl implements LedgerDataSet, Transactional { +public class LedgerDataSetImpl implements LedgerDataset, Transactional { - private LedgerAdminDataset adminAccount; + private LedgerAdminDataset adminDataset; private UserAccountSet userAccountSet; private DataAccountSet dataAccountSet; private ContractAccountSet contractAccountSet; - - private boolean readonly; + private boolean readonly; /** * Create new block; + * * @param adminAccount * @param userAccountSet * @param dataAccountSet * @param contractAccountSet * @param readonly */ - public LedgerDataSetImpl(LedgerAdminDataset adminAccount, - UserAccountSet userAccountSet, DataAccountSet dataAccountSet, ContractAccountSet contractAccountSet, - boolean readonly) { - this.adminAccount = adminAccount; + public LedgerDataSetImpl(LedgerAdminDataset adminAccount, UserAccountSet userAccountSet, + DataAccountSet dataAccountSet, ContractAccountSet contractAccountSet, boolean readonly) { + this.adminDataset = adminAccount; this.userAccountSet = userAccountSet; this.dataAccountSet = dataAccountSet; this.contractAccountSet = contractAccountSet; @@ -37,8 +34,8 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { } @Override - public LedgerAdminInfo getAdminAccount() { - return adminAccount; + public LedgerAdminDataset getAdminDataset() { + return adminDataset; } @Override @@ -52,13 +49,13 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { } @Override - public ContractAccountSet getContractAccountSet() { + public ContractAccountSet getContractAccountset() { return contractAccountSet; } @Override public boolean isUpdated() { - return adminAccount.isUpdated() || userAccountSet.isUpdated() || dataAccountSet.isUpdated() + return adminDataset.isUpdated() || userAccountSet.isUpdated() || dataAccountSet.isUpdated() || contractAccountSet.isUpdated(); } @@ -71,7 +68,7 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { return; } - adminAccount.commit(); + adminDataset.commit(); userAccountSet.commit(); dataAccountSet.commit(); contractAccountSet.commit(); @@ -79,7 +76,7 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { @Override public void cancel() { - adminAccount.cancel(); + adminDataset.cancel(); userAccountSet.cancel(); dataAccountSet.cancel(); contractAccountSet.cancel(); @@ -90,4 +87,12 @@ public class LedgerDataSetImpl implements LedgerDataSet, Transactional { return readonly; } + void setReadonly() { + this.readonly = true; + this.adminDataset.setReadonly(); + this.userAccountSet.setReadonly(); + this.dataAccountSet.setReadonly(); + this.contractAccountSet.setReadonly(); + } + } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java new file mode 100644 index 00000000..46e91855 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java @@ -0,0 +1,21 @@ +package com.jd.blockchain.ledger.core; + +/** + * {@link LedgerDataset} 表示账本在某一个区块上的数据集合; + * + * @author huanghaiquan + * + */ +public interface LedgerDataset{ + + boolean isReadonly(); + + LedgerAdminDataset getAdminDataset(); + + UserAccountSet getUserAccountSet(); + + DataAccountSet getDataAccountSet(); + + ContractAccountSet getContractAccountset(); + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerEditor.java index 46c21655..466dd30a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerEditor.java @@ -11,7 +11,7 @@ import com.jd.blockchain.ledger.TransactionRequest; *

        * * {@link LedgerEditor} 以上一个区块作为数据编辑的起点;
        - * 对账本数据({@link #getDataSet()})的批量更改可以作为一个交易({@link LedgerTransaction})整体提交暂存,形成暂存点; + * 对账本数据({@link #getDataset()})的批量更改可以作为一个交易({@link LedgerTransaction})整体提交暂存,形成暂存点; *
        * * @author huanghaiquan @@ -33,11 +33,25 @@ public interface LedgerEditor { */ long getBlockHeight(); + /** + * 最新的账本数据集; + * + * @return + */ + LedgerDataset getLedgerDataset(); + + /** + * 最新的交易集合; + * + * @return + */ + TransactionSet getTransactionSet(); + /** * 开始新事务;
        * * 方法返回之前,将会校验交易请求的用户签名列表和节点签名列表,并在后续对数据集 - * {@link LedgerTransactionContext#getDataSet()} 的操作时,校验这些用户和节点是否具备权限;
        + * {@link LedgerTransactionContext#getDataset()} 的操作时,校验这些用户和节点是否具备权限;
        * * 校验失败将引发异常 {@link LedgerException}; *

        @@ -52,7 +66,8 @@ public interface LedgerEditor { * * * - * 注:方法不解析、不执行交易中的操作;

        + * 注:方法不解析、不执行交易中的操作; + *

        * * @param txRequest 交易请求; * @return diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java index 137d15a2..73400223 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java @@ -53,22 +53,22 @@ public interface LedgerRepository extends Closeable { LedgerBlock getBlock(long height); LedgerAdminInfo getAdminInfo(); + + LedgerAdminInfo getAdminInfo(LedgerBlock block); LedgerBlock getBlock(HashDigest hash); - LedgerDataSet getDataSet(LedgerBlock block); + LedgerDataset getDataSet(LedgerBlock block); TransactionSet getTransactionSet(LedgerBlock block); - LedgerAdminInfo getAdminAccount(LedgerBlock block); - UserAccountSet getUserAccountSet(LedgerBlock block); DataAccountSet getDataAccountSet(LedgerBlock block); ContractAccountSet getContractAccountSet(LedgerBlock block); - default LedgerDataSet getDataSet() { + default LedgerDataset getDataSet() { return getDataSet(getLatestBlock()); } @@ -76,10 +76,6 @@ public interface LedgerRepository extends Closeable { return getTransactionSet(getLatestBlock()); } - default LedgerAdminInfo getAdminAccount() { - return getAdminAccount(getLatestBlock()); - } - default UserAccountSet getUserAccountSet() { return getUserAccountSet(getLatestBlock()); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java similarity index 66% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 8a14f014..0b973a69 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.Crypto; @@ -12,18 +12,8 @@ import com.jd.blockchain.ledger.LedgerDataSnapshot; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.core.AccountAccessPolicy; -import com.jd.blockchain.ledger.core.ContractAccountSet; -import com.jd.blockchain.ledger.core.DataAccountSet; -import com.jd.blockchain.ledger.core.LedgerAdminDataset; -import com.jd.blockchain.ledger.core.LedgerConsts; -import com.jd.blockchain.ledger.core.LedgerDataSet; -import com.jd.blockchain.ledger.core.LedgerEditor; -import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.LedgerTransactionContext; -import com.jd.blockchain.ledger.core.SettingContext; -import com.jd.blockchain.ledger.core.TransactionSet; -import com.jd.blockchain.ledger.core.UserAccountSet; +import com.jd.blockchain.ledger.core.impl.LedgerBlockData; +import com.jd.blockchain.ledger.core.impl.OpeningAccessPolicy; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; @@ -57,11 +47,6 @@ public class LedgerRepositoryImpl implements LedgerRepository { private static final Bytes TRANSACTION_SET_PREFIX = Bytes.fromString("TXS" + LedgerConsts.KEY_SEPERATOR); private static final AccountAccessPolicy DEFAULT_ACCESS_POLICY = new OpeningAccessPolicy(); - - /** - * 经过上一轮共识确认的账本管理配置; - */ - private LedgerAdminInfo approvedAdminInfo; private HashDigest ledgerHash; @@ -78,7 +63,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { private volatile LedgerEditor nextBlockEditor; private volatile boolean closed = false; - + public LedgerRepositoryImpl(HashDigest ledgerHash, String keyPrefix, ExPolicyKVStorage exPolicyStorage, VersioningKVStorage versioningStorage) { this.keyPrefix = keyPrefix; @@ -91,6 +76,8 @@ public class LedgerRepositoryImpl implements LedgerRepository { if (getLatestBlockHeight() < 0) { throw new RuntimeException("Ledger doesn't exist!"); } + + retrieveLatestState(); } /* @@ -121,25 +108,37 @@ public class LedgerRepositoryImpl implements LedgerRepository { @Override public LedgerBlock getLatestBlock() { - LedgerState state = getLatestState(); - return state.block; + return latestState.block; } - private LedgerState getLatestState() { - LedgerState state = latestState; - if (state == null) { - LedgerBlock latestBlock = innerGetBlock(innerGetLatestBlockHeight()); - state = new LedgerState(latestBlock); - latestState = state; - } - return state; +// private LedgerState getLatestState() { +// LedgerState state = latestState; +// if (state == null) { +// LedgerBlock latestBlock = innerGetBlock(innerGetLatestBlockHeight()); +// state = new LedgerState(latestBlock); +// latestState = state; +// } +// return state; +// } + + /** + * 重新检索加载最新的状态; + * + * @return + */ + private LedgerState retrieveLatestState() { + LedgerBlock latestBlock = innerGetBlock(innerGetLatestBlockHeight()); + LedgerDataset ledgerDataset = innerGetLedgerDataset(latestBlock); + TransactionSet txSet = loadTransactionSet(latestBlock.getTransactionSetHash(), + ledgerDataset.getAdminDataset().getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, + versioningStorage, true); + this.latestState = new LedgerState(latestBlock, ledgerDataset, txSet); + return latestState; } @Override public LedgerBlock retrieveLatestBlock() { - LedgerBlock latestBlock = innerGetBlock(innerGetLatestBlockHeight()); - latestState = new LedgerState(latestBlock); - return latestBlock; + return retrieveLatestState().block; } @Override @@ -198,7 +197,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { if (height < 0) { return null; } - return innerGetBlock(getBlockHash(height)); + return innerGetBlock(innerGetBlockHash(height)); } @Override @@ -220,26 +219,18 @@ public class LedgerRepositoryImpl implements LedgerRepository { throw new RuntimeException("Block hash not equals to it's storage key!"); } - // verify hash; - // boolean requiredVerifyHash = - // adminAccount.getMetadata().getSetting().getCryptoSetting().getAutoVerifyHash(); - // TODO: 未实现从配置中加载是否校验 Hash 的设置; - if (SettingContext.queryingSettings().verifyHash()) { - byte[] blockBodyBytes = null; - if (block.getHeight() == 0) { - // 计算创世区块的 hash 时,不包括 ledgerHash 字段; - block.setLedgerHash(null); - blockBodyBytes = BinaryProtocol.encode(block, BlockBody.class); - // 恢复; - block.setLedgerHash(block.getHash()); - } else { - blockBodyBytes = BinaryProtocol.encode(block, BlockBody.class); - } - HashFunction hashFunc = Crypto.getHashFunction(blockHash.getAlgorithm()); - boolean pass = hashFunc.verify(blockHash, blockBodyBytes); - if (!pass) { - throw new RuntimeException("Block hash verification fail!"); - } + // verify block hash; + byte[] blockBodyBytes = null; + if (block.getHeight() == 0) { + // 计算创世区块的 hash 时,不包括 ledgerHash 字段; + blockBodyBytes = BinaryProtocol.encode(block, BlockBody.class); + } else { + blockBodyBytes = BinaryProtocol.encode(block, BlockBody.class); + } + HashFunction hashFunc = Crypto.getHashFunction(blockHash.getAlgorithm()); + boolean pass = hashFunc.verify(blockHash, blockBodyBytes); + if (!pass) { + throw new RuntimeException("Block hash verification fail!"); } // verify height; @@ -254,9 +245,18 @@ public class LedgerRepositoryImpl implements LedgerRepository { return block; } + /** + * 获取最新区块的账本参数; + * + * @return + */ + private LedgerSettings getLatestSettings() { + return getAdminInfo().getSettings(); + } + @Override public LedgerAdminInfo getAdminInfo() { - return getAdminAccount(getLatestBlock()); + return getAdminInfo(getLatestBlock()); } private LedgerBlock deserialize(byte[] blockBytes) { @@ -266,140 +266,169 @@ public class LedgerRepositoryImpl implements LedgerRepository { @Override public TransactionSet getTransactionSet(LedgerBlock block) { long height = getLatestBlockHeight(); - TransactionSet transactionSet = null; +// TransactionSet transactionSet = null; if (height == block.getHeight()) { - // 缓存读; - LedgerState state = getLatestState(); - transactionSet = state.transactionSet; - if (transactionSet == null) { - LedgerAdminInfo adminAccount = getAdminAccount(block); - transactionSet = loadTransactionSet(block.getTransactionSetHash(), - adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, - versioningStorage, true); - state.transactionSet = transactionSet; - } - return transactionSet; - } - LedgerAdminInfo adminAccount = getAdminAccount(block); +// // 缓存最近一个区块的数据; +// LedgerState state = getLatestState(); +// transactionSet = state.transactionSet; +// if (transactionSet == null) { +// LedgerAdminInfo adminAccount = getAdminInfo(block); +// transactionSet = loadTransactionSet(block.getTransactionSetHash(), +// adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, +// true); +// state.transactionSet = transactionSet; +// } +// return transactionSet; + + // 从缓存中返回最新区块的数据集; + return latestState.getTransactionSet(); + } + LedgerAdminInfo adminAccount = getAdminInfo(block); // All of existing block is readonly; - return loadTransactionSet(block.getTransactionSetHash(), - adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, - versioningStorage, true); + return loadTransactionSet(block.getTransactionSetHash(), adminAccount.getSettings().getCryptoSetting(), + keyPrefix, exPolicyStorage, versioningStorage, true); } @Override - public LedgerAdminDataset getAdminAccount(LedgerBlock block) { + public LedgerAdminDataset getAdminInfo(LedgerBlock block) { long height = getLatestBlockHeight(); - LedgerAdminDataset adminAccount = null; +// LedgerAdminDataset adminAccount = null; if (height == block.getHeight()) { - // 缓存读; - LedgerState state = getLatestState(); - adminAccount = state.adminAccount; - if (adminAccount == null) { - adminAccount = new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, - versioningStorage, true); - state.adminAccount = adminAccount; - } - return adminAccount; +// // 缓存读; +// LedgerState state = getLatestState(); +// adminAccount = state.adminAccount; +// if (adminAccount == null) { +// adminAccount = new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, +// versioningStorage, true); +// state.adminAccount = adminAccount; +// } +// return adminAccount; + + return latestState.getAdminDataset(); } + return createAdminDataset(block); + } + + private LedgerAdminDataset createAdminDataset(LedgerBlock block) { return new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); } @Override public UserAccountSet getUserAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); - UserAccountSet userAccountSet = null; +// UserAccountSet userAccountSet = null; if (height == block.getHeight()) { - // 缓存读; - LedgerState state = getLatestState(); - userAccountSet = state.userAccountSet; - if (userAccountSet == null) { - LedgerAdminDataset adminAccount = getAdminAccount(block); - userAccountSet = loadUserAccountSet(block.getUserAccountSetHash(), - adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, - versioningStorage, true); - state.userAccountSet = userAccountSet; - } - return userAccountSet; - } - LedgerAdminDataset adminAccount = getAdminAccount(block); - return loadUserAccountSet(block.getUserAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), - keyPrefix, exPolicyStorage, versioningStorage, true); +// // 缓存读; +// LedgerState state = getLatestState(); +// userAccountSet = state.userAccountSet; +// if (userAccountSet == null) { +// LedgerAdminDataset adminAccount = getAdminInfo(block); +// userAccountSet = loadUserAccountSet(block.getUserAccountSetHash(), +// adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, +// versioningStorage, true); +// state.userAccountSet = userAccountSet; +// } +// return userAccountSet; + + return latestState.getUserAccountSet(); + } + LedgerAdminDataset adminAccount = getAdminInfo(block); + return createUserAccountSet(block, adminAccount.getSettings().getCryptoSetting()); + } + + private UserAccountSet createUserAccountSet(LedgerBlock block, CryptoSetting cryptoSetting) { + return loadUserAccountSet(block.getUserAccountSetHash(), cryptoSetting, keyPrefix, exPolicyStorage, + versioningStorage, true); } @Override public DataAccountSet getDataAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); - DataAccountSet dataAccountSet = null; +// DataAccountSet dataAccountSet = null; if (height == block.getHeight()) { - // 缓存读; - LedgerState state = getLatestState(); - dataAccountSet = state.dataAccountSet; - if (dataAccountSet == null) { - LedgerAdminDataset adminAccount = getAdminAccount(block); - dataAccountSet = loadDataAccountSet(block.getDataAccountSetHash(), - adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, - versioningStorage, true); - state.dataAccountSet = dataAccountSet; - } - return dataAccountSet; +// // 缓存读; +// LedgerState state = getLatestState(); +// dataAccountSet = state.dataAccountSet; +// if (dataAccountSet == null) { +// LedgerAdminDataset adminAccount = getAdminInfo(block); +// dataAccountSet = loadDataAccountSet(block.getDataAccountSetHash(), +// adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, +// versioningStorage, true); +// state.dataAccountSet = dataAccountSet; +// } +// return dataAccountSet; + + return latestState.getDataAccountSet(); } - LedgerAdminDataset adminAccount = getAdminAccount(block); - return loadDataAccountSet(block.getDataAccountSetHash(), adminAccount.getPreviousSetting().getCryptoSetting(), - keyPrefix, exPolicyStorage, versioningStorage, true); + LedgerAdminDataset adminAccount = getAdminInfo(block); + return createDataAccountSet(block, adminAccount.getSettings().getCryptoSetting()); + } + + private DataAccountSet createDataAccountSet(LedgerBlock block, CryptoSetting setting) { + return loadDataAccountSet(block.getDataAccountSetHash(), setting, keyPrefix, exPolicyStorage, versioningStorage, + true); } @Override public ContractAccountSet getContractAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); - ContractAccountSet contractAccountSet = null; +// ContractAccountSet contractAccountSet = null; if (height == block.getHeight()) { - // 缓存读; - LedgerState state = getLatestState(); - contractAccountSet = state.contractAccountSet; - if (contractAccountSet == null) { - LedgerAdminDataset adminAccount = getAdminAccount(block); - contractAccountSet = loadContractAccountSet(block.getContractAccountSetHash(), - adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, - versioningStorage, true); - state.contractAccountSet = contractAccountSet; - } - return contractAccountSet; +// // 缓存读; +// LedgerState state = getLatestState(); +// contractAccountSet = state.contractAccountSet; +// if (contractAccountSet == null) { +// LedgerAdminDataset adminAccount = getAdminInfo(block); +// contractAccountSet = loadContractAccountSet(block.getContractAccountSetHash(), +// adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, +// versioningStorage, true); +// state.contractAccountSet = contractAccountSet; +// } +// return contractAccountSet; + + return latestState.getContractAccountSet(); } - LedgerAdminDataset adminAccount = getAdminAccount(block); - return loadContractAccountSet(block.getContractAccountSetHash(), - adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, - true); + LedgerAdminDataset adminAccount = getAdminInfo(block); + return createContractAccountSet(block, adminAccount.getSettings().getCryptoSetting()); + } + + private ContractAccountSet createContractAccountSet(LedgerBlock block, CryptoSetting cryptoSetting) { + return loadContractAccountSet(block.getContractAccountSetHash(), cryptoSetting, keyPrefix, exPolicyStorage, + versioningStorage, true); } @Override - public LedgerDataSet getDataSet(LedgerBlock block) { + public LedgerDataset getDataSet(LedgerBlock block) { long height = getLatestBlockHeight(); - LedgerDataSet ledgerDataSet = null; +// LedgerDataSet ledgerDataSet = null; if (height == block.getHeight()) { - // 缓存读; - LedgerState state = getLatestState(); - ledgerDataSet = state.ledgerDataSet; - if (ledgerDataSet == null) { - ledgerDataSet = innerDataSet(block); - state.ledgerDataSet = ledgerDataSet; - } - return ledgerDataSet; +// // 缓存读; +// LedgerState state = getLatestState(); +// ledgerDataSet = state.ledgerDataSet; +// if (ledgerDataSet == null) { +// ledgerDataSet = innerDataSet(block); +// state.ledgerDataSet = ledgerDataSet; +// } +// return ledgerDataSet; + + return latestState.getLedgerDataset(); } // All of existing block is readonly; - return innerDataSet(block); + return innerGetLedgerDataset(block); } - private LedgerDataSet innerDataSet(LedgerBlock block) { - LedgerAdminDataset adminAccount = getAdminAccount(block); - UserAccountSet userAccountSet = getUserAccountSet(block); - DataAccountSet dataAccountSet = getDataAccountSet(block); - ContractAccountSet contractAccountSet = getContractAccountSet(block); - return new LedgerDataSetImpl(adminAccount, userAccountSet, dataAccountSet, contractAccountSet, true); + private LedgerDataset innerGetLedgerDataset(LedgerBlock block) { + LedgerAdminDataset adminDataset = createAdminDataset(block); + CryptoSetting cryptoSetting = adminDataset.getSettings().getCryptoSetting(); + + UserAccountSet userAccountSet = createUserAccountSet(block, cryptoSetting); + DataAccountSet dataAccountSet = createDataAccountSet(block, cryptoSetting); + ContractAccountSet contractAccountSet = createContractAccountSet(block, cryptoSetting); + return new LedgerDataSetImpl(adminDataset, userAccountSet, dataAccountSet, contractAccountSet, true); } @Override @@ -412,9 +441,8 @@ public class LedgerRepositoryImpl implements LedgerRepository { "A new block is in process, cann't create another one until it finish by committing or canceling."); } LedgerBlock previousBlock = getLatestBlock(); - LedgerTransactionalEditor editor = LedgerTransactionalEditor.createEditor(previousBlock, - getAdminInfo().getSettings(), keyPrefix, exPolicyStorage, - versioningStorage); + LedgerTransactionalEditor editor = LedgerTransactionalEditor.createEditor(previousBlock, getLatestSettings(), + keyPrefix, exPolicyStorage, versioningStorage); NewBlockCommittingMonitor committingMonitor = new NewBlockCommittingMonitor(editor, this); this.nextBlockEditor = committingMonitor; return committingMonitor; @@ -503,12 +531,12 @@ public class LedgerRepositoryImpl implements LedgerRepository { return transactionSet; } - static LedgerDataSetImpl loadDataSet(LedgerDataSnapshot dataSnapshot, String keyPrefix, + static LedgerDataSetImpl loadDataSet(LedgerDataSnapshot dataSnapshot, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { LedgerAdminDataset adminAccount = new LedgerAdminDataset(dataSnapshot.getAdminAccountHash(), keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); - CryptoSetting cryptoSetting = adminAccount.getPreviousSetting().getCryptoSetting(); +// CryptoSetting cryptoSetting = adminAccount.getPreviousSetting().getCryptoSetting(); UserAccountSet userAccountSet = loadUserAccountSet(dataSnapshot.getUserAccountSetHash(), cryptoSetting, keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); @@ -597,6 +625,16 @@ public class LedgerRepositoryImpl implements LedgerRepository { return editor.getBlockHeight(); } + @Override + public LedgerDataset getLedgerDataset() { + return editor.getLedgerDataset(); + } + + @Override + public TransactionSet getTransactionSet() { + return editor.getTransactionSet(); + } + @Override public LedgerTransactionContext newTransaction(TransactionRequest txRequest) { return editor.newTransaction(txRequest); @@ -612,7 +650,8 @@ public class LedgerRepositoryImpl implements LedgerRepository { try { editor.commit(); LedgerBlock latestBlock = editor.getCurrentBlock(); - ledgerRepo.latestState = new LedgerState(latestBlock); + ledgerRepo.latestState = new LedgerState(latestBlock, editor.getLedgerDataset(), + editor.getTransactionSet()); } finally { ledgerRepo.nextBlockEditor = null; } @@ -639,20 +678,39 @@ public class LedgerRepositoryImpl implements LedgerRepository { private final LedgerBlock block; - private volatile LedgerAdminDataset adminAccount; + private final TransactionSet transactionSet; + + private final LedgerDataset ledgerDataset; + + public LedgerState(LedgerBlock block, LedgerDataset ledgerDataset, TransactionSet transactionSet) { + this.block = block; + this.ledgerDataset = ledgerDataset; + this.transactionSet = transactionSet; + + } - private volatile UserAccountSet userAccountSet; + public LedgerAdminDataset getAdminDataset() { + return ledgerDataset.getAdminDataset(); + } - private volatile DataAccountSet dataAccountSet; + public LedgerDataset getLedgerDataset() { + return ledgerDataset; + } - private volatile ContractAccountSet contractAccountSet; + public ContractAccountSet getContractAccountSet() { + return ledgerDataset.getContractAccountset(); + } - private volatile TransactionSet transactionSet; + public DataAccountSet getDataAccountSet() { + return ledgerDataset.getDataAccountSet(); + } - private volatile LedgerDataSet ledgerDataSet; + public UserAccountSet getUserAccountSet() { + return ledgerDataset.getUserAccountSet(); + } - public LedgerState(LedgerBlock block) { - this.block = block; + public TransactionSet getTransactionSet() { + return transactionSet; } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java index a4feb79e..b06721e6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java @@ -13,11 +13,18 @@ import java.util.List; public interface LedgerTransactionContext { /** - * 账本数据; + * 账本数据集合; * * @return */ - LedgerDataSet getDataSet(); + LedgerDataset getDataset(); + + /** + * 事务集合; + * + * @return + */ + TransactionSet getTransactionSet(); /** * 交易请求; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java similarity index 91% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java index c23d3d5f..e6ad775b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.List; @@ -20,17 +20,14 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRollbackException; import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.core.LedgerDataSet; -import com.jd.blockchain.ledger.core.LedgerEditor; -import com.jd.blockchain.ledger.core.LedgerTransactionContext; -import com.jd.blockchain.ledger.core.SettingContext; -import com.jd.blockchain.ledger.core.TransactionSet; +import com.jd.blockchain.ledger.core.impl.LedgerBlockData; +import com.jd.blockchain.ledger.core.impl.LedgerTransactionData; +import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.storage.service.utils.BufferedKVStorage; import com.jd.blockchain.transaction.SignatureUtils; import com.jd.blockchain.transaction.TxBuilder; -import com.jd.blockchain.transaction.TxRequestBuilder; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.Base58Utils; @@ -70,10 +67,8 @@ public class LedgerTransactionalEditor implements LedgerEditor { private BufferedKVStorage baseStorage; /** - * 上一个交易的上下文; + * 上一个交易产生的账本快照; */ -// private LedgerTransactionContextImpl previousTxCtx; - private TxSnapshot previousTxSnapshot; /** @@ -81,6 +76,16 @@ public class LedgerTransactionalEditor implements LedgerEditor { */ private volatile LedgerTransactionContextImpl currentTxCtx; + /** + * 最后提交的账本数据集; + */ + private volatile LedgerDataSetImpl latestLedgerDataset; + + /** + * 最后提交的交易集合; + */ + private volatile TransactionSet latestTransactionSet; + /** * @param ledgerHash * @param cryptoSetting @@ -160,6 +165,10 @@ public class LedgerTransactionalEditor implements LedgerEditor { private void commitTxSnapshot(TxSnapshot snapshot) { previousTxSnapshot = snapshot; + latestLedgerDataset = currentTxCtx.getDataset(); + latestLedgerDataset.setReadonly(); + latestTransactionSet = currentTxCtx.getTransactionSet(); + latestTransactionSet.setReadonly(); currentTxCtx = null; } @@ -181,13 +190,23 @@ public class LedgerTransactionalEditor implements LedgerEditor { return ledgerHash; } + @Override + public LedgerDataset getLedgerDataset() { + return latestLedgerDataset; + } + + @Override + public TransactionSet getTransactionSet() { + return latestTransactionSet; + } + /** * 检查当前账本是否是指定交易请求的账本; * * @param txRequest * @return */ - private boolean isRequestedLedger(TransactionRequest txRequest) { + private boolean isRequestMatched(TransactionRequest txRequest) { HashDigest reqLedgerHash = txRequest.getTransactionContent().getLedgerHash(); if (ledgerHash == reqLedgerHash) { return true; @@ -226,7 +245,8 @@ public class LedgerTransactionalEditor implements LedgerEditor { @Override public synchronized LedgerTransactionContext newTransaction(TransactionRequest txRequest) { - if (SettingContext.txSettings().verifyLedger() && !isRequestedLedger(txRequest)) { +// if (SettingContext.txSettings().verifyLedger() && !isRequestMatched(txRequest)) { + if (!isRequestMatched(txRequest)) { throw new IllegalTransactionException( "Transaction request is dispatched to a wrong ledger! --[TxHash=" + txRequest.getTransactionContent().getHash() + "]!", @@ -234,7 +254,8 @@ public class LedgerTransactionalEditor implements LedgerEditor { } // TODO: 把验签和创建交易并行化; - if (SettingContext.txSettings().verifySignature() && !verifyTxContent(txRequest)) { +// if (SettingContext.txSettings().verifySignature() && !verifyTxContent(txRequest)) { + if (!verifyTxContent(txRequest)) { // 抛弃哈希和签名校验失败的交易请求; throw new IllegalTransactionException( "Wrong transaction signature! --[TxHash=" + txRequest.getTransactionContent().getHash() + "]!", @@ -262,18 +283,18 @@ public class LedgerTransactionalEditor implements LedgerEditor { GenesisSnapshot snpht = (GenesisSnapshot) startingPoint; txDataset = LedgerRepositoryImpl.newDataSet(snpht.initSetting, ledgerKeyPrefix, txBufferedStorage, txBufferedStorage); - txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminAccount().getSettings(), + txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminDataset().getSettings(), ledgerKeyPrefix, txBufferedStorage, txBufferedStorage); } else if (startingPoint instanceof TxSnapshot) { // 新的区块; // TxSnapshot; reload dataset and txset; TxSnapshot snpht = (TxSnapshot) startingPoint; // load dataset; - txDataset = LedgerRepositoryImpl.loadDataSet(snpht.dataSnapshot, ledgerKeyPrefix, txBufferedStorage, - txBufferedStorage, false); + txDataset = LedgerRepositoryImpl.loadDataSet(snpht.dataSnapshot, cryptoSetting, ledgerKeyPrefix, + txBufferedStorage, txBufferedStorage, false); // load txset; - txset = LedgerRepositoryImpl.loadTransactionSet(snpht.txsetHash, this.cryptoSetting, ledgerKeyPrefix, + txset = LedgerRepositoryImpl.loadTransactionSet(snpht.txsetHash, cryptoSetting, ledgerKeyPrefix, txBufferedStorage, txBufferedStorage, false); } else { // Unreachable; @@ -283,11 +304,11 @@ public class LedgerTransactionalEditor implements LedgerEditor { } else { // Reuse previous object to optimize performance; // load dataset; - txDataset = LedgerRepositoryImpl.loadDataSet(previousTxSnapshot.dataSnapshot, ledgerKeyPrefix, - txBufferedStorage, txBufferedStorage, false); + txDataset = LedgerRepositoryImpl.loadDataSet(previousTxSnapshot.dataSnapshot, cryptoSetting, + ledgerKeyPrefix, txBufferedStorage, txBufferedStorage, false); // load txset; - txset = LedgerRepositoryImpl.loadTransactionSet(previousTxSnapshot.txsetHash, this.cryptoSetting, + txset = LedgerRepositoryImpl.loadTransactionSet(previousTxSnapshot.txsetHash, cryptoSetting, ledgerKeyPrefix, txBufferedStorage, txBufferedStorage, false); } @@ -476,28 +497,6 @@ public class LedgerTransactionalEditor implements LedgerEditor { } -// /** -// * 账本的数据上下文; -// * -// * @author huanghaiquan -// * -// */ -// private static class LedgerDataContext { -// -// protected LedgerDataSetImpl dataset; -// -// protected TransactionSet txset; -// -// protected BufferedKVStorage storage; -// -// public LedgerDataContext(LedgerDataSetImpl dataset, TransactionSet txset, BufferedKVStorage storage) { -// this.dataset = dataset; -// this.txset = txset; -// this.storage = storage; -// } -// -// } - /** * 交易的上下文; * @@ -534,10 +533,15 @@ public class LedgerTransactionalEditor implements LedgerEditor { } @Override - public LedgerDataSet getDataSet() { + public LedgerDataSetImpl getDataset() { return dataset; } + @Override + public TransactionSet getTransactionSet() { + return txset; + } + @Override public TransactionRequest getTransactionRequest() { return txRequest; @@ -620,8 +624,8 @@ public class LedgerTransactionalEditor implements LedgerEditor { private TransactionStagedSnapshot takeDataSnapshot() { TransactionStagedSnapshot txDataSnapshot = new TransactionStagedSnapshot(); - txDataSnapshot.setAdminAccountHash(dataset.getAdminAccount().getHash()); - txDataSnapshot.setContractAccountSetHash(dataset.getContractAccountSet().getRootHash()); + txDataSnapshot.setAdminAccountHash(dataset.getAdminDataset().getHash()); + txDataSnapshot.setContractAccountSetHash(dataset.getContractAccountset().getRootHash()); txDataSnapshot.setDataAccountSetHash(dataset.getDataAccountSet().getRootHash()); txDataSnapshot.setUserAccountSetHash(dataset.getUserAccountSet().getRootHash()); return txDataSnapshot; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java index 30846867..8b94b5a1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java @@ -124,6 +124,10 @@ public class MerkleDataSet implements Transactional, MerkleProvable { return readonly; } + void setReadonly() { + this.readonly = true; + } + public long getDataCount() { return merkleTree.getDataCount(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java index e1a0f567..ba7c5b83 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java @@ -30,8 +30,8 @@ public interface OperationHandle { * * @return 操作执行结果 */ - BytesValue process(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); + BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); // /** // * 异步解析和执行操作; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java index 962244e5..c96ce3a2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java @@ -153,6 +153,10 @@ public class TransactionSet implements Transactional, MerkleProvable { public boolean isReadonly() { return txSet.isReadonly(); } + + void setReadonly() { + txSet.setReadonly(); + } @Override public boolean isUpdated() { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java index d28c15ed..d6100ea6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java @@ -46,6 +46,10 @@ public class UserAccountSet implements Transactional, MerkleProvable { public boolean isReadonly() { return accountSet.isReadonly(); } + + void setReadonly() { + accountSet.setReadonly(); + } @Override public HashDigest getRootHash() { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java index 77cb4e2f..d4b79319 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java @@ -14,6 +14,8 @@ import com.jd.blockchain.ledger.core.LedgerConsts; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerRepositoryImpl; +import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.KVStorageService; import com.jd.blockchain.storage.service.VersioningKVStorage; @@ -69,7 +71,7 @@ public class LedgerManager implements LedgerManage { ledgerVersioningStorage); // 校验 crypto service provider ; - CryptoSetting cryptoSetting = ledgerRepo.getAdminAccount().getSettings().getCryptoSetting(); + CryptoSetting cryptoSetting = ledgerRepo.getAdminInfo().getSettings().getCryptoSetting(); checkCryptoSetting(cryptoSetting, ledgerHash); // 创建账本上下文; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java index 2433b9d2..af52f39b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java @@ -44,7 +44,7 @@ public class LedgerQueryService implements BlockchainQueryService { public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - LedgerAdminInfo administration = ledger.getAdminAccount(block); + LedgerAdminInfo administration = ledger.getAdminInfo(block); return administration; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionContextImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionContextImpl.java deleted file mode 100644 index c8c09d93..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionContextImpl.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.jd.blockchain.ledger.core.impl; - -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.LedgerTransaction; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.core.LedgerDataSet; -import com.jd.blockchain.ledger.core.LedgerTransactionContext; -import com.jd.blockchain.storage.service.utils.BufferedKVStorage; - diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java index aa8fcf93..750020da 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java @@ -23,7 +23,7 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionRollbackException; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserDoesNotExistException; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.LedgerTransactionContext; @@ -43,7 +43,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { private LedgerEditor newBlockEditor; - private LedgerDataSet previousBlockDataset; + private LedgerDataset previousBlockDataset; private OperationHandleRegisteration opHandles; @@ -61,7 +61,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { * @param previousBlockDataset 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; * @param opHandles 操作处理对象注册表; */ - public TransactionBatchProcessor(LedgerEditor newBlockEditor, LedgerDataSet previousBlockDataset, + public TransactionBatchProcessor(LedgerEditor newBlockEditor, LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService) { this.newBlockEditor = newBlockEditor; this.previousBlockDataset = previousBlockDataset; @@ -135,7 +135,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { TransactionState result; List operationResults = new ArrayList<>(); try { - LedgerDataSet dataset = txCtx.getDataSet(); + LedgerDataset dataset = txCtx.getDataset(); TransactionRequestContext reqCtx = new TransactionRequestContextImpl(request); // TODO: 验证签名者的有效性; for (Bytes edpAddr : reqCtx.getEndpoints()) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java index bf2470e4..c79f55f8 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java @@ -7,7 +7,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; import org.springframework.beans.factory.annotation.Autowired; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerService; @@ -44,7 +44,7 @@ public class TransactionEngineImpl implements TransactionEngine { LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); batch = new InnerTransactionBatchProcessor(ledgerHash, newBlockEditor, previousBlockDataset, opHdlRegs, ledgerService, ledgerBlock.getHeight()); batchs.put(ledgerHash, batch); @@ -79,7 +79,7 @@ public class TransactionEngineImpl implements TransactionEngine { * 操作处理对象注册表; */ public InnerTransactionBatchProcessor(HashDigest ledgerHash, LedgerEditor newBlockEditor, - LedgerDataSet previousBlockDataset, OperationHandleRegisteration opHandles, + LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService, long blockHeight) { super(newBlockEditor, previousBlockDataset, opHandles, ledgerService); this.ledgerHash = ledgerHash; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java index 40f6e2c2..0ab721eb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java @@ -10,7 +10,7 @@ import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.ContractAccount; import com.jd.blockchain.ledger.core.ContractAccountSet; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.TransactionRequestContext; @@ -26,12 +26,12 @@ public abstract class AbtractContractEventHandle implements OperationHandle { } @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { ContractEventSendOperation contractOP = (ContractEventSendOperation) op; // 先从账本校验合约的有效性; // 注意:必须在前一个区块的数据集中进行校验,因为那是经过共识的数据;从当前新区块链数据集校验则会带来攻击风险:未经共识的合约得到执行; - ContractAccountSet contractSet = previousBlockDataset.getContractAccountSet(); + ContractAccountSet contractSet = previousBlockDataset.getContractAccountset(); if (!contractSet.contains(contractOP.getContractAddress())) { throw new LedgerException(String.format("Contract was not registered! --[ContractAddress=%s]", contractOP.getContractAddress())); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java index e18c9304..b17c97e6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Service; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.TransactionRequestContext; @@ -15,14 +15,14 @@ import com.jd.blockchain.ledger.core.impl.OperationHandleContext; public class ContractCodeDeployOperationHandle implements OperationHandle { @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { ContractCodeDeployOperation contractOP = (ContractCodeDeployOperation) op; // TODO: 校验合约代码的正确性; // TODO: 请求者应该提供合约账户的公钥签名,已确定注册的地址的唯一性; - dataset.getContractAccountSet().deploy(contractOP.getContractID().getAddress(), + dataset.getContractAccountset().deploy(contractOP.getContractID().getAddress(), contractOP.getContractID().getPubKey(), contractOP.getAddressSignature(), contractOP.getChainCode()); return null; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java index 2745a377..51a24127 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java @@ -9,7 +9,7 @@ import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.TransactionRequestContext; @@ -23,8 +23,8 @@ public class DataAccountKVSetOperationHandle implements OperationHandle { } @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { DataAccountKVSetOperation kvWriteOp = (DataAccountKVSetOperation) op; DataAccount account = dataset.getDataAccountSet().getDataAccount(kvWriteOp.getAccountAddress()); if (account == null) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java index 69337dfa..20883c8f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java @@ -6,7 +6,7 @@ import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.TransactionRequestContext; @@ -16,8 +16,8 @@ import com.jd.blockchain.ledger.core.impl.OperationHandleContext; public class DataAccountRegisterOperationHandle implements OperationHandle { @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { DataAccountRegisterOperation dataAccountRegOp = (DataAccountRegisterOperation) op; BlockchainIdentity bid = dataAccountRegOp.getAccountID(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java index f583e8cd..85b556f0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java @@ -4,7 +4,7 @@ import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.TransactionRequestContext; @@ -15,8 +15,8 @@ import com.jd.blockchain.utils.Bytes; public class UserRegisterOperationHandle implements OperationHandle { @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { UserRegisterOperation userRegOp = (UserRegisterOperation) op; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index fd9df2d4..85d649de 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -7,7 +7,6 @@ import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; @@ -72,7 +71,7 @@ public class ContractInvokingTest { // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); @@ -127,7 +126,7 @@ public class ContractInvokingTest { BlockchainKeypair contractKey) { // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); @@ -160,7 +159,7 @@ public class ContractInvokingTest { TransactionRequest genesisTxReq = LedgerTestUtils.createLedgerInitTxRequest(partiKeys); LedgerTransactionContext genisisTxCtx = ldgEdt.newTransaction(genesisTxReq); - LedgerDataSet ldgDS = genisisTxCtx.getDataSet(); + LedgerDataset ldgDS = genisisTxCtx.getDataset(); for (int i = 0; i < partiKeys.length; i++) { UserAccount userAccount = ldgDS.getUserAccountSet().register(partiKeys[i].getAddress(), diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java index 0779204e..9eff5b16 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java @@ -27,11 +27,11 @@ import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerTransactionContext; +import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.UserAccount; -import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitSettingData; @@ -95,7 +95,7 @@ public class LedgerEditorTest { public void testWriteDataAccoutKvOp() { LedgerEditor ldgEdt = createLedgerInitEditor(); LedgerTransactionContext genisisTxCtx = createGenisisTx(ldgEdt, participants); - LedgerDataSet ldgDS = genisisTxCtx.getDataSet(); + LedgerDataset ldgDS = genisisTxCtx.getDataset(); AsymmetricKeypair cryptoKeyPair = signatureFunction.generateKeypair(); BlockchainKeypair dataKP = new BlockchainKeypair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); @@ -128,7 +128,7 @@ public class LedgerEditorTest { public void testGennesisBlockCreation() { LedgerEditor ldgEdt = createLedgerInitEditor(); LedgerTransactionContext genisisTxCtx = createGenisisTx(ldgEdt, participants); - LedgerDataSet ldgDS = genisisTxCtx.getDataSet(); + LedgerDataset ldgDS = genisisTxCtx.getDataset(); AsymmetricKeypair cryptoKeyPair = signatureFunction.generateKeypair(); BlockchainKeypair userKP = new BlockchainKeypair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java index f2021d99..dfa0e730 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java @@ -35,7 +35,7 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DataAccountSet; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerTransactionContext; @@ -94,7 +94,7 @@ public class LedgerManagerTest { // 记录交易,注册用户; LedgerTransactionContext txCtx = ldgEdt.newTransaction(genesisTxReq); - LedgerDataSet ldgDS = txCtx.getDataSet(); + LedgerDataset ldgDS = txCtx.getDataset(); BlockchainKeypair userKP = BlockchainKeyGenerator.getInstance().generate(); UserAccount userAccount = ldgDS.getUserAccountSet().register(userKP.getAddress(), userKP.getPubKey()); @@ -121,6 +121,9 @@ public class LedgerManagerTest { // 提交数据,写入存储; ldgEdt.commit(); + + assertNull(genesisBlock.getLedgerHash()); + assertNotNull(genesisBlock.getHash()); // 重新加载并校验结果; LedgerManager reloadLedgerManager = new LedgerManager(); @@ -145,7 +148,7 @@ public class LedgerManagerTest { TransactionRequest txRequest = txReqBuilder.buildRequest(); LedgerTransactionContext txCtx1 = editor1.newTransaction(txRequest); - txCtx1.getDataSet().getDataAccountSet().register(dataKey.getAddress(), dataKey.getPubKey(), null); + txCtx1.getDataset().getDataAccountSet().register(dataKey.getAddress(), dataKey.getPubKey(), null); txCtx1.commit(TransactionState.SUCCESS); LedgerBlock block1 = editor1.prepare(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java index f857a6ad..6f8e90f0 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java @@ -26,14 +26,14 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerTransactionContext; +import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; @@ -75,7 +75,7 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); @@ -120,7 +120,7 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); @@ -158,7 +158,7 @@ public class TransactionBatchProcessorTest { assertEquals(newBlock.getHash(), latestBlock.getHash()); assertEquals(1, newBlock.getHeight()); - LedgerDataSet ledgerDS = ledgerRepo.getDataSet(latestBlock); + LedgerDataset ledgerDS = ledgerRepo.getDataSet(latestBlock); boolean existUser1 = ledgerDS.getUserAccountSet().contains(userKeypair1.getAddress()); boolean existUser2 = ledgerDS.getUserAccountSet().contains(userKeypair2.getAddress()); assertTrue(existUser1); @@ -177,7 +177,7 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); @@ -235,7 +235,7 @@ public class TransactionBatchProcessorTest { assertNotNull(tx3); assertEquals(TransactionState.SUCCESS, tx3.getExecutionState()); - LedgerDataSet ledgerDS = ledgerRepo.getDataSet(latestBlock); + LedgerDataset ledgerDS = ledgerRepo.getDataSet(latestBlock); boolean existUser1 = ledgerDS.getUserAccountSet().contains(userKeypair1.getAddress()); boolean existUser2 = ledgerDS.getUserAccountSet().contains(userKeypair2.getAddress()); boolean existUser3 = ledgerDS.getUserAccountSet().contains(userKeypair3.getAddress()); @@ -256,7 +256,7 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); @@ -378,7 +378,7 @@ public class TransactionBatchProcessorTest { TransactionRequest genesisTxReq = LedgerTestUtils.createLedgerInitTxRequest(partiKeys); LedgerTransactionContext genisisTxCtx = ldgEdt.newTransaction(genesisTxReq); - LedgerDataSet ldgDS = genisisTxCtx.getDataSet(); + LedgerDataset ldgDS = genisisTxCtx.getDataset(); for (int i = 0; i < partiKeys.length; i++) { UserAccount userAccount = ldgDS.getUserAccountSet().register(partiKeys[i].getAddress(), diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java index ca09e1f5..035db6f2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java @@ -1,29 +1,17 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.binaryproto.DataContract; -import com.jd.blockchain.binaryproto.DataField; -import com.jd.blockchain.binaryproto.PrimitiveType; -import com.jd.blockchain.consts.DataCodes; - -@DataContract(code = DataCodes.LEDGER_ADMIN_INFO, name = "LEDGER-ADMIN-INFO") public interface LedgerAdminInfo { - @DataField(order = 1, refContract = true) LedgerMetadata_V2 getMetadata(); - @DataField(order = 2, refContract = true) LedgerSettings getSettings(); - @DataField(order = 3, primitiveType = PrimitiveType.INT64) long getParticipantCount(); - @DataField(order = 4, refContract = true, list = true) ParticipantNode[] getParticipants(); - @DataField(order = 5, refContract = true) UserRoleSettings getUserRoles(); - @DataField(order = 6, refContract = true) RolePrivilegeSettings getRolePrivileges(); } \ No newline at end of file diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index a7e07ace..7449e248 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -226,7 +226,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag LedgerRepository ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); // load provider; - LedgerAdminInfo ledgerAdminAccount = ledgerRepository.getAdminAccount(); + LedgerAdminInfo ledgerAdminAccount = ledgerRepository.getAdminInfo(); String consensusProvider = ledgerAdminAccount.getSettings().getConsensusProvider(); ConsensusProvider provider = ConsensusProviders.getProvider(consensusProvider); // find current node; diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java index ce6175d6..ff1c9176 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/BlockchainServiceProxy.java @@ -1,7 +1,19 @@ package com.jd.blockchain.sdk.proxy; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.ContractInfo; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.KVInfoVO; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.TransactionTemplate; +import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.sdk.BlockchainEventHandle; import com.jd.blockchain.sdk.BlockchainEventListener; import com.jd.blockchain.sdk.BlockchainService; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index 76f3a1e7..ef0f76e6 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; @@ -278,7 +278,7 @@ public class LedgerPerformanceTest { ConsoleUtils.info("\r\n\r\n================= 准备测试交易 [执行合约] ================="); LedgerBlock latestBlock = ledger.getLatestBlock(); - LedgerDataSet previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); LedgerEditor newEditor = ledger.createNextBlock(); TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, previousDataSet, opHandler, ledgerManager); @@ -311,7 +311,7 @@ public class LedgerPerformanceTest { long batchStartTs = System.currentTimeMillis(); for (int i = 0; i < batchCount; i++) { LedgerBlock latestBlock = ledger.getLatestBlock(); - LedgerDataSet previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); if (statistic) { ConsoleUtils.info("------ 开始执行交易, 即将生成区块[高度:%s] ------", (latestBlock.getHeight() + 1)); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index 306d065f..a57bf7a6 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -24,7 +24,7 @@ import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; @@ -76,7 +76,7 @@ public class LedgerBlockGeneratingTest { LedgerBlock latestBlock = ledger.getLatestBlock(); assertEquals(height + i, latestBlock.getHeight()); - LedgerDataSet previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); ConsoleUtils.info("------ 开始执行交易, 即将生成区块[%s] ------", (latestBlock.getHeight() + 1)); long startTs = System.currentTimeMillis(); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index 09de6518..4fdc4f85 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -423,7 +423,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI // TODO:暂时只支持注册用户的初始化操作; for (int i = 1; i < ops.length; i++) { UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; - txCtx.getDataSet().getUserAccountSet().register(userRegOP.getUserID().getAddress(), + txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), userRegOP.getUserID().getPubKey()); } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java index 962c79ae..7af0d55f 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java @@ -286,7 +286,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon // TODO:暂时只支持注册用户的初始化操作; for (int i = 1; i < ops.length; i++) { UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; - txCtx.getDataSet().getUserAccountSet().register(userRegOP.getUserID().getAddress(), + txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), userRegOP.getUserID().getPubKey()); } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 776554cf..f88ff7b6 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -51,7 +51,7 @@ import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.impl.LedgerManager; @@ -122,7 +122,7 @@ public class MockerNodeContext implements BlockchainQueryService { DataContractRegistry.register(ClientIdentifications.class); DataContractRegistry.register(ClientIdentification.class); - DataContractRegistry.register(LedgerAdminInfo.class); +// DataContractRegistry.register(LedgerAdminInfo.class); ByteArrayObjectUtil.init(); } @@ -442,7 +442,7 @@ public class MockerNodeContext implements BlockchainQueryService { public OperationResult[] txProcess(TransactionRequest txRequest) { LedgerEditor newEditor = ledgerRepository.createNextBlock(); LedgerBlock latestBlock = ledgerRepository.getLatestBlock(); - LedgerDataSet previousDataSet = ledgerRepository.getDataSet(latestBlock); + LedgerDataset previousDataSet = ledgerRepository.getDataSet(latestBlock); TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, previousDataSet, opHandler, ledgerManager); TransactionResponse txResp = txProc.schedule(txRequest); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index cb26ac92..a661be6f 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -10,7 +10,7 @@ import com.jd.blockchain.contract.EventProcessingAware; import com.jd.blockchain.contract.LedgerContext; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.TransactionRequestContext; @@ -29,8 +29,8 @@ public class MockerContractExeHandle implements OperationHandle { private HashDigest ledgerHash; @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { ContractEventSendOperation contractOP = (ContractEventSendOperation) op; HashDigest txHash = requestContext.getRequest().getTransactionContent().getHash(); From 9bc12aa5708e4f0e42307852f9ecbb33b16e4a86 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 23 Aug 2019 11:02:29 +0800 Subject: [PATCH 047/124] Renamed packages of ledger-core; --- .../DefaultOperationHandleRegisteration.java | 13 +++++----- .../{impl => }/GenesisLedgerStorageProxy.java | 1 + .../core/{impl => }/LedgerBlockData.java | 2 +- .../ledger/core/{impl => }/LedgerManager.java | 8 +----- .../core/{impl => }/LedgerQueryService.java | 25 ++++++++++++------- .../ledger/core/LedgerRepositoryImpl.java | 2 -- .../{impl => }/LedgerTransactionData.java | 2 +- .../core/LedgerTransactionalEditor.java | 3 --- .../core/{impl => }/OpeningAccessPolicy.java | 3 +-- .../ledger/core/OperationHandle.java | 1 - .../{impl => }/OperationHandleContext.java | 2 +- .../OperationHandleRegisteration.java | 4 +-- .../{impl => }/TransactionBatchProcessor.java | 8 +----- .../{impl => }/TransactionEngineImpl.java | 6 +---- .../TransactionRequestContextImpl.java | 3 +-- .../{impl => }/TransactionStagedSnapshot.java | 2 +- .../handles/AbtractContractEventHandle.java | 6 ++--- .../ContractCodeDeployOperationHandle.java | 4 +-- .../handles/ContractLedgerContext.java | 4 +-- .../DataAccountKVSetOperationHandle.java | 4 +-- .../DataAccountRegisterOperationHandle.java | 4 +-- .../JVMContractEventSendOperationHandle.java | 2 +- .../handles/UserRegisterOperationHandle.java | 4 +-- .../ledger/{ => core}/AccountSetTest.java | 4 +-- .../ledger/{ => core}/BaseAccountTest.java | 2 +- .../{ => core}/ContractInvokingHandle.java | 4 +-- .../{ => core}/ContractInvokingTest.java | 5 +--- .../ledger/{ => core}/LedgerAccountTest.java | 2 +- .../LedgerAdminDatasetTest.java} | 4 +-- .../{ => core}/LedgerBlockImplTest.java | 6 ++--- .../ledger/{ => core}/LedgerEditorTest.java | 2 +- .../{ => core}/LedgerInitOperationTest.java | 2 +- .../LedgerInitSettingSerializeTest.java | 2 +- .../ledger/{ => core}/LedgerManagerTest.java | 4 +-- .../ledger/{ => core}/LedgerMetaDataTest.java | 2 +- .../ledger/{ => core}/LedgerTestUtils.java | 4 +-- .../{ => core}/LedgerTransactionDataTest.java | 6 ++--- .../ledger/{ => core}/MerkleDataSetTest.java | 2 +- .../ledger/{ => core}/MerkleTreeTest.java | 2 +- .../{ => core}/RolePrivilegeDatasetTest.java | 2 +- .../ledger/{ => core}/TestContract.java | 2 +- .../ledger/{ => core}/TestContractImpl.java | 2 +- .../TransactionBatchProcessorTest.java | 10 ++++---- .../ledger/{ => core}/TransactionSetTest.java | 6 ++--- .../TransactionStagedSnapshotTest.java | 4 +-- .../{ => core}/UserRoleDatasetTest.java | 2 +- .../peer/ledger/LedgerConfigurer.java | 8 +++--- .../blockchain/intgr/IntegratedContext.java | 2 +- .../jd/blockchain/intgr/IntegrationTest.java | 2 +- .../intgr/consensus/ConsensusTest.java | 2 +- .../intgr/perf/GlobalPerformanceTest.java | 2 +- .../intgr/perf/LedgerInitializeTest.java | 2 +- .../intgr/perf/LedgerInitializeWebTest.java | 2 +- .../intgr/perf/LedgerPerformanceTest.java | 7 +++--- .../com/jd/blockchain/intgr/perf/Utils.java | 2 +- .../jd/blockchain/intgr/IntegrationBase.java | 2 +- .../intgr/IntegrationTestAll4Redis.java | 2 +- .../intgr/IntegrationTestDataAccount.java | 2 +- .../initializer/LedgerInitializeTest.java | 2 +- .../LedgerInitializeWeb4Nodes.java | 1 - .../LedgerInitializeWeb4SingleStepsTest.java | 2 +- .../ledger/LedgerBlockGeneratingTest.java | 6 ++--- .../tools/initializer/LedgerInitCommand.java | 2 +- .../web/InitWebServerConfiguration.java | 2 +- .../mocker/MockerLedgerInitializer.java | 1 - .../blockchain/mocker/MockerNodeContext.java | 6 ++--- .../handler/MockerContractExeHandle.java | 8 +++--- .../MockerOperationHandleRegister.java | 10 ++++---- .../mocker/node/NodeWebContext.java | 2 +- 69 files changed, 124 insertions(+), 147 deletions(-) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/DefaultOperationHandleRegisteration.java (73%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/GenesisLedgerStorageProxy.java (97%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerBlockData.java (98%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerManager.java (94%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerQueryService.java (95%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/LedgerTransactionData.java (99%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/OpeningAccessPolicy.java (82%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/OperationHandleContext.java (74%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/OperationHandleRegisteration.java (50%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/TransactionBatchProcessor.java (97%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/TransactionEngineImpl.java (92%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/TransactionRequestContextImpl.java (95%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/TransactionStagedSnapshot.java (96%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/AbtractContractEventHandle.java (94%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/ContractCodeDeployOperationHandle.java (93%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/ContractLedgerContext.java (99%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/DataAccountKVSetOperationHandle.java (94%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/DataAccountRegisterOperationHandle.java (93%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/JVMContractEventSendOperationHandle.java (96%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{impl => }/handles/UserRegisterOperationHandle.java (90%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/AccountSetTest.java (96%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/BaseAccountTest.java (98%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/ContractInvokingHandle.java (93%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/ContractInvokingTest.java (96%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerAccountTest.java (97%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{LedgerAdminAccountTest.java => core/LedgerAdminDatasetTest.java} (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerBlockImplTest.java (96%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerEditorTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerInitOperationTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerInitSettingSerializeTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerManagerTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerMetaDataTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerTestUtils.java (98%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/LedgerTransactionDataTest.java (98%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/MerkleDataSetTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/MerkleTreeTest.java (99%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/RolePrivilegeDatasetTest.java (98%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/TestContract.java (96%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/TestContractImpl.java (94%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/TransactionBatchProcessorTest.java (98%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/TransactionSetTest.java (98%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/TransactionStagedSnapshotTest.java (94%) rename source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/{ => core}/UserRoleDatasetTest.java (98%) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java similarity index 73% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java index 1966c716..15d5a0f1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.ArrayList; import java.util.List; @@ -6,12 +6,11 @@ import java.util.List; import org.springframework.stereotype.Component; import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.ledger.core.OperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.ContractCodeDeployOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.JVMContractEventSendOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.DataAccountKVSetOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.DataAccountRegisterOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.UserRegisterOperationHandle; +import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; +import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; +import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; +import com.jd.blockchain.ledger.core.handles.JVMContractEventSendOperationHandle; +import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; @Component public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/GenesisLedgerStorageProxy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/GenesisLedgerStorageProxy.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/GenesisLedgerStorageProxy.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/GenesisLedgerStorageProxy.java index 5682c50a..c93e1fed 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/GenesisLedgerStorageProxy.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/GenesisLedgerStorageProxy.java @@ -1,3 +1,4 @@ +package com.jd.blockchain.ledger.core; //package com.jd.blockchain.ledger.core.impl; // //import com.jd.blockchain.storage.service.ExPolicyKVStorage; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerBlockData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerBlockData.java similarity index 98% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerBlockData.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerBlockData.java index 1e8865b7..3c667ebe 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerBlockData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerBlockData.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java index d4b79319..aad657fc 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.HashMap; import java.util.Map; @@ -10,12 +10,6 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.core.LedgerConsts; -import com.jd.blockchain.ledger.core.LedgerEditor; -import com.jd.blockchain.ledger.core.LedgerManage; -import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.LedgerRepositoryImpl; -import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.KVStorageService; import com.jd.blockchain.storage.service.VersioningKVStorage; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java similarity index 95% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java index af52f39b..da140171 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java @@ -1,18 +1,25 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.ArrayList; import java.util.List; import com.jd.blockchain.contract.ContractException; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.ContractAccountSet; -import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.DataAccountSet; -import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.TransactionSet; -import com.jd.blockchain.ledger.core.UserAccountSet; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.ContractInfo; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.KVDataObject; +import com.jd.blockchain.ledger.KVDataVO; +import com.jd.blockchain.ledger.KVInfoVO; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.QueryUtil; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 0b973a69..66405cf4 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -12,8 +12,6 @@ import com.jd.blockchain.ledger.LedgerDataSnapshot; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.core.impl.LedgerBlockData; -import com.jd.blockchain.ledger.core.impl.OpeningAccessPolicy; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionData.java similarity index 99% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionData.java index 432c24e1..516f338f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionData.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.DigitalSignature; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java index e6ad775b..4b9f33b5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java @@ -20,9 +20,6 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRollbackException; import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.core.impl.LedgerBlockData; -import com.jd.blockchain.ledger.core.impl.LedgerTransactionData; -import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.storage.service.utils.BufferedKVStorage; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OpeningAccessPolicy.java similarity index 82% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OpeningAccessPolicy.java index 14c5b9b4..6628aad3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OpeningAccessPolicy.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OpeningAccessPolicy.java @@ -1,8 +1,7 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.core.AccountAccessPolicy; import com.jd.blockchain.utils.Bytes; /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java index ba7c5b83..02d0f453 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java @@ -2,7 +2,6 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; public interface OperationHandle { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OperationHandleContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java similarity index 74% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OperationHandleContext.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java index 6e702e0b..ab169976 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OperationHandleContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.Operation; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java similarity index 50% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OperationHandleRegisteration.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java index 169c5249..2953f945 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/OperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java @@ -1,6 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; - -import com.jd.blockchain.ledger.core.OperationHandle; +package com.jd.blockchain.ledger.core; public interface OperationHandleRegisteration { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java similarity index 97% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index 750020da..599041d0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.ArrayList; import java.util.Iterator; @@ -23,12 +23,6 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionRollbackException; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserDoesNotExistException; -import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerEditor; -import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.LedgerTransactionContext; -import com.jd.blockchain.ledger.core.OperationHandle; -import com.jd.blockchain.ledger.core.TransactionRequestContext; import com.jd.blockchain.service.TransactionBatchProcess; import com.jd.blockchain.service.TransactionBatchResult; import com.jd.blockchain.service.TransactionBatchResultHandle; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java similarity index 92% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index c79f55f8..c81cb1f9 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -7,10 +7,6 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; import org.springframework.beans.factory.annotation.Autowired; -import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerEditor; -import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.service.TransactionBatchProcess; import com.jd.blockchain.service.TransactionEngine; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionRequestContextImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java similarity index 95% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionRequestContextImpl.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java index ec53360a..3348a342 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionRequestContextImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import java.util.HashMap; import java.util.Map; @@ -7,7 +7,6 @@ import java.util.Set; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.core.TransactionRequestContext; import com.jd.blockchain.utils.Bytes; /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionStagedSnapshot.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionStagedSnapshot.java similarity index 96% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionStagedSnapshot.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionStagedSnapshot.java index 14d8e8a4..cee5487f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionStagedSnapshot.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionStagedSnapshot.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl; +package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.LedgerDataSnapshot; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java index 0ab721eb..09b25e28 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import org.springframework.stereotype.Service; @@ -11,11 +11,11 @@ import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.ContractAccount; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.LedgerQueryService; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; @Service public abstract class AbtractContractEventHandle implements OperationHandle { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java similarity index 93% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java index b17c97e6..c4ae6ce7 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import org.springframework.stereotype.Service; @@ -8,8 +8,8 @@ import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; @Service public class ContractCodeDeployOperationHandle implements OperationHandle { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractLedgerContext.java similarity index 99% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractLedgerContext.java index 0aa0e3c9..0d0d3c3d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractLedgerContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractLedgerContext.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import java.util.ArrayList; import java.util.List; @@ -6,7 +6,7 @@ import java.util.List; import com.jd.blockchain.contract.LedgerContext; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.transaction.DataAccountKVSetOperationBuilder; import com.jd.blockchain.transaction.DataAccountRegisterOperationBuilder; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index 51a24127..c3bc832d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import org.springframework.stereotype.Service; @@ -12,8 +12,8 @@ import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; import com.jd.blockchain.utils.Bytes; @Service diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java similarity index 93% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java index 20883c8f..4b4c87b0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import org.springframework.stereotype.Service; @@ -9,8 +9,8 @@ import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; @Service public class DataAccountRegisterOperationHandle implements OperationHandle { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java similarity index 96% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java index da4da430..5107ffd5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.contract.engine.ContractCode; import com.jd.blockchain.contract.engine.ContractEngine; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java similarity index 90% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java index 85b556f0..bceaaa2a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/UserRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BytesValue; @@ -7,8 +7,8 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java similarity index 96% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java index 5b69b118..d3e565a5 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/AccountSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -17,7 +17,7 @@ import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.core.AccountSet; import com.jd.blockchain.ledger.core.BaseAccount; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.impl.OpeningAccessPolicy; +import com.jd.blockchain.ledger.core.OpeningAccessPolicy; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; public class AccountSetTest { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java index 4a641b7d..d02f9b11 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/BaseAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java similarity index 93% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java index c0a31321..83883d06 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -8,7 +8,7 @@ import com.jd.blockchain.contract.engine.ContractCode; import com.jd.blockchain.contract.jvm.AbstractContractCode; import com.jd.blockchain.contract.jvm.ContractDefinition; import com.jd.blockchain.ledger.core.ContractAccount; -import com.jd.blockchain.ledger.core.impl.handles.AbtractContractEventHandle; +import com.jd.blockchain.ledger.core.handles.AbtractContractEventHandle; import com.jd.blockchain.utils.Bytes; public class ContractInvokingHandle extends AbtractContractEventHandle { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java similarity index 96% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index 85d649de..e7e59409 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -1,13 +1,10 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; -import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.TxBuilder; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java similarity index 97% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java index 18566ca6..27dcc438 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index 9979809d..1967b0c0 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerAdminAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -41,7 +41,7 @@ import com.jd.blockchain.transaction.LedgerInitSettingData; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.net.NetworkAddress; -public class LedgerAdminAccountTest { +public class LedgerAdminDatasetTest { private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), SMCryptoService.class.getName() }; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerBlockImplTest.java similarity index 96% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerBlockImplTest.java index ce29e1a5..9479cc2b 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerBlockImplTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerBlockImplTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午10:45 * Description: */ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; @@ -19,8 +19,8 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerDataSnapshot; -import com.jd.blockchain.ledger.core.impl.LedgerBlockData; -import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; +import com.jd.blockchain.ledger.core.LedgerBlockData; +import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; /** * diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java index 9eff5b16..c73badb1 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java index 483f7019..672ba12a 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitOperationTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java index a06b3ed1..1914f17b 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerInitSettingSerializeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java index dfa0e730..fe2a1f19 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -37,11 +37,11 @@ import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DataAccountSet; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerTransactionContext; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitSettingData; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java index 2629461a..e7cba7ad 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java index 1625411b..44c15b92 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import java.util.Random; @@ -19,7 +19,7 @@ import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; +import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitSettingData; import com.jd.blockchain.transaction.TransactionService; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTransactionDataTest.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTransactionDataTest.java index 9e53ae24..f676ea87 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTransactionDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTransactionDataTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午9:48 * Description: */ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; @@ -35,8 +35,8 @@ import com.jd.blockchain.ledger.Transaction; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.core.impl.LedgerTransactionData; -import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; +import com.jd.blockchain.ledger.core.LedgerTransactionData; +import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; import com.jd.blockchain.transaction.BlockchainOperationFactory; import com.jd.blockchain.transaction.DigitalSignatureBlob; import com.jd.blockchain.transaction.TxContentBlob; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/MerkleDataSetTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/MerkleDataSetTest.java index 0570fee9..a6f273b7 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/MerkleDataSetTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/MerkleTreeTest.java similarity index 99% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/MerkleTreeTest.java index d8cb3d29..dbb131ff 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleTreeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/MerkleTreeTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/RolePrivilegeDatasetTest.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/RolePrivilegeDatasetTest.java index 3c18ab32..18937fa0 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/RolePrivilegeDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/RolePrivilegeDatasetTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContract.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TestContract.java similarity index 96% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContract.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TestContract.java index 4d9a5ccd..4cbe2784 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContract.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TestContract.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import com.jd.blockchain.contract.Contract; import com.jd.blockchain.contract.ContractEvent; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContractImpl.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TestContractImpl.java similarity index 94% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContractImpl.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TestContractImpl.java index 3375f7f3..339cb4fb 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContractImpl.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TestContractImpl.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; public interface TestContractImpl { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java index 6f8e90f0..9f5ffb3b 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -26,16 +26,16 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.DataAccount; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerTransactionContext; import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; +import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.ledger.core.UserAccount; -import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; public class TransactionBatchProcessorTest { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java index f023a987..47902dcb 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -31,9 +31,9 @@ import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.core.LedgerTransactionData; import com.jd.blockchain.ledger.core.TransactionSet; -import com.jd.blockchain.ledger.core.impl.LedgerTransactionData; -import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; +import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.io.BytesUtils; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionStagedSnapshotTest.java similarity index 94% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionStagedSnapshotTest.java index 3693f60d..9eebeafd 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionStagedSnapshotTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionStagedSnapshotTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午10:49 * Description: */ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertEquals; @@ -18,7 +18,7 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.ledger.LedgerDataSnapshot; -import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; +import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; /** * diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/UserRoleDatasetTest.java similarity index 98% rename from source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java rename to source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/UserRoleDatasetTest.java index d9b89047..44046ee6 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/UserRoleDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/UserRoleDatasetTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger; +package test.com.jd.blockchain.ledger.core; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/ledger/LedgerConfigurer.java b/source/peer/src/main/java/com/jd/blockchain/peer/ledger/LedgerConfigurer.java index 5a2bc247..f6c8bcb9 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/ledger/LedgerConfigurer.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/ledger/LedgerConfigurer.java @@ -3,10 +3,10 @@ package com.jd.blockchain.peer.ledger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.TransactionEngineImpl; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; +import com.jd.blockchain.ledger.core.LedgerManager; +import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +import com.jd.blockchain.ledger.core.TransactionEngineImpl; import com.jd.blockchain.service.TransactionEngine; @Configuration diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegratedContext.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegratedContext.java index ef543c42..7a3bed53 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegratedContext.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegratedContext.java @@ -6,7 +6,7 @@ import java.util.Map; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; -import com.jd.blockchain.ledger.core.impl.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java index a7051431..014dcd62 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java @@ -35,8 +35,8 @@ import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.ledger.core.DataAccountSet; import com.jd.blockchain.ledger.core.LedgerManage; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.KVStorageService; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java index d5532f63..b954a08f 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java @@ -27,8 +27,8 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java index b2f1c135..178cb46a 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java @@ -29,8 +29,8 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index f4b24b36..d33f0cb9 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -22,10 +22,10 @@ import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; //import com.jd.blockchain.storage.service.utils.MemoryBasedDb; import com.jd.blockchain.tools.initializer.DBConnectionConfig; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index c6ca3fdc..122bcf00 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -27,10 +27,10 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; //import com.jd.blockchain.storage.service.utils.MemoryBasedDb; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index ef0f76e6..c227045f 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -12,6 +12,8 @@ import java.util.stream.DoubleStream; import com.jd.blockchain.crypto.*; import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; + import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.binaryproto.DataContractRegistry; @@ -32,10 +34,9 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; +import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.storage.service.impl.redis.JedisConnection; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index b49fcb6a..d8424013 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -26,8 +26,8 @@ import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index a13445a5..b5b24f92 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -34,8 +34,8 @@ import com.jd.blockchain.contract.ReadContract; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.DbConnectionFactory; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java index 9dd3ea10..00470903 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java @@ -7,8 +7,8 @@ import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DataAccountSet; import com.jd.blockchain.ledger.core.LedgerManage; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java index 41d8c800..e23fc48a 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java @@ -28,8 +28,8 @@ import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.DataAccountSet; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index ffdcaa80..d01de50f 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -31,10 +31,10 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index 9415e3b6..9831cf24 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -8,7 +8,6 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; import com.jd.blockchain.tools.initializer.*; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java index 64371207..081bf1d1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java @@ -28,8 +28,8 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index a57bf7a6..233af7d1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -24,12 +24,12 @@ import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; +import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProperties; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index 77992e2a..65c8138a 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -14,7 +14,7 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.core.impl.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.tools.initializer.LedgerBindingConfig.BindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.tools.keygen.KeyGenCommand; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/InitWebServerConfiguration.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/InitWebServerConfiguration.java index ec918972..69195dad 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/InitWebServerConfiguration.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/InitWebServerConfiguration.java @@ -8,7 +8,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import com.jd.blockchain.ledger.core.impl.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.utils.io.ByteArray; import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java index 7af0d55f..d8bd2c14 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java @@ -9,7 +9,6 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.*; diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index f88ff7b6..79223ddd 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -53,10 +53,10 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.LedgerQueryService; -import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; +import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.mocker.config.MockerConstant; import com.jd.blockchain.mocker.config.PresetAnswerPrompter; import com.jd.blockchain.mocker.handler.MockerContractExeHandle; diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index a661be6f..a57a181a 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -11,13 +11,13 @@ import com.jd.blockchain.contract.LedgerContext; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.LedgerManager; -import com.jd.blockchain.ledger.core.impl.LedgerQueryService; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; -import com.jd.blockchain.ledger.core.impl.handles.ContractLedgerContext; +import com.jd.blockchain.ledger.core.handles.ContractLedgerContext; import com.jd.blockchain.mocker.proxy.ExecutorProxy; public class MockerContractExeHandle implements OperationHandle { diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java index f0819d72..52c8392a 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java @@ -5,11 +5,11 @@ import java.util.List; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.core.OperationHandle; -import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; -import com.jd.blockchain.ledger.core.impl.handles.ContractCodeDeployOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.DataAccountKVSetOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.DataAccountRegisterOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.UserRegisterOperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; +import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; +import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; +import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; public class MockerOperationHandleRegister implements OperationHandleRegisteration { diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java index 38bc0575..5735bf36 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java @@ -5,8 +5,8 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; -import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.mocker.config.LedgerInitWebConfiguration; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; From bf1a71c4a2f3f1d89b92da5ed8dff2861ad01fee Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 26 Aug 2019 02:44:33 +0800 Subject: [PATCH 048/124] Implemented LedgerSecurityManager; --- .../ledger/core/LedgerAdminDataset.java | 4 + .../ledger/core/LedgerRepositoryImpl.java | 4 +- .../ledger/core/LedgerSecurityManager.java | 39 +-- .../core/LedgerSecurityManagerImpl.java | 287 ++++++++++++++++++ .../core/LedgerTransactionalEditor.java | 38 +-- .../ledger/core/MultiIdsPolicy.java | 21 ++ .../ledger/core/OperationHandle.java | 37 +-- .../ledger/core/OperationHandleContext.java | 10 +- .../ledger/core/ParticipantDataset.java | 6 + .../ledger/core/RolePrivilegeDataset.java | 9 +- .../ledger/core/SecurityContext.java | 38 +++ .../ledger/core/SecurityPolicy.java | 109 +++++++ .../core/TransactionBatchProcessor.java | 144 +++++++-- .../ledger/core/TransactionEngineImpl.java | 26 +- .../core/TransactionRequestContext.java | 70 ----- .../core/TransactionRequestContextImpl.java | 79 ----- .../core/TransactionRequestExtension.java | 115 +++++++ .../core/TransactionRequestExtensionImpl.java | 108 +++++++ .../ledger/core/UserRolesPrivileges.java | 63 ++++ .../AbstractLedgerOperationHandle.java | 67 ++++ .../handles/AbtractContractEventHandle.java | 29 +- .../ContractCodeDeployOperationHandle.java | 45 ++- .../DataAccountKVSetOperationHandle.java | 42 ++- .../DataAccountRegisterOperationHandle.java | 49 ++- .../JVMContractEventSendOperationHandle.java | 8 - .../handles/UserRegisterOperationHandle.java | 36 +-- .../ledger/core/ContractInvokingTest.java | 29 +- .../core/LedgerSecurityManagerTest.java | 141 +++++++++ .../core/TransactionBatchProcessorTest.java | 57 +++- .../blockchain/ledger/AbstractPrivilege.java | 58 ---- .../jd/blockchain/ledger/LedgerPrivilege.java | 20 +- .../ledger/LedgerSecurityException.java | 17 ++ .../ParticipantDoesNotExistException.java | 15 + .../jd/blockchain/ledger/PrivilegeBitset.java | 121 ++++++++ .../com/jd/blockchain/ledger/Privileges.java | 65 ++++ .../ledger/RolePrivilegeSettings.java | 11 + .../jd/blockchain/ledger/RolePrivileges.java | 35 +-- .../ledger/TransactionPrivilege.java | 18 +- .../blockchain/ledger/TransactionState.java | 10 + .../blockchain/mocker/MockerNodeContext.java | 14 +- .../handler/MockerContractExeHandle.java | 4 +- 41 files changed, 1569 insertions(+), 529 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityContext.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContext.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtension.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtensionImpl.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java delete mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSecurityException.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantDoesNotExistException.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privileges.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index cbc0af77..b85873a2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -290,6 +290,10 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { return participants.getParticipants(); } + ParticipantDataset getParticipantDataset() { + return participants; + } + /** * 加入新的参与方; 如果指定的参与方已经存在,则引发 LedgerException 异常; * diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 66405cf4..45da206c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -30,7 +30,7 @@ import com.jd.blockchain.utils.codec.Base58Utils; * @author huanghaiquan * */ -public class LedgerRepositoryImpl implements LedgerRepository { +class LedgerRepositoryImpl implements LedgerRepository { private static final Bytes LEDGER_PREFIX = Bytes.fromString("IDX" + LedgerConsts.KEY_SEPERATOR); @@ -422,7 +422,7 @@ public class LedgerRepositoryImpl implements LedgerRepository { private LedgerDataset innerGetLedgerDataset(LedgerBlock block) { LedgerAdminDataset adminDataset = createAdminDataset(block); CryptoSetting cryptoSetting = adminDataset.getSettings().getCryptoSetting(); - + UserAccountSet userAccountSet = createUserAccountSet(block, cryptoSetting); DataAccountSet dataAccountSet = createDataAccountSet(block, cryptoSetting); ContractAccountSet contractAccountSet = createContractAccountSet(block, cryptoSetting); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java index aa0692a2..1ee72045 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -2,37 +2,12 @@ package com.jd.blockchain.ledger.core; import java.util.Set; -import com.jd.blockchain.ledger.LedgerPrivilege; -import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.utils.Bytes; -/** - * - * {@link LedgerSecurityManager} implements the functions of security - * management, including authentication, authorization, data confidentiality, - * etc. - * - * @author huanghaiquan - * - */ -public class LedgerSecurityManager { - - public static final String ANONYMOUS_ROLE = "_ANONYMOUS"; - - public static final String DEFAULT_ROLE = "_DEFAULT"; - - - public Set getRoleNames(){ - throw new IllegalStateException("Not implemented!"); - } - - public RolePrivileges setRole(String role, LedgerPrivilege privilege) { - throw new IllegalStateException("Not implemented!"); - } +public interface LedgerSecurityManager { - public RolePrivileges getRole(String role) { - throw new IllegalStateException("Not implemented!"); - } - - - -} + String DEFAULT_ROLE = "_DEFAULT"; + + SecurityPolicy getSecurityPolicy(Set endpoints, Set nodes); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java new file mode 100644 index 00000000..adfc33d8 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java @@ -0,0 +1,287 @@ +package com.jd.blockchain.ledger.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSecurityException; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.utils.Bytes; + +/** + * 账本安全管理器; + * + * @author huanghaiquan + * + */ +public class LedgerSecurityManagerImpl implements LedgerSecurityManager { + + private RolePrivilegeSettings rolePrivilegeSettings; + + private UserRoleSettings userRolesSettings; + + private Map userPrivilegesCache = new ConcurrentHashMap<>(); + + private Map userRolesCache = new ConcurrentHashMap<>(); + private Map rolesPrivilegeCache = new ConcurrentHashMap<>(); + + public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserRoleSettings userRolesSettings) { + this.rolePrivilegeSettings = rolePrivilegeSettings; + this.userRolesSettings = userRolesSettings; + } + + @Override + public SecurityPolicy getSecurityPolicy(Set endpoints, Set nodes) { + + Map endpointPrivilegeMap = new HashMap<>(); + Map nodePrivilegeMap = new HashMap<>(); + + for (Bytes userAddress : endpoints) { + UserRolesPrivileges userPrivileges = getUserRolesPrivilegs(userAddress); + endpointPrivilegeMap.put(userAddress, userPrivileges); + } + + for (Bytes userAddress : nodes) { + UserRolesPrivileges userPrivileges = getUserRolesPrivilegs(userAddress); + nodePrivilegeMap.put(userAddress, userPrivileges); + } + + return new UserRolesSecurityPolicy(endpointPrivilegeMap, nodePrivilegeMap); + } + + private UserRolesPrivileges getUserRolesPrivilegs(Bytes userAddress) { + UserRolesPrivileges userPrivileges = userPrivilegesCache.get(userAddress); + if (userPrivileges != null) { + return userPrivileges; + } + + UserRoles userRoles = null; + + List privilegesList = new ArrayList<>(); + + // 加载用户的角色列表; + userRoles = userRolesCache.get(userAddress); + if (userRoles == null) { + userRoles = userRolesSettings.getUserRoles(userAddress); + if (userRoles != null) { + userRolesCache.put(userAddress, userRoles); + } + } + + // 计算用户的综合权限; + if (userRoles != null) { + String[] roles = userRoles.getRoles(); + RolePrivileges privilege = null; + for (String role : roles) { + // 先从缓存读取,如果没有再从原始数据源进行加载; + privilege = rolesPrivilegeCache.get(role); + if (privilege == null) { + privilege = rolePrivilegeSettings.getRolePrivilege(role); + if (privilege == null) { + // 略过不存在的无效角色; + continue; + } + rolesPrivilegeCache.put(role, privilege); + } + privilegesList.add(privilege); + } + } + // 如果用户未被授权任何角色,则采用默认角色的权限; + if (privilegesList.size() == 0) { + RolePrivileges privilege = getDefaultRolePrivilege(); + privilegesList.add(privilege); + } + + if (userRoles == null) { + userPrivileges = new UserRolesPrivileges(userAddress, RolesPolicy.UNION, privilegesList); + } else { + userPrivileges = new UserRolesPrivileges(userAddress, userRoles.getPolicy(), privilegesList); + } + + userPrivilegesCache.put(userAddress, userPrivileges); + return userPrivileges; + } + + private RolePrivileges getDefaultRolePrivilege() { + RolePrivileges privileges = rolesPrivilegeCache.get(DEFAULT_ROLE); + if (privileges == null) { + privileges = rolePrivilegeSettings.getRolePrivilege(DEFAULT_ROLE); + if (privileges == null) { + throw new LedgerSecurityException( + "This ledger is missing the default role-privilege settings for the users who don't have a role!"); + } + } + return privileges; + } + + private class UserRolesSecurityPolicy implements SecurityPolicy { + + /** + * 终端用户的权限表; + */ + private Map endpointPrivilegeMap = new HashMap<>(); + + /** + * 节点参与方的权限表; + */ + private Map nodePrivilegeMap = new HashMap<>(); + + public UserRolesSecurityPolicy(Map endpointPrivilegeMap, + Map nodePrivilegeMap) { + this.endpointPrivilegeMap = endpointPrivilegeMap; + this.nodePrivilegeMap = nodePrivilegeMap; + } + + @Override + public boolean isEnableToEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) { + if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { + if (p.getLedgerPrivileges().isEnable(permission)) { + return true; + } + } + return false; + } else if (MultiIdsPolicy.ALL == midPolicy) { + // 全部; + for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { + if (!p.getLedgerPrivileges().isEnable(permission)) { + return false; + } + } + return true; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public boolean isEnableToEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) { + if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { + if (p.getTransactionPrivileges().isEnable(permission)) { + return true; + } + } + return false; + } else if (MultiIdsPolicy.ALL == midPolicy) { + // 全部; + for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { + if (!p.getTransactionPrivileges().isEnable(permission)) { + return false; + } + } + return true; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public boolean isEnableToNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) { + if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (UserRolesPrivileges p : nodePrivilegeMap.values()) { + if (p.getLedgerPrivileges().isEnable(permission)) { + return true; + } + } + return false; + } else if (MultiIdsPolicy.ALL == midPolicy) { + // 全部; + for (UserRolesPrivileges p : nodePrivilegeMap.values()) { + if (!p.getLedgerPrivileges().isEnable(permission)) { + return false; + } + } + return true; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public boolean isEnableToNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) { + if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (UserRolesPrivileges p : nodePrivilegeMap.values()) { + if (p.getTransactionPrivileges().isEnable(permission)) { + return true; + } + } + return false; + } else if (MultiIdsPolicy.ALL == midPolicy) { + // 全部; + for (UserRolesPrivileges p : nodePrivilegeMap.values()) { + if (!p.getTransactionPrivileges().isEnable(permission)) { + return false; + } + } + return true; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public void checkEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) + throws LedgerSecurityException { + if (!isEnableToEndpoints(permission, midPolicy)) { + throw new LedgerSecurityException(String.format( + "The security policy [Permission=%s, Policy=%s] for endpoints rejected the current operation!", + permission, midPolicy)); + } + } + + @Override + public void checkEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) + throws LedgerSecurityException { + if (!isEnableToEndpoints(permission, midPolicy)) { + throw new LedgerSecurityException(String.format( + "The security policy [Permission=%s, Policy=%s] for endpoints rejected the current operation!", + permission, midPolicy)); + } + } + + @Override + public void checkNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException { + if (!isEnableToNodes(permission, midPolicy)) { + throw new LedgerSecurityException(String.format( + "The security policy [Permission=%s, Policy=%s] for nodes rejected the current operation!", + permission, midPolicy)); + } + } + + @Override + public void checkNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) + throws LedgerSecurityException { + if (!isEnableToNodes(permission, midPolicy)) { + throw new LedgerSecurityException(String.format( + "The security policy [Permission=%s, Policy=%s] for nodes rejected the current operation!", + permission, midPolicy)); + } + } + + @Override + public Set getEndpoints() { + return endpointPrivilegeMap.keySet(); + } + + @Override + public Set getNodes() { + return nodePrivilegeMap.keySet(); + } + + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java index 4b9f33b5..a6395d92 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java @@ -214,32 +214,9 @@ public class LedgerTransactionalEditor implements LedgerEditor { return ledgerHash.equals(reqLedgerHash); } - private boolean verifyTxContent(TransactionRequest request) { - TransactionContent txContent = request.getTransactionContent(); - if (!TxBuilder.verifyTxContentHash(txContent, txContent.getHash())) { - return false; - } - DigitalSignature[] endpointSignatures = request.getEndpointSignatures(); - if (endpointSignatures != null) { - for (DigitalSignature signature : endpointSignatures) { - if (!SignatureUtils.verifyHashSignature(txContent.getHash(), signature.getDigest(), - signature.getPubKey())) { - return false; - } - } - } - DigitalSignature[] nodeSignatures = request.getNodeSignatures(); - if (nodeSignatures != null) { - for (DigitalSignature signature : nodeSignatures) { - if (!SignatureUtils.verifyHashSignature(txContent.getHash(), signature.getDigest(), - signature.getPubKey())) { - return false; - } - } - } - return true; - } - + /** + * 注:此方法不验证交易完整性和签名有效性,仅仅设计为进行交易记录的管理;调用者应在此方法之外进行数据完整性和签名有效性的检查; + */ @Override public synchronized LedgerTransactionContext newTransaction(TransactionRequest txRequest) { // if (SettingContext.txSettings().verifyLedger() && !isRequestMatched(txRequest)) { @@ -250,15 +227,6 @@ public class LedgerTransactionalEditor implements LedgerEditor { TransactionState.IGNORED_BY_WRONG_LEDGER); } - // TODO: 把验签和创建交易并行化; -// if (SettingContext.txSettings().verifySignature() && !verifyTxContent(txRequest)) { - if (!verifyTxContent(txRequest)) { - // 抛弃哈希和签名校验失败的交易请求; - throw new IllegalTransactionException( - "Wrong transaction signature! --[TxHash=" + txRequest.getTransactionContent().getHash() + "]!", - TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); - } - if (currentTxCtx != null) { throw new IllegalStateException( "Unable to open another new transaction before the current transaction is completed! --[TxHash=" diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java new file mode 100644 index 00000000..974eb203 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java @@ -0,0 +1,21 @@ +package com.jd.blockchain.ledger.core; + +/** + * 多身份的权限校验策略; + * + * @author huanghaiquan + * + */ +public enum MultiIdsPolicy { + + /** + * 至少有一个都能通过; + */ + AT_LEAST_ONE, + + /** + * 每一个都能通过; + */ + ALL + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java index 02d0f453..94affc27 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java @@ -3,7 +3,6 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.Operation; - public interface OperationHandle { /** @@ -18,36 +17,16 @@ public interface OperationHandle { * 同步解析和执行操作; * * - * @param op - * 操作实例; - * @param newBlockDataset - * 需要修改的新区块的数据集; - * @param requestContext - * 交易请求上下文; - * @param previousBlockDataset - * 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; + * @param op 操作实例; + * @param newBlockDataset 需要修改的新区块的数据集; + * @param requestContext 交易请求上下文; + * @param previousBlockDataset 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集;注:此数据集是只读的; * - * @return 操作执行结果 + * @param handleContext 操作上下文;` + * @param ledgerService + * @return */ - BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestContext requestContext, + BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); -// /** -// * 异步解析和执行操作; -// * TODO 未来规划实现 -// * -// * -// * @param op -// * 操作实例; -// * @param newBlockDataset -// * 需要修改的新区块的数据集; -// * @param requestContext -// * 交易请求上下文; -// * @param previousBlockDataset -// * 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; -// * -// * @return 操作执行结果 -// */ -// AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, -// LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java index ab169976..1d837f15 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleContext.java @@ -2,8 +2,14 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.ledger.Operation; +/** + * 在交易处理过程中,提供对多种交易操作处理器互相调用的机制; + * + * @author huanghaiquan + * + */ public interface OperationHandleContext { - + void handle(Operation operation); - + } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java index 05267228..3185ae91 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java @@ -77,6 +77,12 @@ public class ParticipantDataset implements Transactional, MerkleProvable { return address; } + public boolean contains(Bytes address) { + Bytes key = encodeKey(address); + long latestVersion = dataset.getVersion(key); + return latestVersion > -1; + } + /** * 返回指定地址的参与方凭证; * diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java index 56c98bf4..8798ca66 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java @@ -7,6 +7,7 @@ import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerPrivilege; import com.jd.blockchain.ledger.PrivilegeSet; +import com.jd.blockchain.ledger.Privileges; import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.TransactionPermission; @@ -61,9 +62,11 @@ public class RolePrivilegeDataset implements Transactional, MerkleProvable, Role return dataset.getDataCount(); } - /** - * - */ + @Override + public long addRolePrivilege(String roleName, Privileges privileges) { + return addRolePrivilege(roleName, privileges.getLedgerPrivilege(), privileges.getTransactionPrivilege()); + } + @Override public long addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { RolePrivileges roleAuth = new RolePrivileges(roleName, -1, ledgerPrivilege, txPrivilege); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityContext.java new file mode 100644 index 00000000..d3ad83ba --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityContext.java @@ -0,0 +1,38 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Set; + +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSecurityException; +import com.jd.blockchain.ledger.TransactionPermission; + +public class SecurityContext { + + private static ThreadLocal policyHolder = new ThreadLocal(); + + public static void setContextUsersPolicy(SecurityPolicy policy) { + policyHolder.set(policy); + } + + public static SecurityPolicy removeContextUsersPolicy() { + SecurityPolicy p = policyHolder.get(); + policyHolder.remove(); + return p; + } + + public static SecurityPolicy getContextUsersPolicy() { + return policyHolder.get(); + } + + /** + * 把上下文安全策略切换为指定的策略,并执行参数指定的 {@link Runnable} 操作,当操作完成后恢复原来的上下文策略; + * + * @param contextUsersPolicy + * @param runnable + */ + public static void switchContextUsersPolicy(SecurityPolicy contextUsersPolicy, Runnable runnable) { + + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java new file mode 100644 index 00000000..78b85564 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java @@ -0,0 +1,109 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Set; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSecurityException; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.utils.Bytes; + +/** + * 针对特定交易请求的账本安全策略; + * + * @author huanghaiquan + * + */ +public interface SecurityPolicy { + + /** + * 签署交易的终端用户的地址列表;(来自{@link TransactionRequest#getEndpointSignatures()}) + * + * @return + */ + Set getEndpoints(); + + /** + * 签署交易的节点参与方的地址列表(来自{@link TransactionRequest#getNodeSignatures()}) + * + * @return + */ + Set getNodes(); + + /** + * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        + * + * @param permission 要检查的权限; + * @param midPolicy 针对多个签名用户的权限策略; + * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; + */ + boolean isEnableToEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy); + + /** + * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        + * + * @param permission 要检查的权限; + * @param midPolicy 针对多个签名用户的权限策略; + * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; + */ + boolean isEnableToEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy); + + /** + * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        + * + * @param permission 要检查的权限; + * @param midPolicy 针对多个签名用户的权限策略; + * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; + */ + boolean isEnableToNodes(LedgerPermission permission, MultiIdsPolicy midPolicy); + + /** + * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        + * + * @param permission 要检查的权限; + * @param midPolicy 针对多个签名用户的权限策略; + * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; + */ + boolean isEnableToNodes(TransactionPermission permission, MultiIdsPolicy midPolicy); + + /** + * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        + * 如果未获授权,方法将引发 {@link LedgerSecurityException} 异常; + * + * @param permission 要检查的权限; + * @param midPolicy 针对多个签名用户的权限策略; + * @throws LedgerSecurityException + */ + void checkEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + + /** + * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        + * 如果未获授权,方法将引发 {@link LedgerSecurityException} 异常; + * + * @param permission + * @param midPolicy + * @throws LedgerSecurityException + */ + void checkEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + + /** + * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        + * 如果未获授权,方法将引发 {@link LedgerSecurityException} 异常; + * + * @param permission + * @param midPolicy + * @throws LedgerSecurityException + */ + void checkNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + + /** + * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        + * 如果未获授权,方法将引发 {@link LedgerSecurityException} 异常; + * + * @param permission + * @param midPolicy + * @throws LedgerSecurityException + */ + void checkNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index 599041d0..96ebb5c4 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -1,6 +1,7 @@ package com.jd.blockchain.ledger.core; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -15,24 +16,32 @@ import com.jd.blockchain.ledger.DataAccountDoesNotExistException; import com.jd.blockchain.ledger.IllegalTransactionException; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSecurityException; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.OperationResult; import com.jd.blockchain.ledger.OperationResultData; +import com.jd.blockchain.ledger.ParticipantDoesNotExistException; +import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionRollbackException; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserDoesNotExistException; +import com.jd.blockchain.ledger.core.TransactionRequestExtension.Credential; import com.jd.blockchain.service.TransactionBatchProcess; import com.jd.blockchain.service.TransactionBatchResult; import com.jd.blockchain.service.TransactionBatchResultHandle; +import com.jd.blockchain.transaction.SignatureUtils; +import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.transaction.TxResponseMessage; -import com.jd.blockchain.utils.Bytes; public class TransactionBatchProcessor implements TransactionBatchProcess { private static final Logger LOGGER = LoggerFactory.getLogger(TransactionBatchProcessor.class); + private LedgerSecurityManager securityManager; + private LedgerService ledgerService; private LedgerEditor newBlockEditor; @@ -55,8 +64,9 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { * @param previousBlockDataset 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; * @param opHandles 操作处理对象注册表; */ - public TransactionBatchProcessor(LedgerEditor newBlockEditor, LedgerDataset previousBlockDataset, - OperationHandleRegisteration opHandles, LedgerService ledgerService) { + public TransactionBatchProcessor(LedgerSecurityManager securityManager, LedgerEditor newBlockEditor, + LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService) { + this.securityManager = securityManager; this.newBlockEditor = newBlockEditor; this.previousBlockDataset = previousBlockDataset; this.opHandles = opHandles; @@ -76,12 +86,26 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { try { LOGGER.debug("Start handling transaction... --[BlockHeight={}][RequestHash={}][TxHash={}]", newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash()); + + TransactionRequestExtension reqExt = new TransactionRequestExtensionImpl(request); + + // 初始化交易的用户安全策略; + SecurityPolicy securityPolicy = securityManager.getSecurityPolicy(reqExt.getEndpointAddresses(), + reqExt.getNodeAddresses()); + SecurityContext.setContextUsersPolicy(securityPolicy); + + // 安全校验; + checkSecurity(); + + // 验证交易请求; + checkRequest(reqExt); + // 创建交易上下文; // 此调用将会验证交易签名,验签失败将会抛出异常,同时,不记录签名错误的交易到链上; LedgerTransactionContext txCtx = newBlockEditor.newTransaction(request); // 处理交易; - resp = handleTx(request, txCtx); + resp = handleTx(reqExt, txCtx); LOGGER.debug("Complete handling transaction. --[BlockHeight={}][RequestHash={}][TxHash={}]", newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash()); @@ -93,10 +117,9 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { "Ignore transaction caused by IllegalTransactionException! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), e.getMessage()), e); - + } catch (BlockRollbackException e) { - // 抛弃发生处理异常的交易请求; -// resp = discard(request, TransactionState.IGNORED_BY_BLOCK_FULL_ROLLBACK); + // 发生区块级别的处理异常,向上重新抛出异常进行处理,整个区块可能被丢弃; LOGGER.error(String.format( "Ignore transaction caused by BlockRollbackException! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), @@ -110,12 +133,86 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), e.getMessage()), e); + } finally { + // 清空交易的用户安全策略; + SecurityContext.removeContextUsersPolicy(); } responseList.add(resp); return resp; } + /** + * 验证交易的参与方的权限; + */ + private void checkSecurity() { + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + + // 验证当前交易请求的节点参与方是否具有权限; + securityPolicy.checkNodes(LedgerPermission.APPROVE_TX, MultiIdsPolicy.AT_LEAST_ONE); + } + + private void checkRequest(TransactionRequestExtension reqExt) { + // TODO: 把验签和创建交易并行化; + checkTxContent(reqExt); + checkEndpointSignatures(reqExt); + checkNodeSignatures(reqExt); + } + + private void checkTxContent(TransactionRequestExtension requestExt) { + TransactionContent txContent = requestExt.getTransactionContent(); + if (!TxBuilder.verifyTxContentHash(txContent, txContent.getHash())) { + // 由于哈希校验失败,引发IllegalTransactionException,使外部调用抛弃此交易请求; + throw new IllegalTransactionException( + "Wrong transaction content hash! --[TxHash=" + requestExt.getTransactionContent().getHash() + "]!", + TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); + } + } + + private void checkEndpointSignatures(TransactionRequestExtension request) { + TransactionContent txContent = request.getTransactionContent(); + Collection endpoints = request.getEndpoints(); + if (endpoints != null) { + for (Credential endpoint : endpoints) { + if (!previousBlockDataset.getUserAccountSet().contains(endpoint.getAddress())) { + throw new UserDoesNotExistException( + "The endpoint signer[" + endpoint.getAddress() + "] was not registered!"); + } + + if (!SignatureUtils.verifyHashSignature(txContent.getHash(), endpoint.getSignature().getDigest(), + endpoint.getPubKey())) { + // 由于签名校验失败,引发IllegalTransactionException,使外部调用抛弃此交易请求; + throw new IllegalTransactionException( + String.format("Wrong transaction endpoint signature! --[Tx Hash=%s][Endpoint Signer=%s]!", + request.getTransactionContent().getHash(), endpoint.getAddress()), + TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); + } + } + } + } + + private void checkNodeSignatures(TransactionRequestExtension request) { + TransactionContent txContent = request.getTransactionContent(); + Collection nodes = request.getNodes(); + if (nodes != null) { + for (Credential node : nodes) { + if (!previousBlockDataset.getAdminDataset().getParticipantDataset().contains(node.getAddress())) { + throw new ParticipantDoesNotExistException( + "The node signer[" + node.getAddress() + "] was not registered to the participant set!"); + } + + if (!SignatureUtils.verifyHashSignature(txContent.getHash(), node.getSignature().getDigest(), + node.getPubKey())) { + // 由于签名校验失败,引发IllegalTransactionException,使外部调用抛弃此交易请求; + throw new IllegalTransactionException( + String.format("Wrong transaction node signature! --[Tx Hash=%s][Node Signer=%s]!", + request.getTransactionContent().getHash(), node.getAddress()), + TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); + } + } + } + } + /** * 处理交易;
        * @@ -125,23 +222,11 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { * @param txCtx * @return */ - private TransactionResponse handleTx(TransactionRequest request, LedgerTransactionContext txCtx) { + private TransactionResponse handleTx(TransactionRequestExtension request, LedgerTransactionContext txCtx) { TransactionState result; List operationResults = new ArrayList<>(); try { LedgerDataset dataset = txCtx.getDataset(); - TransactionRequestContext reqCtx = new TransactionRequestContextImpl(request); - // TODO: 验证签名者的有效性; - for (Bytes edpAddr : reqCtx.getEndpoints()) { - if (!previousBlockDataset.getUserAccountSet().contains(edpAddr)) { - throw new LedgerException("The endpoint signer[" + edpAddr + "] was not registered!"); - } - } - for (Bytes edpAddr : reqCtx.getNodes()) { - if (!previousBlockDataset.getUserAccountSet().contains(edpAddr)) { - throw new LedgerException("The node signer[" + edpAddr + "] was not registered!"); - } - } // 执行操作; Operation[] ops = request.getTransactionContent().getOperations(); @@ -151,14 +236,14 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { // assert; Instance of operation are one of User related operations or // DataAccount related operations; OperationHandle hdl = opHandles.getHandle(operation.getClass()); - hdl.process(operation, dataset, reqCtx, previousBlockDataset, this, ledgerService); + hdl.process(operation, dataset, request, previousBlockDataset, this, ledgerService); } }; OperationHandle opHandle; int opIndex = 0; for (Operation op : ops) { opHandle = opHandles.getHandle(op.getClass()); - BytesValue opResult = opHandle.process(op, dataset, reqCtx, previousBlockDataset, handleContext, + BytesValue opResult = opHandle.process(op, dataset, request, previousBlockDataset, handleContext, ledgerService); if (opResult != null) { operationResults.add(new OperationResultData(opIndex, opResult)); @@ -177,6 +262,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), e.getMessage()), e); } catch (BlockRollbackException e) { + // 回滚整个区块; result = TransactionState.IGNORED_BY_BLOCK_FULL_ROLLBACK; txCtx.rollback(); LOGGER.error( @@ -195,17 +281,27 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { result = TransactionState.USER_DOES_NOT_EXIST; } else if (e instanceof ContractDoesNotExistException) { result = TransactionState.CONTRACT_DOES_NOT_EXIST; + } else if (e instanceof ParticipantDoesNotExistException) { + result = TransactionState.PARTICIPANT_DOES_NOT_EXIST; } txCtx.discardAndCommit(result, operationResults); LOGGER.error(String.format( - "Due to ledger exception, the data changes resulting from the transaction will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", + "Due to ledger exception, the data changes resulting from transaction execution will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", + newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), + e.getMessage()), e); + } catch (LedgerSecurityException e) { + // TODO: 识别更详细的异常类型以及执行对应的处理; + result = TransactionState.REJECTED_BY_SECURITY_POLICY; + txCtx.discardAndCommit(result, operationResults); + LOGGER.error(String.format( + "Due to ledger security exception, the data changes resulting from transaction execution will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), e.getMessage()), e); } catch (Exception e) { result = TransactionState.SYSTEM_ERROR; txCtx.discardAndCommit(TransactionState.SYSTEM_ERROR, operationResults); LOGGER.error(String.format( - "Due to system exception, the data changes resulting from the transaction will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", + "Due to system exception, the data changes resulting from transaction execution will be rolled back and the results of the transaction will be committed! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s", newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(), e.getMessage()), e); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index c81cb1f9..d9d47840 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -41,8 +41,12 @@ public class TransactionEngineImpl implements TransactionEngine { LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); - batch = new InnerTransactionBatchProcessor(ledgerHash, newBlockEditor, previousBlockDataset, opHdlRegs, - ledgerService, ledgerBlock.getHeight()); + + LedgerAdminDataset previousAdminDataset = previousBlockDataset.getAdminDataset(); + LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getRolePrivileges(), + previousAdminDataset.getUserRoles()); + batch = new InnerTransactionBatchProcessor(ledgerHash, securityManager, newBlockEditor, previousBlockDataset, + opHdlRegs, ledgerService, ledgerBlock.getHeight()); batchs.put(ledgerHash, batch); return batch; } @@ -65,19 +69,15 @@ public class TransactionEngineImpl implements TransactionEngine { /** * 创建交易批处理器; * - * @param ledgerHash - * 账本哈希; - * @param newBlockEditor - * 新区块的数据编辑器; - * @param previousBlockDataset - * 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; - * @param opHandles - * 操作处理对象注册表; + * @param ledgerHash 账本哈希; + * @param newBlockEditor 新区块的数据编辑器; + * @param previousBlockDataset 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; + * @param opHandles 操作处理对象注册表; */ - public InnerTransactionBatchProcessor(HashDigest ledgerHash, LedgerEditor newBlockEditor, - LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, + public InnerTransactionBatchProcessor(HashDigest ledgerHash, LedgerSecurityManager securityManager, + LedgerEditor newBlockEditor, LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService, long blockHeight) { - super(newBlockEditor, previousBlockDataset, opHandles, ledgerService); + super(securityManager, newBlockEditor, previousBlockDataset, opHandles, ledgerService); this.ledgerHash = ledgerHash; this.blockHeight = blockHeight; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContext.java deleted file mode 100644 index 324c4e0e..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContext.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import java.util.Set; - -import com.jd.blockchain.ledger.DigitalSignature; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.utils.Bytes; - -/** - * 交易请求上下文; - * - * @author huanghaiquan - * - */ -public interface TransactionRequestContext { - - /** - * 交易请求; - * - * @return - */ - TransactionRequest getRequest(); - - /** - * 签名发起请求的终端用户的地址列表; - * - * @return - */ - Set getEndpoints(); - - /** - * 签名发起请求的节点的地址列表; - * - * @return - */ - Set getNodes(); - - /** - * 请求的终端发起人列表中是否包含指定地址的终端用户; - * - * @param address - * @return - */ - boolean containsEndpoint(Bytes address); - - /** - * 请求的经手节点列表中是否包含指定地址的节点; - * - * @param address - * @return - */ - boolean containsNode(Bytes address); - - /** - * 获取交易请求中指定地址的终端的签名; - * - * @param address - * @return - */ - DigitalSignature getEndpointSignature(Bytes address); - - /** - * 获取交易请求中指定地址的节点的签名; - * - * @param address - * @return - */ - DigitalSignature getNodeSignature(Bytes address); - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java deleted file mode 100644 index 3348a342..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestContextImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import com.jd.blockchain.crypto.AddressEncoding; -import com.jd.blockchain.ledger.DigitalSignature; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.utils.Bytes; - -/** - * @Author zhaogw - * @Date 2018/9/5 14:52 - */ -public class TransactionRequestContextImpl implements TransactionRequestContext { - - private TransactionRequest request; - - private Map endpointSignatures = new HashMap<>(); - - private Map nodeSignatures = new HashMap<>(); - - public TransactionRequestContextImpl(TransactionRequest request) { - this.request = request; - resolveSigners(); - } - - private void resolveSigners() { - if (request.getEndpointSignatures() != null) { - for (DigitalSignature signature : request.getEndpointSignatures()) { - Bytes address = AddressEncoding.generateAddress(signature.getPubKey()); - endpointSignatures.put(address, signature); - } - } - if (request.getEndpointSignatures() != null) { - for (DigitalSignature signature : request.getNodeSignatures()) { - Bytes address = AddressEncoding.generateAddress(signature.getPubKey()); - nodeSignatures.put(address, signature); - } - } - } - - @Override - public TransactionRequest getRequest() { - return request; - } - - @Override - public Set getEndpoints() { - return endpointSignatures.keySet(); - } - - @Override - public Set getNodes() { - return nodeSignatures.keySet(); - } - - @Override - public boolean containsEndpoint(Bytes address) { - return endpointSignatures.containsKey(address); - } - - @Override - public boolean containsNode(Bytes address) { - return nodeSignatures.containsKey(address); - } - - @Override - public DigitalSignature getEndpointSignature(Bytes address) { - return endpointSignatures.get(address); - } - - @Override - public DigitalSignature getNodeSignature(Bytes address) { - return nodeSignatures.get(address); - } - -} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtension.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtension.java new file mode 100644 index 00000000..ec6b1122 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtension.java @@ -0,0 +1,115 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Collection; +import java.util.Set; + +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.BlockchainIdentityData; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.utils.Bytes; + +/** + * 交易请求上下文; + * + * @author huanghaiquan + * + */ +public interface TransactionRequestExtension extends TransactionRequest { + +// /** +// * 交易请求; +// * +// * @return +// */ +// TransactionRequest getRequest(); + + /** + * 签名发起请求的终端用户的地址列表; + * + * @return + */ + Set getEndpointAddresses(); + + /** + * 签名发起请求的终端用户列表; + * + * @return + */ + Collection getEndpoints(); + + /** + * 签名发起请求的节点的地址列表; + * + * @return + */ + Set getNodeAddresses(); + + /** + * 签名发起请求的节点列表; + * + * @return + */ + Collection getNodes(); + + /** + * 请求的终端发起人列表中是否包含指定地址的终端用户; + * + * @param address + * @return + */ + boolean containsEndpoint(Bytes address); + + /** + * 请求的经手节点列表中是否包含指定地址的节点; + * + * @param address + * @return + */ + boolean containsNode(Bytes address); + + /** + * 获取交易请求中指定地址的终端的签名; + * + * @param address + * @return + */ + DigitalSignature getEndpointSignature(Bytes address); + + /** + * 获取交易请求中指定地址的节点的签名; + * + * @param address + * @return + */ + DigitalSignature getNodeSignature(Bytes address); + + public static class Credential { + + private final BlockchainIdentity identity; + + private final DigitalSignature signature; + + Credential(DigitalSignature signature) { + this.identity = new BlockchainIdentityData(signature.getPubKey()); + this.signature = signature; + } + + public Bytes getAddress() { + return identity.getAddress(); + } + + public PubKey getPubKey() { + return identity.getPubKey(); + } + + public BlockchainIdentity getIdentity() { + return identity; + } + + public DigitalSignature getSignature() { + return signature; + } + } +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtensionImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtensionImpl.java new file mode 100644 index 00000000..1d93fbbe --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionRequestExtensionImpl.java @@ -0,0 +1,108 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.utils.Bytes; + +/** + * 交易请求的扩展信息; + * + * @author huanghaiquan + * + */ +public class TransactionRequestExtensionImpl implements TransactionRequestExtension { + + private TransactionRequest request; + + private Map endpointSignatures = new HashMap<>(); + + private Map nodeSignatures = new HashMap<>(); + + public TransactionRequestExtensionImpl(TransactionRequest request) { + this.request = request; + resolveSigners(); + } + + private void resolveSigners() { + if (request.getEndpointSignatures() != null) { + for (DigitalSignature signature : request.getEndpointSignatures()) { + Credential cred = new Credential(signature); + endpointSignatures.put(cred.getIdentity().getAddress(), cred); + } + } + if (request.getEndpointSignatures() != null) { + for (DigitalSignature signature : request.getNodeSignatures()) { + Credential cred = new Credential(signature); + nodeSignatures.put(cred.getIdentity().getAddress(), cred); + } + } + } + + @Override + public Set getEndpointAddresses() { + return endpointSignatures.keySet(); + } + + @Override + public Set getNodeAddresses() { + return nodeSignatures.keySet(); + } + + @Override + public Collection getEndpoints() { + return endpointSignatures.values(); + } + + @Override + public Collection getNodes() { + return nodeSignatures.values(); + } + + @Override + public boolean containsEndpoint(Bytes address) { + return endpointSignatures.containsKey(address); + } + + @Override + public boolean containsNode(Bytes address) { + return nodeSignatures.containsKey(address); + } + + @Override + public DigitalSignature getEndpointSignature(Bytes address) { + return endpointSignatures.get(address).getSignature(); + } + + @Override + public DigitalSignature getNodeSignature(Bytes address) { + return nodeSignatures.get(address).getSignature(); + } + + @Override + public HashDigest getHash() { + return request.getHash(); + } + + @Override + public DigitalSignature[] getNodeSignatures() { + return request.getNodeSignatures(); + } + + @Override + public DigitalSignature[] getEndpointSignatures() { + return request.getEndpointSignatures(); + } + + @Override + public TransactionContent getTransactionContent() { + return request.getTransactionContent(); + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java new file mode 100644 index 00000000..4a626c70 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java @@ -0,0 +1,63 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Collection; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerPrivilege; +import com.jd.blockchain.ledger.PrivilegeBitset; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.TransactionPrivilege; +import com.jd.blockchain.utils.Bytes; + +/** + * {@link UserRolesPrivileges} 表示多角色用户的综合权限; + * + * @author huanghaiquan + * + */ +class UserRolesPrivileges { + + private Bytes userAddress; + + private PrivilegeBitset ledgerPrivileges; + + private PrivilegeBitset transactionPrivileges; + + public UserRolesPrivileges(Bytes userAddress, RolesPolicy policy, Collection privilegesList) { + this.userAddress = userAddress; + LedgerPrivilege[] ledgerPrivileges = privilegesList.stream().map(p -> p.getLedgerPrivilege()) + .toArray(LedgerPrivilege[]::new); + TransactionPrivilege[] transactionPrivileges = privilegesList.stream().map(p -> p.getTransactionPrivilege()) + .toArray(TransactionPrivilege[]::new); + + this.ledgerPrivileges = ledgerPrivileges[0].clone(); + this.transactionPrivileges = transactionPrivileges[0].clone(); + + if (policy == RolesPolicy.UNION) { + this.ledgerPrivileges.union(ledgerPrivileges, 1, ledgerPrivileges.length - 1); + this.transactionPrivileges.union(transactionPrivileges, 1, transactionPrivileges.length - 1); + + } else if (policy == RolesPolicy.INTERSECT) { + this.ledgerPrivileges.intersect(ledgerPrivileges, 1, ledgerPrivileges.length - 1); + this.transactionPrivileges.intersect(transactionPrivileges, 1, transactionPrivileges.length - 1); + } else { + throw new IllegalStateException("Unsupported roles policy[" + policy.toString() + "]!"); + } + + } + + public Bytes getUserAddress() { + return userAddress; + } + + public PrivilegeBitset getLedgerPrivileges() { + return ledgerPrivileges; + } + + public PrivilegeBitset getTransactionPrivileges() { + return transactionPrivileges; + } + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java new file mode 100644 index 00000000..311dc9d5 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java @@ -0,0 +1,67 @@ +package com.jd.blockchain.ledger.core.handles; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; + +/** + * 执行直接账本操作的处理类; + * + * @author huanghaiquan + * + * @param + */ +public abstract class AbstractLedgerOperationHandle implements OperationHandle { + + static { + DataContractRegistry.register(BytesValue.class); + } + + private final Class SUPPORTED_OPERATION_TYPE; + + public AbstractLedgerOperationHandle(Class supportedOperationType) { + this.SUPPORTED_OPERATION_TYPE = supportedOperationType; + } + + @Override + public final boolean support(Class operationType) { + return SUPPORTED_OPERATION_TYPE.isAssignableFrom(operationType); + } + + @Override + public final BytesValue process(Operation op, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(TransactionPermission.DIRECT_OPERATION, MultiIdsPolicy.AT_LEAST_ONE); + + // 操作账本; + @SuppressWarnings("unchecked") + T concretedOp = (T) op; + doProcess(concretedOp, newBlockDataset, requestContext, previousBlockDataset, handleContext, ledgerService); + + // 账本操作没有返回值; + return null; + } + + /** + * @param op + * @param newBlockDataset + * @param requestContext + * @param previousBlockDataset + * @param handleContext + * @param ledgerService + */ + protected abstract void doProcess(T op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, + LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java index 09b25e28..8a410f1a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java @@ -8,14 +8,18 @@ import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.ContractEventSendOperation; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.core.ContractAccount; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.TransactionRequestContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; @Service public abstract class AbtractContractEventHandle implements OperationHandle { @@ -26,9 +30,22 @@ public abstract class AbtractContractEventHandle implements OperationHandle { } @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + public BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(TransactionPermission.CONTRACT_OPERATION, MultiIdsPolicy.AT_LEAST_ONE); + + // 操作账本; ContractEventSendOperation contractOP = (ContractEventSendOperation) op; + + return doProcess(requestContext, contractOP, newBlockDataset, previousBlockDataset, opHandleContext, + ledgerService); + } + + private BytesValue doProcess(TransactionRequestExtension request, ContractEventSendOperation contractOP, + LedgerDataset newBlockDataset, LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, + LedgerService ledgerService) { // 先从账本校验合约的有效性; // 注意:必须在前一个区块的数据集中进行校验,因为那是经过共识的数据;从当前新区块链数据集校验则会带来攻击风险:未经共识的合约得到执行; ContractAccountSet contractSet = previousBlockDataset.getContractAccountset(); @@ -50,19 +67,17 @@ public abstract class AbtractContractEventHandle implements OperationHandle { // 创建合约上下文; LocalContractEventContext localContractEventContext = new LocalContractEventContext( - requestContext.getRequest().getTransactionContent().getLedgerHash(), contractOP.getEvent()); - localContractEventContext.setArgs(contractOP.getArgs()).setTransactionRequest(requestContext.getRequest()) + request.getTransactionContent().getLedgerHash(), contractOP.getEvent()); + localContractEventContext.setArgs(contractOP.getArgs()).setTransactionRequest(request) .setLedgerContext(ledgerContext); - // 装载合约; ContractCode contractCode = loadContractCode(contract); // 处理合约事件; return contractCode.processEvent(localContractEventContext); } - - protected abstract ContractCode loadContractCode(ContractAccount contract); + protected abstract ContractCode loadContractCode(ContractAccount contract); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java index c4ae6ce7..6e0d68b8 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java @@ -1,41 +1,36 @@ package com.jd.blockchain.ledger.core.handles; -import org.springframework.stereotype.Service; - -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.TransactionRequestContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; -@Service -public class ContractCodeDeployOperationHandle implements OperationHandle { +public class ContractCodeDeployOperationHandle extends AbstractLedgerOperationHandle { + public ContractCodeDeployOperationHandle() { + super(ContractCodeDeployOperation.class); + } @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { - ContractCodeDeployOperation contractOP = (ContractCodeDeployOperation) op; + protected void doProcess(ContractCodeDeployOperation op, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { // TODO: 校验合约代码的正确性; - - // TODO: 请求者应该提供合约账户的公钥签名,已确定注册的地址的唯一性; - dataset.getContractAccountset().deploy(contractOP.getContractID().getAddress(), - contractOP.getContractID().getPubKey(), contractOP.getAddressSignature(), contractOP.getChainCode()); + // TODO: 请求者应该提供合约账户的公钥签名,以确保注册人对注册的地址和公钥具有合法的使用权; - return null; - } + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(LedgerPermission.UPGRADE_CONTRACT, MultiIdsPolicy.AT_LEAST_ONE); -// @Override -// public AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { -// return null; -// } - - @Override - public boolean support(Class operationType) { - return ContractCodeDeployOperation.class.isAssignableFrom(operationType); + // 操作账本; + ContractCodeDeployOperation contractOP = (ContractCodeDeployOperation) op; + newBlockDataset.getContractAccountset().deploy(contractOP.getContractID().getAddress(), + contractOP.getContractID().getPubKey(), contractOP.getAddressSignature(), contractOP.getChainCode()); } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index c3bc832d..52801883 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -1,32 +1,34 @@ package com.jd.blockchain.ledger.core.handles; -import org.springframework.stereotype.Service; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataAccountDoesNotExistException; import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; -import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.TransactionRequestContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; import com.jd.blockchain.utils.Bytes; -@Service -public class DataAccountKVSetOperationHandle implements OperationHandle { - static { - DataContractRegistry.register(BytesValue.class); +public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHandle { + public DataAccountKVSetOperationHandle() { + super(DataAccountKVSetOperation.class); } @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { - DataAccountKVSetOperation kvWriteOp = (DataAccountKVSetOperation) op; - DataAccount account = dataset.getDataAccountSet().getDataAccount(kvWriteOp.getAccountAddress()); + protected void doProcess(DataAccountKVSetOperation kvWriteOp, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE); + + // 操作账本; + DataAccount account = newBlockDataset.getDataAccountSet().getDataAccount(kvWriteOp.getAccountAddress()); if (account == null) { throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); } @@ -34,17 +36,7 @@ public class DataAccountKVSetOperationHandle implements OperationHandle { for (KVWriteEntry kvw : writeSet) { account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); } - return null; } -// @Override -// public AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { -// return null; -// } - - @Override - public boolean support(Class operationType) { - return DataAccountKVSetOperation.class.isAssignableFrom(operationType); - } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java index 4b4c87b0..06fe0746 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java @@ -1,42 +1,35 @@ package com.jd.blockchain.ledger.core.handles; -import org.springframework.stereotype.Service; - import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.TransactionRequestContext; - -@Service -public class DataAccountRegisterOperationHandle implements OperationHandle { - - @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { - DataAccountRegisterOperation dataAccountRegOp = (DataAccountRegisterOperation) op; - BlockchainIdentity bid = dataAccountRegOp.getAccountID(); +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; - //TODO: 校验用户身份; - - //TODO: 请求者应该提供数据账户的公钥签名,已确定注册的地址的唯一性; - dataset.getDataAccountSet().register(bid.getAddress(), bid.getPubKey(), null); - - return null; +public class DataAccountRegisterOperationHandle extends AbstractLedgerOperationHandle { + public DataAccountRegisterOperationHandle() { + super(DataAccountRegisterOperation.class); } + + @Override + protected void doProcess(DataAccountRegisterOperation op, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { + // TODO: 请求者应该提供数据账户的公钥签名,以更好地确保注册人对该地址和公钥具有合法使用权; -// @Override -// public AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { -// return null; -// } + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(LedgerPermission.REGISTER_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE); - @Override - public boolean support(Class operationType) { - return DataAccountRegisterOperation.class.isAssignableFrom(operationType); + // 操作账本; + DataAccountRegisterOperation dataAccountRegOp = (DataAccountRegisterOperation) op; + BlockchainIdentity bid = dataAccountRegOp.getAccountID(); + newBlockDataset.getDataAccountSet().register(bid.getAddress(), bid.getPubKey(), null); } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java index 5107ffd5..018c4d8a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java @@ -26,12 +26,4 @@ public class JVMContractEventSendOperationHandle extends AbtractContractEventHan return contractCode; } -// @Override -// public AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, -// TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, -// OperationHandleContext handleContext, LedgerService ledgerService) { -// // TODO Auto-generated method stub -// return null; -// } - } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java index bceaaa2a..6a399e71 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java @@ -1,37 +1,37 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BytesValue; -import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.TransactionRequestContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; import com.jd.blockchain.utils.Bytes; - -public class UserRegisterOperationHandle implements OperationHandle { +public class UserRegisterOperationHandle extends AbstractLedgerOperationHandle { + public UserRegisterOperationHandle() { + super(UserRegisterOperation.class); + } @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { - - + protected void doProcess(UserRegisterOperation op, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(LedgerPermission.REGISTER_USER, MultiIdsPolicy.AT_LEAST_ONE); + + // 操作账本; UserRegisterOperation userRegOp = (UserRegisterOperation) op; BlockchainIdentity bid = userRegOp.getUserID(); Bytes userAddress = bid.getAddress(); - dataset.getUserAccountSet().register(userAddress, bid.getPubKey()); - - return null; - } - - @Override - public boolean support(Class operationType) { - return UserRegisterOperation.class.isAssignableFrom(operationType); + newBlockDataset.getUserAccountSet().register(userAddress, bid.getPubKey()); } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index e7e59409..f7821564 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -15,6 +15,7 @@ import org.mockito.Mockito; import java.util.Random; import static org.junit.Assert.*; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; @@ -65,15 +66,15 @@ public class ContractInvokingTest { // 发布指定地址合约 deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); - // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + LedgerSecurityManager securityManager = getSecurityManager(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, + previousBlockDataset, opReg, ledgerManager); // 构建基于接口调用合约的交易请求,用于测试合约调用; TxBuilder txBuilder = new TxBuilder(ledgerHash); @@ -119,16 +120,16 @@ public class ContractInvokingTest { } private void deploy(LedgerRepository ledgerRepo, LedgerManager ledgerManager, - DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, - BlockchainKeypair contractKey) { + DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair contractKey) { // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + LedgerSecurityManager securityManager = getSecurityManager(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, + previousBlockDataset, opReg, ledgerManager); // 构建基于接口调用合约的交易请求,用于测试合约调用; TxBuilder txBuilder = new TxBuilder(ledgerHash); @@ -189,4 +190,18 @@ public class ContractInvokingTest { new Random().nextBytes(chainCode); return chainCode; } + + private static LedgerSecurityManager getSecurityManager() { + LedgerSecurityManager securityManager = Mockito.mock(LedgerSecurityManager.class); + + SecurityPolicy securityPolicy = Mockito.mock(SecurityPolicy.class); + when(securityPolicy.isEnableToEndpoints(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToEndpoints(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); + + when(securityManager.getSecurityPolicy(any(), any())).thenReturn(securityPolicy); + + return securityManager; + } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java new file mode 100644 index 00000000..1ea099c0 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java @@ -0,0 +1,141 @@ +package test.com.jd.blockchain.ledger.core; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoProvider; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +import com.jd.blockchain.crypto.service.sm.SMCryptoService; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.Privileges; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.LedgerSecurityManager; +import com.jd.blockchain.ledger.core.LedgerSecurityManagerImpl; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.RolePrivilegeDataset; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.UserRoleDataset; +import com.jd.blockchain.storage.service.utils.MemoryKVStorage; +import com.jd.blockchain.utils.Bytes; + +public class LedgerSecurityManagerTest { + + private static final String[] SUPPORTED_PROVIDER_NAMES = { ClassicCryptoService.class.getName(), + SMCryptoService.class.getName() }; + + private static final CryptoAlgorithm HASH_ALGORITHM = Crypto.getAlgorithm("SHA256"); + + private static final CryptoProvider[] SUPPORTED_PROVIDERS = new CryptoProvider[SUPPORTED_PROVIDER_NAMES.length]; + + private static final CryptoSetting CRYPTO_SETTINGS; + + static { + for (int i = 0; i < SUPPORTED_PROVIDER_NAMES.length; i++) { + SUPPORTED_PROVIDERS[i] = Crypto.getProvider(SUPPORTED_PROVIDER_NAMES[i]); + } + + CryptoConfig cryptoConfig = new CryptoConfig(); + cryptoConfig.setAutoVerifyHash(true); + cryptoConfig.setSupportedProviders(SUPPORTED_PROVIDERS); + cryptoConfig.setHashAlgorithm(HASH_ALGORITHM); + + CRYPTO_SETTINGS = cryptoConfig; + } + + private RolePrivilegeSettings initRoles(MemoryKVStorage testStorage, String[] roles, Privileges[] privilege) { + String prefix = "role-privilege/"; + RolePrivilegeDataset rolePrivilegeDataset = new RolePrivilegeDataset(CRYPTO_SETTINGS, prefix, testStorage, + testStorage); + for (int i = 0; i < roles.length; i++) { + rolePrivilegeDataset.addRolePrivilege(roles[i], privilege[i]); + } + + rolePrivilegeDataset.commit(); + + return rolePrivilegeDataset; + } + + private UserRoleSettings initUserRoless(MemoryKVStorage testStorage, Bytes[] userAddresses, RolesPolicy[] policies, + String[][] roles) { + String prefix = "user-roles/"; + UserRoleDataset userRolesDataset = new UserRoleDataset(CRYPTO_SETTINGS, prefix, testStorage, testStorage); + + for (int i = 0; i < userAddresses.length; i++) { + userRolesDataset.addUserRoles(userAddresses[i], policies[i], roles[i]); + } + + userRolesDataset.commit(); + + return userRolesDataset; + } + + @Test + public void testGetSecurityPolicy() { + MemoryKVStorage testStorage = new MemoryKVStorage(); + + final BlockchainKeypair kpManager = BlockchainKeyGenerator.getInstance().generate(); + final BlockchainKeypair kpEmployee = BlockchainKeyGenerator.getInstance().generate(); + final BlockchainKeypair kpDevoice = BlockchainKeyGenerator.getInstance().generate(); + + final Map endpoints = new HashMap<>(); + endpoints.put(kpManager.getAddress(), kpManager); + endpoints.put(kpEmployee.getAddress(), kpEmployee); + + final Map nodes = new HashMap<>(); + nodes.put(kpDevoice.getAddress(), kpDevoice); + + + final String ROLE_ADMIN = "ID_ADMIN"; + final String ROLE_OPERATOR = "OPERATOR"; + final String ROLE_DATA_COLLECTOR = "DATA_COLLECTOR"; + + final Privileges PRIVILEGES_ADMIN = Privileges.configure() + .enable(LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT) + .enable(TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION); + + final Privileges PRIVILEGES_OPERATOR = Privileges.configure() + .enable(LedgerPermission.WRITE_DATA_ACCOUNT, LedgerPermission.APPROVE_TX) + .enable(TransactionPermission.CONTRACT_OPERATION); + + final Privileges PRIVILEGES_DATA_COLLECTOR = Privileges.configure().enable(LedgerPermission.WRITE_DATA_ACCOUNT) + .enable(TransactionPermission.CONTRACT_OPERATION); + + RolePrivilegeSettings rolePrivilegeSettings = initRoles(testStorage, + new String[] { ROLE_ADMIN, ROLE_OPERATOR, ROLE_DATA_COLLECTOR }, + new Privileges[] { PRIVILEGES_ADMIN, PRIVILEGES_OPERATOR, PRIVILEGES_DATA_COLLECTOR }); + + String[] managerRoles = new String[] { ROLE_ADMIN, ROLE_OPERATOR }; + String[] employeeRoles = new String[] { ROLE_OPERATOR }; + String[] devoiceRoles = new String[] { ROLE_DATA_COLLECTOR }; + UserRoleSettings userRolesSettings = initUserRoless(testStorage, + new Bytes[] { kpManager.getAddress(), kpEmployee.getAddress(), kpDevoice.getAddress() }, + new RolesPolicy[] { RolesPolicy.UNION, RolesPolicy.UNION, RolesPolicy.UNION }, + new String[][] { managerRoles, employeeRoles, devoiceRoles }); + + LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(rolePrivilegeSettings, userRolesSettings); + + SecurityPolicy policy = securityManager.getSecurityPolicy(endpoints.keySet(), nodes.keySet()); + + assertTrue(policy.isEnableToEndpoints(LedgerPermission.REGISTER_USER, MultiIdsPolicy.AT_LEAST_ONE)); + assertTrue(policy.isEnableToEndpoints(LedgerPermission.REGISTER_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE)); + assertTrue(policy.isEnableToEndpoints(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE)); + assertTrue(policy.isEnableToEndpoints(LedgerPermission.APPROVE_TX, MultiIdsPolicy.AT_LEAST_ONE)); + assertFalse(policy.isEnableToEndpoints(LedgerPermission.REGISTER_USER, MultiIdsPolicy.ALL)); + assertFalse(policy.isEnableToEndpoints(LedgerPermission.AUTHORIZE_ROLES, MultiIdsPolicy.AT_LEAST_ONE)); + } + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java index 9f5ffb3b..20cc013c 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java @@ -5,8 +5,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; import org.junit.Test; +import org.mockito.Mockito; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; @@ -17,10 +20,12 @@ import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.EndpointRequest; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerTransaction; import com.jd.blockchain.ledger.NodeRequest; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionState; @@ -31,9 +36,11 @@ import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerSecurityManager; import com.jd.blockchain.ledger.core.LedgerTransactionContext; import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; @@ -85,8 +92,9 @@ public class TransactionBatchProcessorTest { LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + LedgerSecurityManager securityManager = getSecurityManager(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, + previousBlockDataset, opReg, ledgerManager); // 注册新用户; BlockchainKeypair userKeypair = BlockchainKeyGenerator.getInstance().generate(); @@ -108,6 +116,20 @@ public class TransactionBatchProcessorTest { assertEquals(TransactionState.SUCCESS, txResp.getExecutionState()); } + private static LedgerSecurityManager getSecurityManager() { + LedgerSecurityManager securityManager = Mockito.mock(LedgerSecurityManager.class); + + SecurityPolicy securityPolicy = Mockito.mock(SecurityPolicy.class); + when(securityPolicy.isEnableToEndpoints(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToEndpoints(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); + + when(securityManager.getSecurityPolicy(any(), any())).thenReturn(securityPolicy); + + return securityManager; + } + @Test public void testMultiTxsProcess() { final MemoryKVStorage STORAGE = new MemoryKVStorage(); @@ -130,8 +152,9 @@ public class TransactionBatchProcessorTest { LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + LedgerSecurityManager securityManager = getSecurityManager(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, + previousBlockDataset, opReg, ledgerManager); // 注册新用户; BlockchainKeypair userKeypair1 = BlockchainKeyGenerator.getInstance().generate(); @@ -187,8 +210,9 @@ public class TransactionBatchProcessorTest { LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + LedgerSecurityManager securityManager = getSecurityManager(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, + previousBlockDataset, opReg, ledgerManager); // 注册新用户; BlockchainKeypair userKeypair1 = BlockchainKeyGenerator.getInstance().generate(); @@ -267,8 +291,9 @@ public class TransactionBatchProcessorTest { LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + LedgerSecurityManager securityManager = getSecurityManager(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, + previousBlockDataset, opReg, ledgerManager); BlockchainKeypair dataAccountKeypair = BlockchainKeyGenerator.getInstance().generate(); TransactionRequest transactionRequest1 = LedgerTestUtils.createTxRequest_DataAccountReg(dataAccountKeypair, @@ -293,7 +318,8 @@ public class TransactionBatchProcessorTest { newBlockEditor = ledgerRepo.createNextBlock(); previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); - txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); + txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, previousBlockDataset, opReg, + ledgerManager); txbatchProcessor.schedule(txreq1); txbatchProcessor.schedule(txreq2); @@ -316,7 +342,7 @@ public class TransactionBatchProcessorTest { assertNotNull(v1_1); assertNotNull(v2); assertNotNull(v3); - + assertEquals("V-1-1", v1_0.getValue().toUTF8String()); assertEquals("V-1-2", v1_1.getValue().toUTF8String()); assertEquals("V-2-1", v2.getValue().toUTF8String()); @@ -332,7 +358,8 @@ public class TransactionBatchProcessorTest { newBlockEditor = ledgerRepo.createNextBlock(); previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); - txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); + txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, previousBlockDataset, opReg, + ledgerManager); txbatchProcessor.schedule(txreq5); txbatchProcessor.schedule(txreq6); @@ -343,11 +370,13 @@ public class TransactionBatchProcessorTest { BytesValue v1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1"); v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3"); - long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K1"); + long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) + .getDataVersion("K1"); assertEquals(1, k1_version); - long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K3"); + long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) + .getDataVersion("K3"); assertEquals(1, k3_version); - + assertNotNull(v1); assertNotNull(v3); assertEquals("V-1-2", v1.getValue().toUTF8String()); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java deleted file mode 100644 index df65c93c..00000000 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AbstractPrivilege.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.jd.blockchain.ledger; - -import java.util.BitSet; - -import com.jd.blockchain.utils.io.BytesSerializable; - -/** - * LedgerPrivilege 账本特权是授权给特定角色的权限代码序列; - * - * @author huanghaiquan - * - */ -public abstract class AbstractPrivilege> implements Privilege, BytesSerializable { - - private BitSet permissionBits; - - public AbstractPrivilege() { - permissionBits = new BitSet(); - } - - public AbstractPrivilege(byte[] codeBytes) { - permissionBits = BitSet.valueOf(codeBytes); - } - - public boolean isEnable(E permission) { - return permissionBits.get(getCodeIndex(permission)); - } - - public void enable(E permission) { - permissionBits.set(getCodeIndex(permission)); - } - - public void disable(E permission) { - permissionBits.clear(getCodeIndex(permission)); - } - - @SuppressWarnings("unchecked") - public void enable(E... permissions) { - for (E p : permissions) { - permissionBits.set(getCodeIndex(p)); - } - } - - @SuppressWarnings("unchecked") - public void disable(E... permissions) { - for (E p : permissions) { - permissionBits.clear(getCodeIndex(p)); - } - } - - protected abstract int getCodeIndex(E permission); - - @Override - public byte[] toBytes() { - return permissionBits.toByteArray(); - } - -} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java index 5b5ebf20..dad59a41 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java @@ -6,18 +6,24 @@ package com.jd.blockchain.ledger; * @author huanghaiquan * */ -public class LedgerPrivilege extends AbstractPrivilege { +public class LedgerPrivilege extends PrivilegeBitset { + + private static final CodeIndexer CODE_INDEXER = new LedgerPermissionCodeIndexer(); public LedgerPrivilege() { + super(CODE_INDEXER); } - + public LedgerPrivilege(byte[] codeBytes) { - super(codeBytes); + super(codeBytes, CODE_INDEXER); } - @Override - protected int getCodeIndex(LedgerPermission permission) { - return permission.CODE & 0xFF; - } + private static class LedgerPermissionCodeIndexer implements CodeIndexer { + @Override + public int getCodeIndex(LedgerPermission permission) { + return permission.CODE & 0xFF; + } + + } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSecurityException.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSecurityException.java new file mode 100644 index 00000000..0b3e98a8 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerSecurityException.java @@ -0,0 +1,17 @@ +package com.jd.blockchain.ledger; + +public class LedgerSecurityException extends RuntimeException { + + private static final long serialVersionUID = -4090881296855827888L; + + + + public LedgerSecurityException(String message) { + super(message); + } + + public LedgerSecurityException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantDoesNotExistException.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantDoesNotExistException.java new file mode 100644 index 00000000..54994dc7 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantDoesNotExistException.java @@ -0,0 +1,15 @@ +package com.jd.blockchain.ledger; + +public class ParticipantDoesNotExistException extends LedgerException { + + private static final long serialVersionUID = 397450363050148898L; + + public ParticipantDoesNotExistException(String message) { + super(message); + } + + public ParticipantDoesNotExistException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java new file mode 100644 index 00000000..71b092ce --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java @@ -0,0 +1,121 @@ +package com.jd.blockchain.ledger; + +import java.util.BitSet; + +import com.jd.blockchain.utils.io.BytesSerializable; + +/** + * PrivilegeBitset 定义了用位表示的权限码; + * + * @author huanghaiquan + * + */ +public class PrivilegeBitset> implements Privilege, BytesSerializable { + + private BitSet permissionBits; + + private CodeIndexer codeIndexer; + + public PrivilegeBitset(CodeIndexer codeIndexer) { + this(new BitSet(), codeIndexer); + } + + public PrivilegeBitset(byte[] codeBytes, CodeIndexer codeIndexer) { + this(BitSet.valueOf(codeBytes), codeIndexer); + } + + private PrivilegeBitset(BitSet bits, CodeIndexer codeIndexer) { + this.permissionBits = bits; + this.codeIndexer = codeIndexer; + } + + public boolean isEnable(E permission) { + return permissionBits.get(codeIndexer.getCodeIndex(permission)); + } + + public void enable(E permission) { + permissionBits.set(codeIndexer.getCodeIndex(permission)); + } + + public void disable(E permission) { + permissionBits.clear(codeIndexer.getCodeIndex(permission)); + } + + @SuppressWarnings("unchecked") + public void enable(E... permissions) { + for (E p : permissions) { + permissionBits.set(codeIndexer.getCodeIndex(p)); + } + } + + @SuppressWarnings("unchecked") + public void disable(E... permissions) { + for (E p : permissions) { + permissionBits.clear(codeIndexer.getCodeIndex(p)); + } + } + + @Override + public byte[] toBytes() { + return permissionBits.toByteArray(); + } + + /** + * 把指定的权限合并到当前的权限中;
        + * + * @param privileges + * @return + */ + public Privilege union(PrivilegeBitset... privileges) { + return union(privileges, 0, privileges.length); + } + + /** + * 把指定的权限合并到当前的权限中;
        + * @param privileges + * @param offset + * @param count + * @return + */ + public Privilege union(PrivilegeBitset[] privileges, int offset, int count) { + BitSet bits = this.permissionBits; + for (int i = 0; i < count; i++) { + bits.or(privileges[i + offset].permissionBits); + } + return this; + } + + /** + * 保留当前的权限与指定权限的共同生效的部分,同时清除其它的权限位;
        + * + * @param privileges + * @return + */ + public Privilege intersect(PrivilegeBitset... privileges) { + return intersect(privileges, 0, privileges.length); + } + + /** + * 保留当前的权限与指定权限的共同生效的部分,同时清除其它的权限位;
        + * + * @param privileges + * @param offset + * @param count + * @return + */ + public Privilege intersect(PrivilegeBitset[] privileges, int offset, int count) { + BitSet bits = this.permissionBits; + for (int i = 0; i < count; i++) { + bits.and(privileges[i + offset].permissionBits); + } + return this; + } + + public PrivilegeBitset clone() { + return new PrivilegeBitset((BitSet) permissionBits.clone(), codeIndexer); + } + + static interface CodeIndexer> { + int getCodeIndex(E permission); + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privileges.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privileges.java new file mode 100644 index 00000000..420cbf45 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/Privileges.java @@ -0,0 +1,65 @@ +package com.jd.blockchain.ledger; + +public class Privileges implements PrivilegeSet { + + private LedgerPrivilege ledgerPrivilege; + + private TransactionPrivilege txPrivilege; + + protected Privileges() { + this.ledgerPrivilege = new LedgerPrivilege(); + this.txPrivilege = new TransactionPrivilege(); + } + + protected Privileges(PrivilegeSet privilege) { + this.ledgerPrivilege = privilege.getLedgerPrivilege(); + this.txPrivilege = privilege.getTransactionPrivilege(); + } + + protected Privileges(LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { + this.ledgerPrivilege = ledgerPrivilege; + this.txPrivilege = txPrivilege; + } + + @Override + public LedgerPrivilege getLedgerPrivilege() { + return ledgerPrivilege; + } + + public void setLedgerPrivilege(LedgerPrivilege ledgerPrivilege) { + this.ledgerPrivilege = ledgerPrivilege; + } + + @Override + public TransactionPrivilege getTransactionPrivilege() { + return txPrivilege; + } + + public void setTransactionPrivilege(TransactionPrivilege txPrivilege) { + this.txPrivilege = txPrivilege; + } + + public static Privileges configure() { + return new Privileges(); + } + + public Privileges enable(LedgerPermission...ledgerPermissions) { + this.ledgerPrivilege.enable(ledgerPermissions); + return this; + } + + public Privileges disable(LedgerPermission...ledgerPermissions) { + this.ledgerPrivilege.disable(ledgerPermissions); + return this; + } + + public Privileges enable(TransactionPermission...transactionPermissions) { + this.txPrivilege.enable(transactionPermissions); + return this; + } + + public Privileges disable(TransactionPermission...transactionPermissions) { + this.txPrivilege.disable(transactionPermissions); + return this; + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java index 159b6a48..21e394e2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java @@ -8,6 +8,17 @@ public interface RolePrivilegeSettings { public static final int MAX_ROLE_NAME_LENGTH = 20; long getRoleCount(); + + /** + * 加入新的角色授权;
        + * + * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; + * + * @param roleName 角色名称;不能超过 {@link #MAX_ROLE_NAME_LENGTH} 个 Unicode 字符; + * @param ledgerPrivilege + * @param txPrivilege + */ + long addRolePrivilege(String roleName, Privileges privileges); /** * 加入新的角色授权;
        diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java index 76db4d01..1b0b32ba 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivileges.java @@ -6,35 +6,28 @@ package com.jd.blockchain.ledger; * @author huanghaiquan * */ -public class RolePrivileges implements PrivilegeSet { +public class RolePrivileges extends Privileges { private String roleName; private long version; - private LedgerPrivilege ledgerPrivilege; - - private TransactionPrivilege txPrivilege; - public RolePrivileges(String roleName, long version) { this.roleName = roleName; this.version = version; - this.ledgerPrivilege = new LedgerPrivilege(); - this.txPrivilege = new TransactionPrivilege(); } public RolePrivileges(String roleName, long version, PrivilegeSet privilege) { + super(privilege); this.roleName = roleName; this.version = version; - this.ledgerPrivilege = privilege.getLedgerPrivilege(); - this.txPrivilege = privilege.getTransactionPrivilege(); } - public RolePrivileges(String roleName, long version, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege) { + public RolePrivileges(String roleName, long version, LedgerPrivilege ledgerPrivilege, + TransactionPrivilege txPrivilege) { + super(ledgerPrivilege, txPrivilege); this.roleName = roleName; this.version = version; - this.ledgerPrivilege = ledgerPrivilege; - this.txPrivilege = txPrivilege; } public String getRoleName() { @@ -45,22 +38,4 @@ public class RolePrivileges implements PrivilegeSet { return version; } - @Override - public LedgerPrivilege getLedgerPrivilege() { - return ledgerPrivilege; - } - - public void setLedgerPrivilege(LedgerPrivilege ledgerPrivilege) { - this.ledgerPrivilege = ledgerPrivilege; - } - - @Override - public TransactionPrivilege getTransactionPrivilege() { - return txPrivilege; - } - - public void setTransactionPrivilege(TransactionPrivilege txPrivilege) { - this.txPrivilege = txPrivilege; - } - } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java index 08408326..755a75a7 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java @@ -1,17 +1,23 @@ package com.jd.blockchain.ledger; -public class TransactionPrivilege extends AbstractPrivilege { +public class TransactionPrivilege extends PrivilegeBitset { + + private static final CodeIndexer CODE_INDEXER = new TransactionPermissionCodeIndexer(); public TransactionPrivilege() { + super(CODE_INDEXER); } public TransactionPrivilege(byte[] codeBytes) { - super(codeBytes); + super(codeBytes, CODE_INDEXER); } - @Override - protected int getCodeIndex(TransactionPermission permission) { - return permission.CODE & 0xFF; - } + private static class TransactionPermissionCodeIndexer implements CodeIndexer { + @Override + public int getCodeIndex(TransactionPermission permission) { + return permission.CODE & 0xFF; + } + + } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java index 6955eb94..a93f719e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java @@ -39,6 +39,16 @@ public enum TransactionState { */ CONTRACT_DOES_NOT_EXIST((byte) 0x04), + /** + * 参与方不存在; + */ + PARTICIPANT_DOES_NOT_EXIST((byte) 0x05), + + /** + * 被安全策略拒绝; + */ + REJECTED_BY_SECURITY_POLICY((byte) 0x10), + /** * 由于在错误的账本上执行交易而被丢弃; */ diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 79223ddd..f48e59c0 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -37,6 +37,7 @@ import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInfo; import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerTransaction; import com.jd.blockchain.ledger.NodeRequest; import com.jd.blockchain.ledger.Operation; @@ -44,6 +45,7 @@ import com.jd.blockchain.ledger.OperationResult; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionResponse; @@ -56,6 +58,8 @@ import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerSecurityManager; +import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.mocker.config.MockerConstant; import com.jd.blockchain.mocker.config.PresetAnswerPrompter; @@ -121,7 +125,7 @@ public class MockerNodeContext implements BlockchainQueryService { DataContractRegistry.register(ActionResponse.class); DataContractRegistry.register(ClientIdentifications.class); DataContractRegistry.register(ClientIdentification.class); - + // DataContractRegistry.register(LedgerAdminInfo.class); ByteArrayObjectUtil.init(); @@ -273,7 +277,7 @@ public class MockerNodeContext implements BlockchainQueryService { public LedgerInfo getLedger(HashDigest ledgerHash) { return queryService.getLedger(ledgerHash); } - + @Override public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { return queryService.getLedgerAdminInfo(ledgerHash); @@ -410,7 +414,7 @@ public class MockerNodeContext implements BlockchainQueryService { } @Override - public ContractInfo getContract(HashDigest ledgerHash, String address) { + public ContractInfo getContract(HashDigest ledgerHash, String address) { return queryService.getContract(ledgerHash, address); } @@ -443,8 +447,8 @@ public class MockerNodeContext implements BlockchainQueryService { LedgerEditor newEditor = ledgerRepository.createNextBlock(); LedgerBlock latestBlock = ledgerRepository.getLatestBlock(); LedgerDataset previousDataSet = ledgerRepository.getDataSet(latestBlock); - TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, previousDataSet, opHandler, - ledgerManager); + TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, + previousDataSet, opHandler, ledgerManager); TransactionResponse txResp = txProc.schedule(txRequest); TransactionBatchResultHandle handle = txProc.prepare(); handle.commit(); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index a57a181a..e4db1c39 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -16,7 +16,7 @@ import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.TransactionRequestContext; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; import com.jd.blockchain.ledger.core.handles.ContractLedgerContext; import com.jd.blockchain.mocker.proxy.ExecutorProxy; @@ -29,7 +29,7 @@ public class MockerContractExeHandle implements OperationHandle { private HashDigest ledgerHash; @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestContext requestContext, + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { ContractEventSendOperation contractOP = (ContractEventSendOperation) op; From d2885e5a374cda1163840dbbd9d2e90b741f5501 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 26 Aug 2019 11:38:14 +0800 Subject: [PATCH 049/124] Renamed test package of ledger-model; --- .../jd/blockchain/ledger/{data => }/AddressEncodingTest.java | 2 +- .../com/jd/blockchain/ledger/{data => }/BytesEncodingTest.java | 2 +- .../ledger/{data => }/BytesToBytesValueResolverTest.java | 2 +- .../jd/blockchain/ledger/{data => }/BytesValueEncodingTest.java | 2 +- .../ledger/{data => }/ContractCodeDeployOpTemplateTest.java | 2 +- .../ledger/{data => }/ContractEventSendOpTemplateTest.java | 2 +- .../com/jd/blockchain/ledger/{data => }/ContractTypeTest.java | 2 +- .../ledger/{data => }/DataAccountKVSetOpTemplateTest.java | 2 +- .../ledger/{data => }/DataAccountRegisterOpTemplateTest.java | 2 +- .../blockchain/ledger/{data => }/DigitalSignatureBlobTest.java | 2 +- .../jd/blockchain/ledger/{data => }/ED25519SignatureTest.java | 2 +- .../ledger/{data => }/IntegerToBytesValueResolverTest.java | 2 +- .../test/com/jd/blockchain/ledger/{data => }/KVDataTest.java | 2 +- .../ledger/{data => }/LongToBytesValueResolverTest.java | 2 +- .../com/jd/blockchain/ledger/{data => }/NormalContract.java | 2 +- .../com/jd/blockchain/ledger/{data => }/NormalContractImpl.java | 2 +- .../test/com/jd/blockchain/ledger/{data => }/OpBlobTest.java | 1 + .../ledger/{data => }/ShortToBytesValueResolverTest.java | 2 +- .../com/jd/blockchain/ledger/{data => }/SizeHeaderMaskTest.java | 2 +- .../ledger/{data => }/StringToBytesValueResolverTest.java | 2 +- .../com/jd/blockchain/ledger/{data => }/TxContentBlobTest.java | 2 +- .../jd/blockchain/ledger/{data => }/TxRequestMessageTest.java | 2 +- .../jd/blockchain/ledger/{data => }/TxResponseMessageTest.java | 2 +- .../ledger/{data => }/UserRegisterOpTemplateTest.java | 2 +- 24 files changed, 24 insertions(+), 23 deletions(-) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/AddressEncodingTest.java (96%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/BytesEncodingTest.java (96%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/BytesToBytesValueResolverTest.java (96%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/BytesValueEncodingTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/ContractCodeDeployOpTemplateTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/ContractEventSendOpTemplateTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/ContractTypeTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/DataAccountKVSetOpTemplateTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/DataAccountRegisterOpTemplateTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/DigitalSignatureBlobTest.java (98%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/ED25519SignatureTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/IntegerToBytesValueResolverTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/KVDataTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/LongToBytesValueResolverTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/NormalContract.java (96%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/NormalContractImpl.java (93%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/OpBlobTest.java (95%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/ShortToBytesValueResolverTest.java (96%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/SizeHeaderMaskTest.java (94%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/StringToBytesValueResolverTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/TxContentBlobTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/TxRequestMessageTest.java (99%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/TxResponseMessageTest.java (97%) rename source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/{data => }/UserRegisterOpTemplateTest.java (98%) diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/AddressEncodingTest.java similarity index 96% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/AddressEncodingTest.java index ae8dbef8..aab1dbef 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/AddressEncodingTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/AddressEncodingTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import java.util.Random; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesEncodingTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java similarity index 96% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesEncodingTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java index 07bd9c1e..b2211f08 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesEncodingTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.*; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesToBytesValueResolverTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesToBytesValueResolverTest.java similarity index 96% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesToBytesValueResolverTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesToBytesValueResolverTest.java index fb470200..3635743c 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesToBytesValueResolverTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesToBytesValueResolverTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataType; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesValueEncodingTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesValueEncodingTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesValueEncodingTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesValueEncodingTest.java index 73d51d7b..e5b624c9 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/BytesValueEncodingTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesValueEncodingTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.jd.blockchain.ledger.*; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractCodeDeployOpTemplateTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractCodeDeployOpTemplateTest.java index a54ce94f..5c6b24d2 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractCodeDeployOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractCodeDeployOpTemplateTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午10:53 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractEventSendOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractEventSendOpTemplateTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractEventSendOpTemplateTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractEventSendOpTemplateTest.java index bdfaaa80..d010ae6f 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractEventSendOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractEventSendOpTemplateTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午10:56 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractTypeTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractTypeTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractTypeTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractTypeTest.java index beceb39c..d6a49c9c 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractTypeTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ContractTypeTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountKVSetOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DataAccountKVSetOpTemplateTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountKVSetOpTemplateTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DataAccountKVSetOpTemplateTest.java index a87919b4..d8b33048 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountKVSetOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DataAccountKVSetOpTemplateTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午10:59 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DataAccountRegisterOpTemplateTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DataAccountRegisterOpTemplateTest.java index fcca954f..e13bad09 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DataAccountRegisterOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DataAccountRegisterOpTemplateTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午11:03 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DigitalSignatureBlobTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DigitalSignatureBlobTest.java index b3e73a0a..068a4040 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/DigitalSignatureBlobTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/DigitalSignatureBlobTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 下午2:12 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ED25519SignatureTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ED25519SignatureTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ED25519SignatureTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ED25519SignatureTest.java index 11cc02d1..a9a187ab 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ED25519SignatureTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ED25519SignatureTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import java.util.Random; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/IntegerToBytesValueResolverTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/IntegerToBytesValueResolverTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/IntegerToBytesValueResolverTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/IntegerToBytesValueResolverTest.java index 4b65ae16..8e39aa85 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/IntegerToBytesValueResolverTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/IntegerToBytesValueResolverTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataType; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/KVDataTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/KVDataTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/KVDataTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/KVDataTest.java index 229c308d..d1a6fcb9 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/KVDataTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/KVDataTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午11:08 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/LongToBytesValueResolverTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LongToBytesValueResolverTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/LongToBytesValueResolverTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LongToBytesValueResolverTest.java index 19cbdcbd..899a6415 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/LongToBytesValueResolverTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LongToBytesValueResolverTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataType; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/NormalContract.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/NormalContract.java similarity index 96% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/NormalContract.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/NormalContract.java index 87dcd127..12324b42 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/NormalContract.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/NormalContract.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.jd.blockchain.contract.Contract; import com.jd.blockchain.contract.ContractEvent; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/NormalContractImpl.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/NormalContractImpl.java similarity index 93% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/NormalContractImpl.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/NormalContractImpl.java index ad71598d..d2ba8714 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/NormalContractImpl.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/NormalContractImpl.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; public class NormalContractImpl implements NormalContract{ diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/OpBlobTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/OpBlobTest.java similarity index 95% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/OpBlobTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/OpBlobTest.java index 0692a3e4..6ea6da45 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/OpBlobTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/OpBlobTest.java @@ -1,3 +1,4 @@ +package test.com.jd.blockchain.ledger; //package test.com.jd.blockchain.ledger.data; // //import static org.junit.Assert.*; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ShortToBytesValueResolverTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ShortToBytesValueResolverTest.java similarity index 96% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ShortToBytesValueResolverTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ShortToBytesValueResolverTest.java index 6c4ef0c8..b35cedd7 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ShortToBytesValueResolverTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/ShortToBytesValueResolverTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataType; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/SizeHeaderMaskTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SizeHeaderMaskTest.java similarity index 94% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/SizeHeaderMaskTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SizeHeaderMaskTest.java index c1acfb00..b8e7875c 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/SizeHeaderMaskTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SizeHeaderMaskTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/StringToBytesValueResolverTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/StringToBytesValueResolverTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/StringToBytesValueResolverTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/StringToBytesValueResolverTest.java index 6538e88e..17dd0c04 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/StringToBytesValueResolverTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/StringToBytesValueResolverTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import com.alibaba.fastjson.JSON; import com.jd.blockchain.ledger.BytesValue; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxContentBlobTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxContentBlobTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxContentBlobTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxContentBlobTest.java index fc4120d3..0404ee38 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxContentBlobTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxContentBlobTest.java @@ -1,4 +1,4 @@ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxRequestMessageTest.java similarity index 99% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxRequestMessageTest.java index 6083089f..533eb0b7 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxRequestMessageTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxRequestMessageTest.java @@ -6,7 +6,7 @@ * Date: 2018/9/3 下午3:07 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxResponseMessageTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxResponseMessageTest.java similarity index 97% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxResponseMessageTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxResponseMessageTest.java index c0a944de..bcd50191 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/TxResponseMessageTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/TxResponseMessageTest.java @@ -6,7 +6,7 @@ * Date: 2018/9/6 上午11:00 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/UserRegisterOpTemplateTest.java similarity index 98% rename from source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/UserRegisterOpTemplateTest.java index 27b455f3..95be8d5c 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/UserRegisterOpTemplateTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/UserRegisterOpTemplateTest.java @@ -6,7 +6,7 @@ * Date: 2018/8/30 上午11:04 * Description: */ -package test.com.jd.blockchain.ledger.data; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertEquals; From 108122973314ec12b673fcaac5d67771559f3b6f Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 26 Aug 2019 12:34:30 +0800 Subject: [PATCH 050/124] Fixed the bug that: when the LedgerPrivilege or TransactionPrivilege property of Privileges is not configured, this property is null after deserialization recover this Privileges instance; --- .../ledger/core/LedgerSecurityManager.java | 11 +- .../core/LedgerSecurityManagerImpl.java | 6 +- .../core/TransactionBatchProcessor.java | 2 +- .../ledger/core/ContractInvokingTest.java | 2 +- .../core/LedgerSecurityManagerTest.java | 128 +++++++++++------- .../core/TransactionBatchProcessorTest.java | 2 +- .../blockchain/ledger/LedgerPermission.java | 4 +- .../jd/blockchain/ledger/PrivilegeBitset.java | 39 +++++- .../jd/blockchain/ledger/PrivilegesTest.java | 92 +++++++++++++ 9 files changed, 223 insertions(+), 63 deletions(-) create mode 100644 source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/PrivilegesTest.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java index 1ee72045..ac819e39 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManager.java @@ -6,8 +6,15 @@ import com.jd.blockchain.utils.Bytes; public interface LedgerSecurityManager { - String DEFAULT_ROLE = "_DEFAULT"; + String DEFAULT_ROLE = "DEFAULT"; - SecurityPolicy getSecurityPolicy(Set endpoints, Set nodes); + /** + * 创建一项与指定的终端用户和节点参与方相关的安全策略; + * + * @param endpoints 终端用户的地址列表; + * @param nodes 节点参与方的地址列表; + * @return 一项安全策略; + */ + SecurityPolicy createSecurityPolicy(Set endpoints, Set nodes); } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java index adfc33d8..37e07998 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java @@ -29,8 +29,9 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private UserRoleSettings userRolesSettings; + //用户的权限配置 private Map userPrivilegesCache = new ConcurrentHashMap<>(); - + private Map userRolesCache = new ConcurrentHashMap<>(); private Map rolesPrivilegeCache = new ConcurrentHashMap<>(); @@ -40,8 +41,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public SecurityPolicy getSecurityPolicy(Set endpoints, Set nodes) { - + public SecurityPolicy createSecurityPolicy(Set endpoints, Set nodes) { Map endpointPrivilegeMap = new HashMap<>(); Map nodePrivilegeMap = new HashMap<>(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index 96ebb5c4..37c47906 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -90,7 +90,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { TransactionRequestExtension reqExt = new TransactionRequestExtensionImpl(request); // 初始化交易的用户安全策略; - SecurityPolicy securityPolicy = securityManager.getSecurityPolicy(reqExt.getEndpointAddresses(), + SecurityPolicy securityPolicy = securityManager.createSecurityPolicy(reqExt.getEndpointAddresses(), reqExt.getNodeAddresses()); SecurityContext.setContextUsersPolicy(securityPolicy); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index f7821564..335a21bd 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -200,7 +200,7 @@ public class ContractInvokingTest { when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); - when(securityManager.getSecurityPolicy(any(), any())).thenReturn(securityPolicy); + when(securityManager.createSecurityPolicy(any(), any())).thenReturn(securityPolicy); return securityManager; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java index 1ea099c0..acbe45d7 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerSecurityManagerTest.java @@ -1,5 +1,6 @@ package test.com.jd.blockchain.ledger.core; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -18,10 +19,8 @@ import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.Privileges; -import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.UserRoleSettings; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerSecurityManager; import com.jd.blockchain.ledger.core.LedgerSecurityManagerImpl; @@ -56,30 +55,18 @@ public class LedgerSecurityManagerTest { CRYPTO_SETTINGS = cryptoConfig; } - private RolePrivilegeSettings initRoles(MemoryKVStorage testStorage, String[] roles, Privileges[] privilege) { + private RolePrivilegeDataset createRolePrivilegeDataset(MemoryKVStorage testStorage) { String prefix = "role-privilege/"; RolePrivilegeDataset rolePrivilegeDataset = new RolePrivilegeDataset(CRYPTO_SETTINGS, prefix, testStorage, testStorage); - for (int i = 0; i < roles.length; i++) { - rolePrivilegeDataset.addRolePrivilege(roles[i], privilege[i]); - } - - rolePrivilegeDataset.commit(); return rolePrivilegeDataset; } - private UserRoleSettings initUserRoless(MemoryKVStorage testStorage, Bytes[] userAddresses, RolesPolicy[] policies, - String[][] roles) { + private UserRoleDataset createUserRoleDataset(MemoryKVStorage testStorage) { String prefix = "user-roles/"; UserRoleDataset userRolesDataset = new UserRoleDataset(CRYPTO_SETTINGS, prefix, testStorage, testStorage); - for (int i = 0; i < userAddresses.length; i++) { - userRolesDataset.addUserRoles(userAddresses[i], policies[i], roles[i]); - } - - userRolesDataset.commit(); - return userRolesDataset; } @@ -87,55 +74,102 @@ public class LedgerSecurityManagerTest { public void testGetSecurityPolicy() { MemoryKVStorage testStorage = new MemoryKVStorage(); + // 定义不同角色用户的 keypair; final BlockchainKeypair kpManager = BlockchainKeyGenerator.getInstance().generate(); final BlockchainKeypair kpEmployee = BlockchainKeyGenerator.getInstance().generate(); final BlockchainKeypair kpDevoice = BlockchainKeyGenerator.getInstance().generate(); - - final Map endpoints = new HashMap<>(); - endpoints.put(kpManager.getAddress(), kpManager); - endpoints.put(kpEmployee.getAddress(), kpEmployee); - - final Map nodes = new HashMap<>(); - nodes.put(kpDevoice.getAddress(), kpDevoice); - + final BlockchainKeypair kpPlatform = BlockchainKeyGenerator.getInstance().generate(); + // 定义角色和权限; final String ROLE_ADMIN = "ID_ADMIN"; final String ROLE_OPERATOR = "OPERATOR"; final String ROLE_DATA_COLLECTOR = "DATA_COLLECTOR"; + final String ROLE_PLATFORM = "PLATFORM"; + // 定义管理员角色的权限:【账本权限只允许:注册用户、注册数据账户】【交易权限只允许:调用账本直接操作】 final Privileges PRIVILEGES_ADMIN = Privileges.configure() .enable(LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT) - .enable(TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION); + .enable(TransactionPermission.DIRECT_OPERATION); - final Privileges PRIVILEGES_OPERATOR = Privileges.configure() - .enable(LedgerPermission.WRITE_DATA_ACCOUNT, LedgerPermission.APPROVE_TX) + // 定义操作员角色的权限:【账本权限只允许:写入数据账户】【交易权限只允许:调用合约】 + final Privileges PRIVILEGES_OPERATOR = Privileges.configure().enable(LedgerPermission.WRITE_DATA_ACCOUNT) .enable(TransactionPermission.CONTRACT_OPERATION); + // 定义数据收集器角色的权限:【账本权限只允许:写入数据账户】【交易权限只允许:调用账本直接操作】 final Privileges PRIVILEGES_DATA_COLLECTOR = Privileges.configure().enable(LedgerPermission.WRITE_DATA_ACCOUNT) - .enable(TransactionPermission.CONTRACT_OPERATION); - - RolePrivilegeSettings rolePrivilegeSettings = initRoles(testStorage, - new String[] { ROLE_ADMIN, ROLE_OPERATOR, ROLE_DATA_COLLECTOR }, - new Privileges[] { PRIVILEGES_ADMIN, PRIVILEGES_OPERATOR, PRIVILEGES_DATA_COLLECTOR }); + .enable(TransactionPermission.DIRECT_OPERATION); + + // 定义平台角色的权限:【账本权限只允许:签署合约】 (只允许作为节点签署交易,不允许作为终端发起交易指令) + final Privileges PRIVILEGES_PLATFORM = Privileges.configure().enable(LedgerPermission.APPROVE_TX); + + RolePrivilegeDataset rolePrivilegeDataset = createRolePrivilegeDataset(testStorage); + long v = rolePrivilegeDataset.addRolePrivilege(ROLE_ADMIN, PRIVILEGES_ADMIN); + assertTrue(v > -1); + v = rolePrivilegeDataset.addRolePrivilege(ROLE_OPERATOR, PRIVILEGES_OPERATOR); + assertTrue(v > -1); + v = rolePrivilegeDataset.addRolePrivilege(ROLE_DATA_COLLECTOR, PRIVILEGES_DATA_COLLECTOR); + assertTrue(v > -1); + v = rolePrivilegeDataset.addRolePrivilege(ROLE_PLATFORM, PRIVILEGES_PLATFORM); + assertTrue(v > -1); + rolePrivilegeDataset.commit(); + // 为用户分配角色; String[] managerRoles = new String[] { ROLE_ADMIN, ROLE_OPERATOR }; String[] employeeRoles = new String[] { ROLE_OPERATOR }; String[] devoiceRoles = new String[] { ROLE_DATA_COLLECTOR }; - UserRoleSettings userRolesSettings = initUserRoless(testStorage, - new Bytes[] { kpManager.getAddress(), kpEmployee.getAddress(), kpDevoice.getAddress() }, - new RolesPolicy[] { RolesPolicy.UNION, RolesPolicy.UNION, RolesPolicy.UNION }, - new String[][] { managerRoles, employeeRoles, devoiceRoles }); - - LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(rolePrivilegeSettings, userRolesSettings); - - SecurityPolicy policy = securityManager.getSecurityPolicy(endpoints.keySet(), nodes.keySet()); - - assertTrue(policy.isEnableToEndpoints(LedgerPermission.REGISTER_USER, MultiIdsPolicy.AT_LEAST_ONE)); - assertTrue(policy.isEnableToEndpoints(LedgerPermission.REGISTER_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE)); - assertTrue(policy.isEnableToEndpoints(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE)); - assertTrue(policy.isEnableToEndpoints(LedgerPermission.APPROVE_TX, MultiIdsPolicy.AT_LEAST_ONE)); - assertFalse(policy.isEnableToEndpoints(LedgerPermission.REGISTER_USER, MultiIdsPolicy.ALL)); - assertFalse(policy.isEnableToEndpoints(LedgerPermission.AUTHORIZE_ROLES, MultiIdsPolicy.AT_LEAST_ONE)); + String[] platformRoles = new String[] { ROLE_PLATFORM }; + UserRoleDataset userRolesDataset = createUserRoleDataset(testStorage); + userRolesDataset.addUserRoles(kpManager.getAddress(), RolesPolicy.UNION, managerRoles); + userRolesDataset.addUserRoles(kpEmployee.getAddress(), RolesPolicy.UNION, employeeRoles); + userRolesDataset.addUserRoles(kpDevoice.getAddress(), RolesPolicy.UNION, devoiceRoles); + userRolesDataset.addUserRoles(kpPlatform.getAddress(), RolesPolicy.UNION, platformRoles); + userRolesDataset.commit(); + + // 创建安全管理器; + LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(rolePrivilegeDataset, userRolesDataset); + + // 定义终端用户列表;终端用户一起共同具有 ADMIN、OPERATOR 角色; + final Map endpoints = new HashMap<>(); + endpoints.put(kpManager.getAddress(), kpManager); + endpoints.put(kpEmployee.getAddress(), kpEmployee); + + // 定义节点参与方列表; + final Map nodes = new HashMap<>(); + nodes.put(kpPlatform.getAddress(), kpPlatform); + + // 创建一项与指定的终端用户和节点参与方相关的安全策略; + SecurityPolicy policy = securityManager.createSecurityPolicy(endpoints.keySet(), nodes.keySet()); + + // 校验安全策略的正确性; + LedgerPermission[] ledgerPermissions = LedgerPermission.values(); + for (LedgerPermission p : ledgerPermissions) { + // 终端节点有 ADMIN 和 OPERATOR 两种角色的合并权限; + if (p == LedgerPermission.REGISTER_USER || p == LedgerPermission.REGISTER_DATA_ACCOUNT + || p == LedgerPermission.WRITE_DATA_ACCOUNT) { + assertTrue(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + } else { + assertFalse(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + } + + if (p == LedgerPermission.APPROVE_TX) { + // 共识参与方只有 PLATFORM 角色的权限:核准交易; + assertTrue(policy.isEnableToNodes(p, MultiIdsPolicy.AT_LEAST_ONE)); + } else { + assertFalse(policy.isEnableToNodes(p, MultiIdsPolicy.AT_LEAST_ONE)); + } + } + + TransactionPermission[] transactionPermissions = TransactionPermission.values(); + for (TransactionPermission p : transactionPermissions) { + // 终端节点有 ADMIN 和 OPERATOR 两种角色的合并权限; + if (p == TransactionPermission.DIRECT_OPERATION || p == TransactionPermission.CONTRACT_OPERATION) { + assertTrue(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + } else { + assertFalse(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + } + + assertFalse(policy.isEnableToNodes(p, MultiIdsPolicy.AT_LEAST_ONE)); + } } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java index 20cc013c..8c402f98 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java @@ -125,7 +125,7 @@ public class TransactionBatchProcessorTest { when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); - when(securityManager.getSecurityPolicy(any(), any())).thenReturn(securityPolicy); + when(securityManager.createSecurityPolicy(any(), any())).thenReturn(securityPolicy); return securityManager; } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java index dba568ab..18d0fdfd 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java @@ -45,7 +45,9 @@ public enum LedgerPermission { /** * 参与方核准交易;
        * - * 如果不具备此项权限,则无法作为网关节点接入并签署由终端提交的交易; + * 如果不具备此项权限,则无法作为节点签署由终端提交的交易; + *

        + * 只对交易请求的节点签名列表{@link TransactionRequest#getNodeSignatures()}的用户产生影响; */ APPROVE_TX((byte) 0x06), diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java index 71b092ce..aea812f9 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java @@ -11,17 +11,37 @@ import com.jd.blockchain.utils.io.BytesSerializable; * */ public class PrivilegeBitset> implements Privilege, BytesSerializable { + // 加入前缀位,可避免序列化时输出空的字节数组; + private static final boolean[] PREFIX = { false, false, false, true, false, false, false, true }; + private static final int OFFSET = PREFIX.length; + private static final int MAX_SIZE = 256 - PREFIX.length; private BitSet permissionBits; private CodeIndexer codeIndexer; public PrivilegeBitset(CodeIndexer codeIndexer) { - this(new BitSet(), codeIndexer); + this.permissionBits = new BitSet(); + this.codeIndexer = codeIndexer; + // 设置前缀; + for (int i = 0; i < PREFIX.length; i++) { + permissionBits.set(i, PREFIX[i]); + } } public PrivilegeBitset(byte[] codeBytes, CodeIndexer codeIndexer) { - this(BitSet.valueOf(codeBytes), codeIndexer); + if (codeBytes.length > MAX_SIZE) { + throw new IllegalArgumentException( + "The size of code bytes specified to PrivilegeBitset exceed the max size[" + MAX_SIZE + "]!"); + } + this.permissionBits = BitSet.valueOf(codeBytes); + this.codeIndexer = codeIndexer; + // 校验前缀; + for (int i = 0; i < PREFIX.length; i++) { + if (permissionBits.get(i) != PREFIX[i]) { + throw new IllegalArgumentException("The code bytes is not match the privilege prefix code!"); + } + } } private PrivilegeBitset(BitSet bits, CodeIndexer codeIndexer) { @@ -30,28 +50,28 @@ public class PrivilegeBitset> implements Privilege, BytesSe } public boolean isEnable(E permission) { - return permissionBits.get(codeIndexer.getCodeIndex(permission)); + return permissionBits.get(index(permission)); } public void enable(E permission) { - permissionBits.set(codeIndexer.getCodeIndex(permission)); + permissionBits.set(index(permission)); } public void disable(E permission) { - permissionBits.clear(codeIndexer.getCodeIndex(permission)); + permissionBits.clear(index(permission)); } @SuppressWarnings("unchecked") public void enable(E... permissions) { for (E p : permissions) { - permissionBits.set(codeIndexer.getCodeIndex(p)); + permissionBits.set(index(p)); } } @SuppressWarnings("unchecked") public void disable(E... permissions) { for (E p : permissions) { - permissionBits.clear(codeIndexer.getCodeIndex(p)); + permissionBits.clear(index(p)); } } @@ -72,6 +92,7 @@ public class PrivilegeBitset> implements Privilege, BytesSe /** * 把指定的权限合并到当前的权限中;
        + * * @param privileges * @param offset * @param count @@ -115,6 +136,10 @@ public class PrivilegeBitset> implements Privilege, BytesSe return new PrivilegeBitset((BitSet) permissionBits.clone(), codeIndexer); } + private int index(E permission) { + return OFFSET + codeIndexer.getCodeIndex(permission); + } + static interface CodeIndexer> { int getCodeIndex(E permission); } diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/PrivilegesTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/PrivilegesTest.java new file mode 100644 index 00000000..10f964c0 --- /dev/null +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/PrivilegesTest.java @@ -0,0 +1,92 @@ +package test.com.jd.blockchain.ledger; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.PrivilegeSet; +import com.jd.blockchain.ledger.Privileges; +import com.jd.blockchain.ledger.TransactionPermission; + +public class PrivilegesTest { + + @Test + public void test() { + // 正常情形; + { + Privileges privileges = Privileges.configure() + .enable(LedgerPermission.REGISTER_USER, LedgerPermission.APPROVE_TX) + .enable(TransactionPermission.DIRECT_OPERATION); + + byte[] bytes = BinaryProtocol.encode(privileges, PrivilegeSet.class); + + PrivilegeSet decodePrivileges = BinaryProtocol.decode(bytes); + + assertNotNull(decodePrivileges.getLedgerPrivilege()); + assertNotNull(decodePrivileges.getTransactionPrivilege()); + + for (LedgerPermission p : LedgerPermission.values()) { + if (p == LedgerPermission.REGISTER_USER || p == LedgerPermission.APPROVE_TX) { + assertTrue(decodePrivileges.getLedgerPrivilege().isEnable(p)); + } else { + assertFalse(decodePrivileges.getLedgerPrivilege().isEnable(p)); + } + } + for (TransactionPermission p : TransactionPermission.values()) { + if (p == TransactionPermission.DIRECT_OPERATION) { + assertTrue(decodePrivileges.getTransactionPrivilege().isEnable(p)); + } else { + assertFalse(decodePrivileges.getTransactionPrivilege().isEnable(p)); + } + } + } + // 只定义账本权限的情形; + { + Privileges privileges = Privileges.configure().enable(LedgerPermission.REGISTER_USER, + LedgerPermission.APPROVE_TX); + + byte[] bytes = BinaryProtocol.encode(privileges, PrivilegeSet.class); + + PrivilegeSet decodePrivileges = BinaryProtocol.decode(bytes); + + assertNotNull(decodePrivileges.getLedgerPrivilege()); + assertNotNull(decodePrivileges.getTransactionPrivilege()); + + for (LedgerPermission p : LedgerPermission.values()) { + if (p == LedgerPermission.REGISTER_USER || p == LedgerPermission.APPROVE_TX) { + assertTrue(decodePrivileges.getLedgerPrivilege().isEnable(p)); + } else { + assertFalse(decodePrivileges.getLedgerPrivilege().isEnable(p)); + } + } + for (TransactionPermission p : TransactionPermission.values()) { + assertFalse(decodePrivileges.getTransactionPrivilege().isEnable(p)); + } + } + // 只定义交易权限的情形; + { + Privileges privileges = Privileges.configure().enable(TransactionPermission.CONTRACT_OPERATION); + + byte[] bytes = BinaryProtocol.encode(privileges, PrivilegeSet.class); + + PrivilegeSet decodePrivileges = BinaryProtocol.decode(bytes); + + assertNotNull(decodePrivileges.getLedgerPrivilege()); + assertNotNull(decodePrivileges.getTransactionPrivilege()); + + for (LedgerPermission p : LedgerPermission.values()) { + assertFalse(decodePrivileges.getLedgerPrivilege().isEnable(p)); + } + for (TransactionPermission p : TransactionPermission.values()) { + if (p == TransactionPermission.CONTRACT_OPERATION) { + assertTrue(decodePrivileges.getTransactionPrivilege().isEnable(p)); + } else { + assertFalse(decodePrivileges.getTransactionPrivilege().isEnable(p)); + } + } + } + } + +} From cd63d7612b824745f19f29041e685285ba427abb Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Mon, 26 Aug 2019 19:43:31 +0800 Subject: [PATCH 051/124] add regist participant code for feature --- .../com/jd/blockchain/consts/DataCodes.java | 10 +- .../BftsmartConsensusSettingsBuilder.java | 81 ++++++++++++++ .../consensus/ConsensusSettingsBuilder.java | 4 + .../mq/MsgQueueConsensusSettingsBuilder.java | 47 ++++++++ .../jd/blockchain/ContractDeployExeUtil.java | 20 +--- source/ledger/ledger-core/pom.xml | 5 + .../blockchain/ledger/core/LedgerSetting.java | 24 ---- .../ledger/core/ParticipantCertData.java | 13 +++ .../DefaultOperationHandleRegisteration.java | 7 +- .../ParticipantRegisterOperationHandle.java | 105 ++++++++++++++++++ .../ledger/ContractInvokingTest.java | 1 + .../blockchain/ledger/LedgerManagerTest.java | 15 +-- .../blockchain/ledger/TransactionSetTest.java | 18 +-- .../jd/blockchain/ledger/ParticipantInfo.java | 87 +++++++++------ .../ledger/ParticipantInfoData.java | 65 +++++++++++ .../jd/blockchain/ledger/ParticipantNode.java | 8 ++ .../ledger/ParticipantNodeState.java | 43 +++++++ .../ledger/ParticipantRegisterOperation.java | 13 +++ .../BlockchainOperationFactory.java | 28 +++-- .../transaction/ClientOperator.java | 2 +- .../transaction/ConsensusParticipantData.java | 12 ++ .../transaction/ParticipantOperator.java | 11 ++ .../ParticipantRegisterOpTemplate.java | 24 ++++ .../ParticipantRegisterOperationBuilder.java | 20 ++++ ...rticipantRegisterOperationBuilderImpl.java | 11 ++ .../jd/blockchain/transaction/TxBuilder.java | 3 + .../jd/blockchain/transaction/TxTemplate.java | 6 + .../converters/BinaryMessageConverter.java | 1 + .../peer/web/ManagementController.java | 17 +-- .../sdk/converters/ClientResolveUtil.java | 12 +- .../sdk/client/GatewayServiceFactory.java | 1 + .../test/SDK_GateWay_Participant_Test_.java | 99 +++++++++++++++++ .../intgr/perf/LedgerPerformanceTest.java | 14 +-- .../com/jd/blockchain/intgr/perf/Utils.java | 18 ++- .../jd/blockchain/intgr/IntegrationBase.java | 29 +++++ .../capability/service/SettingsInit.java | 15 +-- .../initializer/LedgerInitProperties.java | 11 ++ .../blockchain/mocker/MockerNodeContext.java | 1 + 38 files changed, 729 insertions(+), 172 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java create mode 100644 source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 4085f8cb..8f0d8d3d 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -49,9 +49,11 @@ public interface DataCodes { public static final int TX_OP_CONTRACT_EVENT_SEND = 0x340; - public static final int TX_RESPONSE = 0x350; + public static final int TX_OP_PARTICIPANT_REG = 0x350; - public static final int TX_OP_RESULT = 0x360; + public static final int TX_RESPONSE = 0x360; + + public static final int TX_OP_RESULT = 0x370; public static final int METADATA = 0x600; @@ -69,7 +71,7 @@ public interface DataCodes { public static final int METADATA_CONSENSUS_SETTING = 0x631; - // public static final int METADATA_PARTICIPANT_INFO = 0x640; + public static final int METADATA_PARTICIPANT_INFO = 0x640; public static final int METADATA_CRYPTO_SETTING = 0x642; @@ -100,6 +102,8 @@ public interface DataCodes { public static final int ENUM_TYPE_BYTES_VALUE_TYPE = 0xB23; + public static final int ENUM_TYPE_PARTICIPANT_NODE_STATE = 0xB24; + public static final int DIGITALSIGNATURE = 0xB30; public static final int DIGITALSIGNATURE_BODY = 0xB31; diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java index e72f866a..af57f726 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java @@ -2,11 +2,18 @@ package com.jd.blockchain.consensus.bftsmart; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; +import com.jd.blockchain.consensus.ConsensusProviders; +import com.jd.blockchain.consensus.NodeSettings; +import com.jd.blockchain.ledger.ParticipantInfo; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; +import com.jd.blockchain.utils.Property; import com.jd.blockchain.utils.codec.Base58Utils; import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.net.NetworkAddress; @@ -64,6 +71,8 @@ public class BftsmartConsensusSettingsBuilder implements ConsensusSettingsBuilde */ public static final String CONSENSUS_SECURE_PATTERN = "system.server.%s.network.secure"; + public static final String BFTSMART_PROVIDER = "com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider"; + private static Properties CONFIG_TEMPLATE; @@ -164,6 +173,30 @@ public class BftsmartConsensusSettingsBuilder implements ConsensusSettingsBuilde return config; } + @Override + public Bytes updateSettings(Bytes oldConsensusSettings, ParticipantInfo participantInfo) { + + //update consensus setting through node and system config two aspects + BftsmartConsensusSettings consensusSettings = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(oldConsensusSettings.toBytes()); + + Property[] systemConfigs = systemConfigs(consensusSettings.getSystemConfigs()); + + BftsmartNodeSettings[] nodeSettings = nodeSettings(consensusSettings.getNodes(), participantInfo); + + BftsmartConsensusConfig bftsmartConsensusConfig = new BftsmartConsensusConfig(nodeSettings, systemConfigs); + + for(int i = 0 ;i < bftsmartConsensusConfig.getNodes().length; i++) { + System.out.printf("id = %d, host = %s, port = %d\r\n", bftsmartConsensusConfig.getNodes()[i].getId(), bftsmartConsensusConfig.getNodes()[i].getNetworkAddress().getHost(), bftsmartConsensusConfig.getNodes()[i].getNetworkAddress().getPort()); + } + + for(int i = 0 ;i < bftsmartConsensusConfig.getSystemConfigs().length; i++) { + System.out.printf("property name = %s, property value = %s\r\n",bftsmartConsensusConfig.getSystemConfigs()[i].getName(), bftsmartConsensusConfig.getSystemConfigs()[i].getValue()); + } + + return new Bytes(ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().encode(bftsmartConsensusConfig)); + + } + private static String keyOfNode(String pattern, int id) { return String.format(pattern, id); } @@ -224,4 +257,52 @@ public class BftsmartConsensusSettingsBuilder implements ConsensusSettingsBuilde PropertiesUtils.setValues(props, bftsmartSettings.getSystemConfigs()); } + /** + * + * update system.servers.num property + * + */ + private Property[] systemConfigs(Property[] systemConfigs) { + Map propertyMap = convert2Map(systemConfigs); + int serverNum = Integer.parseInt(propertyMap.get("system.servers.num").getValue()); + propertyMap.put("system.servers.num", new Property("system.servers.num", String.valueOf(serverNum + 1))); + return convert2Array(propertyMap); + + } + + private Map convert2Map(Property[] properties) { + Map propertyMap = new HashMap<>(); + for (Property property : properties) { + propertyMap.put(property.getName(), property); + } + return propertyMap; + } + + private Property[] convert2Array(Map map) { + Property[] properties = new Property[map.size()]; + int index = 0; + for (Map.Entry entry : map.entrySet()) { + properties[index++] = entry.getValue(); + } + return properties; + } + + /** + * + * update node setting + * + */ + private BftsmartNodeSettings[] nodeSettings(NodeSettings[] nodeSettings, ParticipantInfo participantInfo) { + + BftsmartNodeConfig bftsmartNodeConfig = new BftsmartNodeConfig(participantInfo.getPubKey(), nodeSettings.length, participantInfo.getNetworkAddress()); + + BftsmartNodeSettings[] bftsmartNodeSettings = new BftsmartNodeSettings[nodeSettings.length + 1]; + for (int i = 0; i < nodeSettings.length; i++) { + bftsmartNodeSettings[i] = (BftsmartNodeSettings)nodeSettings[i]; + } + bftsmartNodeSettings[nodeSettings.length] = bftsmartNodeConfig; + return bftsmartNodeSettings; + } + + } diff --git a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettingsBuilder.java b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettingsBuilder.java index c4f46776..4a58f024 100644 --- a/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettingsBuilder.java +++ b/source/consensus/consensus-framework/src/main/java/com/jd/blockchain/consensus/ConsensusSettingsBuilder.java @@ -1,6 +1,8 @@ package com.jd.blockchain.consensus; +import com.jd.blockchain.ledger.ParticipantInfo; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.utils.Bytes; import java.util.Properties; @@ -16,6 +18,8 @@ public interface ConsensusSettingsBuilder { * @return */ ConsensusSettings createSettings(Properties props, ParticipantNode[] participantNodes); + + Bytes updateSettings(Bytes oldConsensusSettings, ParticipantInfo participantInfo); Properties createPropertiesTemplate(); diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java index 85b1fbc3..9eba0389 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java @@ -8,6 +8,7 @@ */ package com.jd.blockchain.consensus.mq; +import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.consensus.ConsensusSettingsBuilder; import com.jd.blockchain.consensus.NodeSettings; @@ -21,11 +22,13 @@ import com.jd.blockchain.consensus.mq.settings.MsgQueueNetworkSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueNodeSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.ParticipantInfo; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.io.BytesEncoder; import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.io.FileUtils; import com.jd.blockchain.utils.net.NetworkAddress; @@ -82,6 +85,8 @@ public class MsgQueueConsensusSettingsBuilder implements ConsensusSettingsBuilde public static final String MSG_QUEUE_BLOCK_MAXDELAY = "system.msg.queue.block.maxdelay"; + public static final String MSG_QUEUE_PROVIDER = "com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider"; + private static Properties CONFIG_TEMPLATE; static { @@ -145,6 +150,48 @@ public class MsgQueueConsensusSettingsBuilder implements ConsensusSettingsBuilde return consensusConfig; } + private MsgQueueNodeSettings[] nodeSettings(NodeSettings[] nodeSettings, ParticipantInfo participantInfo) { + + MsgQueueNodeSettings msgQueueNodeSettings = new MsgQueueNodeConfig(); + ((MsgQueueNodeConfig) msgQueueNodeSettings).setAddress(AddressEncoding.generateAddress(participantInfo.getPubKey()).toBase58()); + ((MsgQueueNodeConfig) msgQueueNodeSettings).setPubKey(participantInfo.getPubKey()); + + MsgQueueNodeSettings[] msgQueuetNodeSettings = new MsgQueueNodeSettings[nodeSettings.length + 1]; + for (int i = 0; i < nodeSettings.length; i++) { + msgQueuetNodeSettings[i] = (MsgQueueNodeSettings)nodeSettings[i]; + } + msgQueuetNodeSettings[nodeSettings.length] = msgQueueNodeSettings; + + return msgQueuetNodeSettings; + } + + @Override + public Bytes updateSettings(Bytes oldConsensusSettings, ParticipantInfo participantInfo) { + + BytesEncoder consensusEncoder = ConsensusProviders.getProvider(MSG_QUEUE_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder(); + + MsgQueueConsensusSettings consensusSettings = (MsgQueueConsensusSettings) consensusEncoder.decode(oldConsensusSettings.toBytes()); + + MsgQueueNodeSettings[] nodeSettings = nodeSettings(consensusSettings.getNodes(), participantInfo); + + MsgQueueConsensusConfig msgQueueConsensusConfig = new MsgQueueConsensusConfig(); + for (int i = 0; i < nodeSettings.length; i++) { + msgQueueConsensusConfig.addNodeSettings(nodeSettings[i]); + } + + msgQueueConsensusConfig.setBlockSettings(consensusSettings.getBlockSettings()); + + msgQueueConsensusConfig.setNetworkSettings(consensusSettings.getNetworkSettings()); + + + for(int i = 0 ;i < msgQueueConsensusConfig.getNodes().length; i++) { + System.out.printf("node addr = %s\r\n", msgQueueConsensusConfig.getNodes()[i].getAddress()); + } + + return new Bytes(consensusEncoder.encode(msgQueueConsensusConfig)); + + } + @Override public Properties createPropertiesTemplate() { return PropertiesUtils.cloneFrom(CONFIG_TEMPLATE); diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java index a206a854..24a25a90 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java @@ -9,24 +9,7 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainIdentityData; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.EndpointRequest; -import com.jd.blockchain.ledger.NodeRequest; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.PreparedTransaction; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionContentBody; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.TransactionTemplate; -import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.*; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.tools.keygen.KeyGenCommand; @@ -111,6 +94,7 @@ public enum ContractDeployExeUtil { DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); } public BlockchainService initBcsrv(String host, int port) { diff --git a/source/ledger/ledger-core/pom.xml b/source/ledger/ledger-core/pom.xml index 3938916c..e7bfe29e 100644 --- a/source/ledger/ledger-core/pom.xml +++ b/source/ledger/ledger-core/pom.xml @@ -40,6 +40,11 @@ contract-framework ${project.version} + + com.jd.blockchain + consensus-framework + ${project.version} + com.jd.blockchain contract-jvm diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java deleted file mode 100644 index 6e9ad134..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSetting.java +++ /dev/null @@ -1,24 +0,0 @@ -//package com.jd.blockchain.ledger.core; -// -//import com.jd.blockchain.binaryproto.DataContract; -//import com.jd.blockchain.binaryproto.DataField; -//import com.jd.blockchain.binaryproto.PrimitiveType; -//import com.jd.blockchain.consts.DataCodes; -//import com.jd.blockchain.ledger.CryptoSetting; -//import com.jd.blockchain.utils.Bytes; -// -//@DataContract(code = DataCodes.METADATA_LEDGER_SETTING) -//public interface LedgerSetting { -// -// @DataField(order=0, primitiveType=PrimitiveType.TEXT) -// String getConsensusProvider(); -// -// @DataField(order=1, primitiveType=PrimitiveType.BYTES) -// Bytes getConsensusSetting(); -// -// @DataField(order=2, refContract=true) -// CryptoSetting getCryptoSetting(); -// -//// PrivilegeModelSetting getPrivilegesModelSetting(); -// -//} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java index 05fd0611..b7918b51 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; /** * 参与方证书数据对象; @@ -15,6 +16,7 @@ public class ParticipantCertData implements ParticipantNode { private String address; private String name; private PubKey pubKey; + private ParticipantNodeState participantNodeState; public ParticipantCertData() { } @@ -24,6 +26,7 @@ public class ParticipantCertData implements ParticipantNode { this.address = participantNode.getAddress(); this.name = participantNode.getName(); this.pubKey = participantNode.getPubKey(); + this.participantNodeState = participantNode.getParticipantNodeState(); } public ParticipantCertData(String address, String name, PubKey pubKey) { @@ -54,4 +57,14 @@ public class ParticipantCertData implements ParticipantNode { public void setId(int id) { this.id = id; } + + @Override + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + + public void setParticipantNodeState(ParticipantNodeState participantNodeState) { + this.participantNodeState = participantNodeState; + } + } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java index 1966c716..d7db478d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java @@ -3,15 +3,11 @@ package com.jd.blockchain.ledger.core.impl; import java.util.ArrayList; import java.util.List; +import com.jd.blockchain.ledger.core.impl.handles.*; import org.springframework.stereotype.Component; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.core.OperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.ContractCodeDeployOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.JVMContractEventSendOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.DataAccountKVSetOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.DataAccountRegisterOperationHandle; -import com.jd.blockchain.ledger.core.impl.handles.UserRegisterOperationHandle; @Component public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { @@ -29,6 +25,7 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis opHandles.add(new DataAccountKVSetOperationHandle()); opHandles.add(new DataAccountRegisterOperationHandle()); opHandles.add(new UserRegisterOperationHandle()); + opHandles.add(new ParticipantRegisterOperationHandle()); opHandles.add(new ContractCodeDeployOperationHandle()); opHandles.add(new JVMContractEventSendOperationHandle()); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java new file mode 100644 index 00000000..e06d813c --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java @@ -0,0 +1,105 @@ +package com.jd.blockchain.ledger.core.impl.handles; + +import com.jd.blockchain.consensus.ConsensusProvider; +import com.jd.blockchain.consensus.ConsensusProviders; +import com.jd.blockchain.crypto.AddressEncoding; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.ledger.core.impl.OperationHandleContext; +import com.jd.blockchain.utils.Bytes; + +public class ParticipantRegisterOperationHandle implements OperationHandle { + @Override + public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, + LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + + ParticipantRegisterOperation participantRegOp = (ParticipantRegisterOperation) op; + + System.out.println("ParticipantRegisterOperationHandle start\r\n"); + + LedgerAdminAccount adminAccount = dataset.getAdminAccount(); + + ParticipantInfo participantInfo = participantRegOp.getParticipantInfo(); + + ConsensusProvider provider = ConsensusProviders.getProvider(adminAccount.getSetting().getConsensusProvider()); + + ParticipantNode participantNode = new PartNode((int)(adminAccount.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTED); + + LedgerAdminAccount.LedgerMetadataImpl metadata = (LedgerAdminAccount.LedgerMetadataImpl) adminAccount.getMetadata(); + + + PubKey pubKey = participantNode.getPubKey(); + + BlockchainIdentityData identityData = new BlockchainIdentityData(pubKey); + + //update consensus setting + Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(metadata.getSetting().getConsensusSetting(), participantInfo); + + LedgerSetting ledgerSetting = new LedgerConfiguration(adminAccount.getSetting().getConsensusProvider(), + newConsensusSettings, metadata.getSetting().getCryptoSetting()); + + metadata.setSetting(ledgerSetting); +// metadata.setViewId(metadata.getViewId() + 1); + + //reg participant as user + dataset.getUserAccountSet().register(identityData.getAddress(), pubKey); + + //add new participant as consensus node + adminAccount.addParticipant(participantNode); + + return null; + } + + @Override + public boolean support(Class operationType) { + return ParticipantRegisterOperation.class.isAssignableFrom(operationType); + } + + private static class PartNode implements ParticipantNode { + + private int id; + + private String address; + + private String name; + + private PubKey pubKey; + + private ParticipantNodeState participantNodeState; + + public PartNode(int id, String name, PubKey pubKey, ParticipantNodeState participantNodeState) { + this.id = id; + this.name = name; + this.pubKey = pubKey; + this.address = AddressEncoding.generateAddress(pubKey).toBase58(); + this.participantNodeState = participantNodeState; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getAddress() { + return address; + } + + @Override + public String getName() { + return name; + } + + @Override + public PubKey getPubKey() { + return pubKey; + } + + @Override + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + } + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index fd9df2d4..3ee6f875 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -32,6 +32,7 @@ public class ContractInvokingTest { DataContractRegistry.register(EndpointRequest.class); DataContractRegistry.register(TransactionResponse.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); } private static final String LEDGER_KEY_PREFIX = "LDG://"; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java index dfc17f24..a38e5ca8 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertNull; import java.util.stream.Stream; +import com.jd.blockchain.ledger.*; import org.junit.Before; import org.junit.Test; @@ -19,19 +20,6 @@ import com.jd.blockchain.crypto.SignatureFunction; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.BlockBody; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.DigitalSignature; -import com.jd.blockchain.ledger.LedgerBlock; -import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.LedgerTransaction; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.ContractAccountSet; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DataAccountSet; @@ -56,6 +44,7 @@ public class LedgerManagerTest { DataContractRegistry.register(TransactionContent.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(BlockBody.class); DataContractRegistry.register(CryptoProvider.class); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java index f023a987..ebfdc55a 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java @@ -9,28 +9,13 @@ import static org.junit.Assert.assertTrue; import java.util.Random; +import com.jd.blockchain.ledger.*; import org.junit.Test; import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.BytesDataList; -import com.jd.blockchain.ledger.BytesValueList; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.DigitalSignature; -import com.jd.blockchain.ledger.LedgerTransaction; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.TransactionSet; import com.jd.blockchain.ledger.core.impl.LedgerTransactionData; import com.jd.blockchain.ledger.core.impl.TransactionStagedSnapshot; @@ -51,6 +36,7 @@ public class TransactionSetTest { DataContractRegistry.register(DataAccountKVSetOperation.class); DataContractRegistry.register(ContractCodeDeployOperation.class); DataContractRegistry.register(ContractEventSendOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); CryptoSetting defCryptoSetting = LedgerTestUtils.createDefaultCryptoSetting(); MemoryKVStorage testStorage = new MemoryKVStorage(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java index 903cabfd..07f6e1d0 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java @@ -1,35 +1,52 @@ -//package com.jd.blockchain.ledger; -// -//import com.jd.blockchain.base.data.TypeCodes; -//import com.jd.blockchain.binaryproto.DataContract; -//import com.jd.blockchain.binaryproto.DataField; -//import com.jd.blockchain.crypto.asymmetric.PubKey; -// -//import my.utils.ValueType; -// -///** -// * 参与方信息; -// * -// * @author huanghaiquan -// * -// */ -//@DataContract(code = TypeCodes.METADATA_PARTICIPANT_INFO) -//public interface ParticipantInfo { -// -// /** -// * 参与者名称; -// * -// * @return -// */ -// @DataField(order = 1, primitiveType = ValueType.TEXT) -// String getName(); -// -// /** -// * 公钥; -// * -// * @return -// */ -// @DataField(order = 2, primitiveType = ValueType.BYTES) -// PubKey getPubKey(); -// -//} \ No newline at end of file +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.utils.net.NetworkAddress; + + +/** + * 参与方信息; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.METADATA_PARTICIPANT_INFO) +public interface ParticipantInfo { + + /** + * regist or unregist; + * + * @return + */ + @DataField(order = 0, primitiveType = PrimitiveType.TEXT) + String getFlag(); + + /** + * 参与者名称; + * + * @return + */ + @DataField(order = 1, primitiveType = PrimitiveType.TEXT) + String getName(); + + /** + * 公钥; + * + * @return + */ + @DataField(order = 2, primitiveType = PrimitiveType.BYTES) + PubKey getPubKey(); + + /** + * 共识协议的网络地址; + * + * @return + */ + @DataField(order = 3, primitiveType = PrimitiveType.BYTES) + NetworkAddress getNetworkAddress(); + +} \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java new file mode 100644 index 00000000..b41bb079 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java @@ -0,0 +1,65 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.utils.net.NetworkAddress; + +/** + * 即将要注册的参与方的信息 + * @author zhangshuang + * @create 2019/7/8 + * @since 1.0.0 + */ +public class ParticipantInfoData implements ParticipantInfo { + + + private String name; + + private PubKey pubKey; + + private NetworkAddress networkAddress; + + private String flag;//代表注册参与方或者删除参与方 + + public ParticipantInfoData(String flag, String name, PubKey pubKey, NetworkAddress networkAddress) { + this.flag = flag; + this.name = name; + this.pubKey = pubKey; + this.networkAddress = networkAddress; + } + + @Override + public String getFlag() { + return flag; + } + + public void setFlag(String flag) { + this.flag = flag; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public PubKey getPubKey() { + return pubKey; + } + + public void setPubKey(PubKey pubKey) { + this.pubKey = pubKey; + } + + @Override + public NetworkAddress getNetworkAddress() { + return networkAddress; + } + + public void setNetworkAddress(NetworkAddress networkAddress) { + this.networkAddress = networkAddress; + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java index dd2c62fa..82730bba 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNode.java @@ -48,4 +48,12 @@ public interface ParticipantNode {// extends ConsensusNode, ParticipantInfo { */ @DataField(order = 3, primitiveType = PrimitiveType.BYTES) PubKey getPubKey(); + + /** + * 节点的状态:已注册/已参与共识 + * + * @return + */ + @DataField(order = 4, refEnum = true) + ParticipantNodeState getParticipantNodeState(); } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java new file mode 100644 index 00000000..375f196b --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java @@ -0,0 +1,43 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.EnumContract; +import com.jd.blockchain.binaryproto.EnumField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + + +/** + * 参与方节点状态 + * @author zhangshuang + * @create 2019/7/8 + * @since 1.0.0 + */ +@EnumContract(code= DataCodes.ENUM_TYPE_PARTICIPANT_NODE_STATE) +public enum ParticipantNodeState { + + /** + * 已注册; + */ + REGISTED((byte) 0), + + /** + * 已共识; + */ + CONSENSUSED((byte) 1); + + @EnumField(type= PrimitiveType.INT8) + public final byte CODE; + + private ParticipantNodeState(byte code) { + this.CODE = code; + } + + public static ParticipantNodeState valueOf(byte code) { + for (ParticipantNodeState tr : values()) { + if (tr.CODE == code) { + return tr; + } + } + throw new IllegalArgumentException("Unsupported participant node state code!"); + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java new file mode 100644 index 00000000..2762e924 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java @@ -0,0 +1,13 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.DataCodes; + +@DataContract(code= DataCodes.TX_OP_PARTICIPANT_REG) +public interface ParticipantRegisterOperation extends Operation { + + @DataField(order=1, refContract = true) + ParticipantInfo getParticipantInfo(); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index ef9b138a..e9d7fa77 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -4,17 +4,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BytesValue; -import com.jd.blockchain.ledger.BytesValueList; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.LedgerInitOperation; -import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.*; import com.jd.blockchain.utils.Bytes; /** @@ -33,6 +23,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe // private static final ContractEventSendOperationBuilderImpl CONTRACT_EVENT_SEND_OP_BUILDER = new ContractEventSendOperationBuilderImpl(); + private static final ParticipantRegisterOperationBuilderImpl PARTICIPANT_REG_OP_BUILDER = new ParticipantRegisterOperationBuilderImpl(); + private LedgerInitOperationBuilder ledgerInitOpBuilder = new LedgerInitOperationBuilderFilter(); private UserRegisterOperationBuilder userRegOpBuilder = new UserRegisterOperationBuilderFilter(); @@ -45,6 +37,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private ContractInvocationProxyBuilder contractInvoProxyBuilder = new ContractInvocationProxyBuilder(); + private ParticipantRegisterOperationBuilder participantRegOpBuilder = new ParticipantRegisterOperationBuilderFilter(); + // TODO: 暂时只支持单线程情形,未考虑多线程; private List operationList = new ArrayList<>(); @@ -82,6 +76,9 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe return contractEventSendOpBuilder; } + @Override + public ParticipantRegisterOperationBuilder participants() {return participantRegOpBuilder;} + @Override public T contract(String address, Class contractIntf) { return contractInvoProxyBuilder.create(address, contractIntf, contractEventSendOpBuilder); @@ -256,6 +253,15 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe } } + private class ParticipantRegisterOperationBuilderFilter implements ParticipantRegisterOperationBuilder { + @Override + public ParticipantRegisterOperation register(ParticipantInfo participantInfo) { + ParticipantRegisterOperation op = PARTICIPANT_REG_OP_BUILDER.register(participantInfo); + operationList.add(op); + return op; + } + } + private class ContractEventSendOperationBuilderFilter implements ContractEventSendOperationBuilder { @Override diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java index 5e47ef89..8797b4ce 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java @@ -6,6 +6,6 @@ package com.jd.blockchain.transaction; * @author huanghaiquan * */ -public interface ClientOperator extends UserOperator, DataAccountOperator, ContractOperator, EventOperator { +public interface ClientOperator extends UserOperator, DataAccountOperator, ContractOperator, EventOperator, ParticipantOperator { } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java index b4e64744..99a233ba 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java @@ -2,6 +2,7 @@ package com.jd.blockchain.transaction; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; import com.jd.blockchain.utils.net.NetworkAddress; public class ConsensusParticipantData implements ParticipantNode { @@ -16,6 +17,9 @@ public class ConsensusParticipantData implements ParticipantNode { private NetworkAddress hostAddress; + private ParticipantNodeState participantNodeState; + + public int getId() { return id; } @@ -56,4 +60,12 @@ public class ConsensusParticipantData implements ParticipantNode { this.address = address; } + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + + public void setParticipantState(ParticipantNodeState participantNodeState) { + this.participantNodeState = participantNodeState; + } + } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java new file mode 100644 index 00000000..94237cc3 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java @@ -0,0 +1,11 @@ +package com.jd.blockchain.transaction; + +public interface ParticipantOperator { + + /** + * 注册参与方操作; + * + * @return + */ + ParticipantRegisterOperationBuilder participants(); +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java new file mode 100644 index 00000000..3eeaea53 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java @@ -0,0 +1,24 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.ledger.ParticipantInfo; +import com.jd.blockchain.ledger.ParticipantRegisterOperation; + +public class ParticipantRegisterOpTemplate implements ParticipantRegisterOperation { + + static { + DataContractRegistry.register(ParticipantRegisterOperation.class); + } + + private ParticipantInfo participantInfo; + + public ParticipantRegisterOpTemplate(ParticipantInfo participantInfo) { + + this.participantInfo = participantInfo; + } + + @Override + public ParticipantInfo getParticipantInfo() { + return participantInfo; + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java new file mode 100644 index 00000000..3776b691 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java @@ -0,0 +1,20 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.ParticipantInfo; +import com.jd.blockchain.ledger.ParticipantRegisterOperation; + +public interface ParticipantRegisterOperationBuilder { + + /** + * 注册; + * + * @param + * + * @param + * + * @return + */ + ParticipantRegisterOperation register(ParticipantInfo participantInfo); + + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java new file mode 100644 index 00000000..56339bfa --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java @@ -0,0 +1,11 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.ParticipantInfo; +import com.jd.blockchain.ledger.ParticipantRegisterOperation; + +public class ParticipantRegisterOperationBuilderImpl implements ParticipantRegisterOperationBuilder { + @Override + public ParticipantRegisterOperation register(ParticipantInfo participantNode) { + return new ParticipantRegisterOpTemplate(participantNode); + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java index 1ff23a2f..9a810c56 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java @@ -110,6 +110,9 @@ public class TxBuilder implements TransactionBuilder { return opFactory.contractEvents(); } + @Override + public ParticipantRegisterOperationBuilder participants() {return opFactory.participants(); } + @Override public T contract(Bytes address, Class contractIntf) { return opFactory.contract(address, contractIntf); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java index 9777d238..e448db0e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java @@ -65,6 +65,12 @@ public class TxTemplate implements TransactionTemplate { return txBuilder.contracts(); } + @Override + public ParticipantRegisterOperationBuilder participants() { + stateManager.operate(); + return txBuilder.participants(); + } + @Override public T contract(Bytes address, Class contractIntf) { stateManager.operate(); diff --git a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java b/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java index 124b85cd..b2683e2f 100644 --- a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java +++ b/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java @@ -46,6 +46,7 @@ public class BinaryMessageConverter extends AbstractHttpMessageConverter DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(ActionRequest.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index ac95d361..ec2443a3 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.jd.blockchain.ledger.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -31,21 +32,6 @@ import com.jd.blockchain.consensus.service.NodeServer; import com.jd.blockchain.consensus.service.ServerSettings; import com.jd.blockchain.consensus.service.StateMachineReplicate; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.EndpointRequest; -import com.jd.blockchain.ledger.LedgerBlock; -import com.jd.blockchain.ledger.LedgerInitOperation; -import com.jd.blockchain.ledger.NodeRequest; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionContentBody; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerAdminAccount; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerRepository; @@ -116,6 +102,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java index 3c251a66..7cce33c1 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java @@ -193,7 +193,7 @@ public class ClientResolveUtil { JSONObject pubKeyObj = currConsensusParticipant.getJSONObject("pubKey"); String pubKeyBase58 = pubKeyObj.getString("value"); // 生成ParticipantNode对象 - ParticipantCertData participantCertData = new ParticipantCertData(id, addressBase58, name, new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes())); + ParticipantCertData participantCertData = new ParticipantCertData(id, addressBase58, name, new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes()), ParticipantNodeState.CONSENSUSED); participantNodes[i] = participantCertData; } ledgerInitSettingData.setConsensusParticipants(participantNodes); @@ -275,6 +275,7 @@ public class ClientResolveUtil { private String address; private String name; private PubKey pubKey; + private ParticipantNodeState participantNodeState; public ParticipantCertData() { } @@ -285,11 +286,12 @@ public class ClientResolveUtil { this.pubKey = participantNode.getPubKey(); } - public ParticipantCertData(int id, String address, String name, PubKey pubKey) { + public ParticipantCertData(int id, String address, String name, PubKey pubKey, ParticipantNodeState participantNodeState) { this.id = id; this.address = address; this.name = name; this.pubKey = pubKey; + this.participantNodeState = participantNodeState; } @Override @@ -314,6 +316,12 @@ public class ClientResolveUtil { public void setId(int id) { this.id = id; } + + @Override + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + } public static class KvData implements KVDataEntry { diff --git a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java index 1fb01269..df8a4991 100644 --- a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java +++ b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java @@ -47,6 +47,7 @@ public class GatewayServiceFactory implements BlockchainServiceFactory, Closeabl DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(ActionRequest.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java new file mode 100644 index 00000000..c4e0e76a --- /dev/null +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java @@ -0,0 +1,99 @@ +package test.com.jd.blockchain.sdk.test; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.*; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.sdk.samples.SDKDemo_Constant; +import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.net.NetworkAddress; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * 注册参与方测试 + * @author zhangshuang + * @create 2019/7/4 + * @since 1.0.0 + */ + +public class SDK_GateWay_Participant_Test_ { + + private PrivKey privKey; + private PubKey pubKey; + + private BlockchainKeypair CLIENT_CERT = null; + + private String GATEWAY_IPADDR = null; + + private int GATEWAY_PORT; + + private boolean SECURE; + + private BlockchainService service; + + //根据密码工具产生的公私钥 + static String PUB = "3snPdw7i7PkdgqiGX7GbZuFSi1cwZn7vtjw4vifb1YoXgr9k6Kfmis"; + String PRIV = "177gjtZu8w1phqHFVNiFhA35cfimXmP6VuqrBFhfbXBWK8s4TRwro2tnpffwP1Emwr6SMN6"; + + @Before + public void init() { + + privKey = SDK_GateWay_KeyPair_Para.privkey1; + pubKey = SDK_GateWay_KeyPair_Para.pubKey1; + + CLIENT_CERT = new BlockchainKeypair(SDK_GateWay_KeyPair_Para.pubKey0, SDK_GateWay_KeyPair_Para.privkey0); + GATEWAY_IPADDR = "127.0.0.1"; + GATEWAY_PORT = 11000; + SECURE = false; + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, + CLIENT_CERT); + service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); + } + + @Test + public void registerParticipant_Test() { + HashDigest[] ledgerHashs = service.getLedgerHashs(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); + + //existed signer + AsymmetricKeypair keyPair = new BlockchainKeypair(pubKey, privKey); + + PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV, SDKDemo_Constant.PASSWORD); + + PubKey pubKey = KeyGenCommand.decodePubKey(PUB); + + System.out.println("Address = "+AddressEncoding.generateAddress(pubKey)); + + BlockchainKeypair user = new BlockchainKeypair(pubKey, privKey); + + NetworkAddress networkAddress = new NetworkAddress("127.0.0.1", 20000); + + ParticipantInfo participantInfo = new ParticipantInfoData("add","Peer4", user.getPubKey(), networkAddress); + + // 注册参与方 + txTemp.participants().register(participantInfo); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + + // 使用私钥进行签名; + prepTx.sign(keyPair); + + // 提交交易; + TransactionResponse transactionResponse = prepTx.commit(); + assertTrue(transactionResponse.isSuccess()); + + } +} diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index 76f3a1e7..7f6ee769 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.DoubleStream; import com.jd.blockchain.crypto.*; +import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.CryptoConfig; import org.springframework.core.io.ClassPathResource; @@ -18,18 +19,6 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.BytesDataList; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.LedgerBlock; -import com.jd.blockchain.ledger.LedgerInitOperation; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerDataSet; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; @@ -87,6 +76,7 @@ public class LedgerPerformanceTest { DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(DataAccountKVSetOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); } public static void test(String[] args) { diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index 80b133ad..67f4781a 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -10,6 +10,7 @@ import com.jd.blockchain.crypto.*; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; import com.jd.blockchain.tools.keygen.KeyGenCommand; import org.springframework.core.io.ClassPathResource; @@ -77,7 +78,7 @@ public class Utils { public static ParticipantNode[] loadParticipantNodes() { ParticipantNode[] participantNodes = new ParticipantNode[PUB_KEYS.length]; for (int i = 0; i < PUB_KEYS.length; i++) { - participantNodes[i] = new PartNode(i, KeyGenCommand.decodePubKey(PUB_KEYS[i])); + participantNodes[i] = new PartNode(i, KeyGenCommand.decodePubKey(PUB_KEYS[i]), ParticipantNodeState.CONSENSUSED); } return participantNodes; } @@ -235,15 +236,18 @@ public class Utils { private PubKey pubKey; - public PartNode(int id, PubKey pubKey) { - this(id, id + "", pubKey); + private ParticipantNodeState participantNodeState; + + public PartNode(int id, PubKey pubKey, ParticipantNodeState participantNodeState) { + this(id, id + "", pubKey, participantNodeState); } - public PartNode(int id, String name, PubKey pubKey) { + public PartNode(int id, String name, PubKey pubKey, ParticipantNodeState participantNodeState) { this.id = id; this.name = name; this.pubKey = pubKey; this.address = pubKey.toBase58(); + this.participantNodeState = participantNodeState; } @Override @@ -265,6 +269,12 @@ public class Utils { public PubKey getPubKey() { return pubKey; } + + @Override + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + } } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index a13445a5..c553dcd9 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -166,6 +166,35 @@ public class IntegrationBase { return kvResponse; } + public static KeyPairResponse testSDK_RegisterParticipant(AsymmetricKeypair adminKey, HashDigest ledgerHash, + BlockchainService blockchainService) { + // 注册参与方,并验证最终写入; + BlockchainKeypair participant = BlockchainKeyGenerator.getInstance().generate(); + + // 定义交易; + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + + ParticipantInfoData participantInfoData = new ParticipantInfoData("add", "peer4", participant.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); + + txTpl.participants().register(participantInfoData); + + // 签名; + PreparedTransaction ptx = txTpl.prepare(); + + HashDigest transactionHash = ptx.getHash(); + + ptx.sign(adminKey); + + // 提交并等待共识返回; + TransactionResponse txResp = ptx.commit(); + + KeyPairResponse keyPairResponse = new KeyPairResponse(); + keyPairResponse.keyPair = participant; + keyPairResponse.txResp = txResp; + keyPairResponse.txHash = transactionHash; + return keyPairResponse; + } + public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerRepository ledgerRepository, KeyPairType keyPairType) { TransactionResponse txResp = keyPairResponse.txResp; diff --git a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java index 4723d615..f6c9bced 100644 --- a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java +++ b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java @@ -17,19 +17,7 @@ import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.EndpointRequest; -import com.jd.blockchain.ledger.LedgerBlock; -import com.jd.blockchain.ledger.NodeRequest; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionContentBody; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.*; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.codec.Base58Utils; @@ -58,6 +46,7 @@ public class SettingsInit { DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index 7b48cec6..db75db26 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -13,6 +13,7 @@ import com.jd.blockchain.consts.Global; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.PropertiesUtils; import com.jd.blockchain.utils.codec.HexUtils; @@ -272,6 +273,8 @@ public class LedgerInitProperties { // private NetworkAddress consensusAddress; + private ParticipantNodeState participantNodeState; + private NetworkAddress initializerAddress; public int getId() { @@ -303,6 +306,14 @@ public class LedgerInitProperties { // this.pubKeyPath = pubKeyPath; // } + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + + public void setParticipantNodeState(ParticipantNodeState participantNodeState) { + this.participantNodeState = participantNodeState; + } + public NetworkAddress getInitializerAddress() { return initializerAddress; } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 82a79c61..56aced5d 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -86,6 +86,7 @@ public class MockerNodeContext implements BlockchainQueryService { DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(DataAccountKVSetOperation.class); DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); From 0ef87b5108e435d30c4ad2e43e700f9c63a5e13c Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 26 Aug 2019 22:59:19 +0800 Subject: [PATCH 052/124] Refactored ; --- .../core/LedgerSecurityManagerImpl.java | 13 +++- .../core/LedgerTransactionalEditor.java | 4 -- .../ledger/core/RolePrivilegeDataset.java | 10 ++- .../ledger/core/UserRoleDataset.java | 5 ++ .../ledger/core/LedgerAdminDatasetTest.java | 36 +++++----- .../ledger/RolePrivilegeSettings.java | 66 +++++++++++++------ .../blockchain/ledger/UserRoleSettings.java | 43 +++++++----- 7 files changed, 110 insertions(+), 67 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java index 37e07998..6b74c2ce 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerSecurityException; import com.jd.blockchain.ledger.RolePrivilegeSettings; @@ -29,9 +30,9 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private UserRoleSettings userRolesSettings; - //用户的权限配置 + // 用户的权限配置 private Map userPrivilegesCache = new ConcurrentHashMap<>(); - + private Map userRolesCache = new ConcurrentHashMap<>(); private Map rolesPrivilegeCache = new ConcurrentHashMap<>(); @@ -39,7 +40,13 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { this.rolePrivilegeSettings = rolePrivilegeSettings; this.userRolesSettings = userRolesSettings; } - + + + public static void initSecuritySettings(LedgerInitSetting initSettings, LedgerEditor editor) { + + } + + @Override public SecurityPolicy createSecurityPolicy(Set endpoints, Set nodes) { Map endpointPrivilegeMap = new HashMap<>(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java index a6395d92..70658ae2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java @@ -8,7 +8,6 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.BlockBody; import com.jd.blockchain.ledger.BlockRollbackException; import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.IllegalTransactionException; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerDataSnapshot; @@ -16,15 +15,12 @@ import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.LedgerTransaction; import com.jd.blockchain.ledger.OperationResult; -import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRollbackException; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.storage.service.utils.BufferedKVStorage; -import com.jd.blockchain.transaction.SignatureUtils; -import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.Base58Utils; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java index 8798ca66..c5685ecf 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java @@ -92,7 +92,7 @@ public class RolePrivilegeDataset implements Transactional, MerkleProvable, Role } /** - * 设置角色授权;
        + * 设置角色权限;
        * 如果版本校验不匹配,则返回 -1; * * @param roleAuth @@ -108,7 +108,7 @@ public class RolePrivilegeDataset implements Transactional, MerkleProvable, Role } /** - * 更新角色授权;
        + * 更新角色权限;
        * 如果指定的角色不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; * * @param participant @@ -242,7 +242,7 @@ public class RolePrivilegeDataset implements Transactional, MerkleProvable, Role } /** - * 查询角色授权; + * 查询角色权限; * *
        * 如果不存在,则返回 null; @@ -279,4 +279,8 @@ public class RolePrivilegeDataset implements Transactional, MerkleProvable, Role return getRolePrivileges(0, (int) getRoleCount()); } + @Override + public boolean isReadonly() { + return dataset.isReadonly(); + } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java index 0c6d72d1..355f5973 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java @@ -166,4 +166,9 @@ public class UserRoleDataset implements Transactional, MerkleProvable, UserRoleS return pns; } + @Override + public boolean isReadonly() { + return dataset.isReadonly(); + } + } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index 1967b0c0..3c31a02e 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -88,45 +88,45 @@ public class LedgerAdminDatasetTest { MemoryKVStorage testStorage = new MemoryKVStorage(); // Create intance with init setting; - LedgerAdminDataset ledgerAdminAccount = new LedgerAdminDataset(initSetting, keyPrefix, testStorage, + LedgerAdminDataset ledgerAdminDataset = new LedgerAdminDataset(initSetting, keyPrefix, testStorage, testStorage); - ledgerAdminAccount.getRolePrivileges().addRolePrivilege("DEFAULT", + ledgerAdminDataset.getRolePrivileges().addRolePrivilege("DEFAULT", new LedgerPermission[] { LedgerPermission.AUTHORIZE_ROLES, LedgerPermission.REGISTER_USER, LedgerPermission.APPROVE_TX }, new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION }); - ledgerAdminAccount.getUserRoles().addUserRoles(parties[0].getAddress(), RolesPolicy.UNION, "DEFAULT"); + ledgerAdminDataset.getUserRoles().addUserRoles(parties[0].getAddress(), RolesPolicy.UNION, "DEFAULT"); // New created instance is updated until being committed; - assertTrue(ledgerAdminAccount.isUpdated()); + assertTrue(ledgerAdminDataset.isUpdated()); // Hash of account is null until being committed; - assertNull(ledgerAdminAccount.getHash()); + assertNull(ledgerAdminDataset.getHash()); - LedgerMetadata_V2 meta = ledgerAdminAccount.getMetadata(); + LedgerMetadata_V2 meta = ledgerAdminDataset.getMetadata(); assertNull(meta.getParticipantsHash()); // Commit, and check the storage keys; - ledgerAdminAccount.commit(); + ledgerAdminDataset.commit(); // New created instance isn't updated after being committed; - assertFalse(ledgerAdminAccount.isUpdated()); + assertFalse(ledgerAdminDataset.isUpdated()); // Hash of account isn't null after being committed; - assertNotNull(ledgerAdminAccount.getHash()); + assertNotNull(ledgerAdminDataset.getHash()); - meta = ledgerAdminAccount.getMetadata(); + meta = ledgerAdminDataset.getMetadata(); assertNotNull(meta.getParticipantsHash()); assertNotNull(meta.getSettingsHash()); assertNotNull(meta.getRolePrivilegesHash()); assertNotNull(meta.getUserRolesHash()); - assertNotNull(ledgerAdminAccount.getRolePrivileges().getRolePrivilege("DEFAULT")); + assertNotNull(ledgerAdminDataset.getRolePrivileges().getRolePrivilege("DEFAULT")); // ---------------------- // Reload account from storage with readonly mode, and check the integrity of // data; - HashDigest adminAccHash = ledgerAdminAccount.getHash(); + HashDigest adminAccHash = ledgerAdminDataset.getHash(); LedgerAdminDataset reloadAdminAccount1 = new LedgerAdminDataset(adminAccHash, keyPrefix, testStorage, testStorage, true); @@ -137,15 +137,15 @@ public class LedgerAdminDatasetTest { assertNotNull(meta2.getUserRolesHash()); // verify realod settings of admin account; - verifyRealoadingSettings(reloadAdminAccount1, adminAccHash, ledgerAdminAccount.getMetadata(), - ledgerAdminAccount.getSettings()); + verifyRealoadingSettings(reloadAdminAccount1, adminAccHash, ledgerAdminDataset.getMetadata(), + ledgerAdminDataset.getSettings()); // verify the consensus participant list; verifyRealoadingParities(reloadAdminAccount1, parties1); // It will throw exeception because of this account is readonly; verifyReadonlyState(reloadAdminAccount1); - verifyRealoadingRoleAuthorizations(reloadAdminAccount1, ledgerAdminAccount.getRolePrivileges(), - ledgerAdminAccount.getUserRoles()); + verifyRealoadingRoleAuthorizations(reloadAdminAccount1, ledgerAdminDataset.getRolePrivileges(), + ledgerAdminDataset.getUserRoles()); // -------------- // 重新加载,并进行修改; @@ -178,8 +178,8 @@ public class LedgerAdminDatasetTest { // load the last version of account and verify again; LedgerAdminDataset previousAdminAccount = new LedgerAdminDataset(adminAccHash, keyPrefix, testStorage, testStorage, true); - verifyRealoadingSettings(previousAdminAccount, adminAccHash, ledgerAdminAccount.getMetadata(), - ledgerAdminAccount.getSettings()); + verifyRealoadingSettings(previousAdminAccount, adminAccHash, ledgerAdminDataset.getMetadata(), + ledgerAdminDataset.getSettings()); verifyRealoadingParities(previousAdminAccount, parties1); verifyReadonlyState(previousAdminAccount); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java index 21e394e2..b678d644 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java @@ -1,16 +1,55 @@ package com.jd.blockchain.ledger; public interface RolePrivilegeSettings { - + /** * 角色名称的最大 Unicode 字符数; */ public static final int MAX_ROLE_NAME_LENGTH = 20; + /** + * 角色的数量; + * + * @return + */ long getRoleCount(); - + + /** + * 查询角色权限; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + RolePrivileges getRolePrivilege(String roleName); + + /** + * 返回指定位置的角色权限; + * + * @param index + * @param count + * @return + */ + RolePrivileges[] getRolePrivileges(int index, int count); + /** - * 加入新的角色授权;
        + * 返回所有的角色权限; + * + * @return + */ + RolePrivileges[] getRolePrivileges(); + + /** + * 是否只读; + * + * @return + */ + boolean isReadonly(); + + /** + * 加入新的角色权限;
        * * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; * @@ -21,7 +60,7 @@ public interface RolePrivilegeSettings { long addRolePrivilege(String roleName, Privileges privileges); /** - * 加入新的角色授权;
        + * 加入新的角色权限;
        * * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; * @@ -32,7 +71,7 @@ public interface RolePrivilegeSettings { long addRolePrivilege(String roleName, LedgerPrivilege ledgerPrivilege, TransactionPrivilege txPrivilege); /** - * 加入新的角色授权;
        + * 加入新的角色权限;
        * * 如果指定的角色已经存在,则引发 {@link LedgerException} 异常; * @@ -45,7 +84,7 @@ public interface RolePrivilegeSettings { long addRolePrivilege(String roleName, LedgerPermission[] ledgerPermissions, TransactionPermission[] txPermissions); /** - * 更新角色授权;
        + * 更新角色权限;
        * 如果指定的角色不存在,或者版本不匹配,则引发 {@link LedgerException} 异常; * * @param participant @@ -116,19 +155,4 @@ public interface RolePrivilegeSettings { long disablePermissions(String roleName, LedgerPermission[] ledgerPermissions, TransactionPermission[] txPermissions); - /** - * 查询角色授权; - * - *
        - * 如果不存在,则返回 null; - * - * @param address - * @return - */ - RolePrivileges getRolePrivilege(String roleName); - - RolePrivileges[] getRolePrivileges(int index, int count); - - RolePrivileges[] getRolePrivileges(); - } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java index 15ef546c..f7426ccf 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java @@ -16,6 +16,31 @@ public interface UserRoleSettings { */ long getUserCount(); + /** + * 查询角色授权; + * + *
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + UserRoles getUserRoles(Bytes userAddress); + + /** + * 返回全部的用户授权; + * + * @return + */ + UserRoles[] getUserRoles(); + + /** + * 是否只读; + * + * @return + */ + boolean isReadonly(); + /** * 加入新的用户角色授权;
        * @@ -46,22 +71,4 @@ public interface UserRoleSettings { */ long setRoles(Bytes userAddress, RolesPolicy policy, String... roles); - /** - * 查询角色授权; - * - *
        - * 如果不存在,则返回 null; - * - * @param address - * @return - */ - UserRoles getUserRoles(Bytes userAddress); - - /** - * 返回全部的用户授权; - * - * @return - */ - UserRoles[] getUserRoles(); - } \ No newline at end of file From 82c2233fed8a88853b30d2a9ae54e24e3bf89de5 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 27 Aug 2019 03:05:41 +0800 Subject: [PATCH 053/124] Upgraded readme; --- README.md | 649 ++++--------------------------------- docs/design.md | 630 +++++++++++++++++++++++++++++++++++ docs/images/deployment.jpg | Bin 0 -> 131011 bytes 3 files changed, 700 insertions(+), 579 deletions(-) create mode 100644 docs/design.md create mode 100644 docs/images/deployment.jpg diff --git a/README.md b/README.md index 6b98db84..6854f3b7 100644 --- a/README.md +++ b/README.md @@ -6,621 +6,112 @@ ------------------------------------------------------------------------ -### 版本修订历史 - - - - - - - - - - - - - - - - - - - - - - - - - -
        版本号作 者修改日期备 注
        0.0.6黄海泉2017-11-10 - 定义JD区块链项目的目标与关键能力;
        - 定义JD区块链的核心对象模型;
        - 定义“账户”的生成算法和“区块/交易/操作/账户”的关键属性;
        - 描述了编程接口的示例代码; -
        0.0.7黄海泉2017-11-17 - 丰富了对“节点共识”、“节点分区”两项关键能力的详细描述; -
        0.0.8黄海泉2018-07-17 - 增加部署图;增加智能合约开发的示例; -
        ------------------------------------------------------------------------- - -## 一、概述 -JD区块链项目的目标是提供一个面向广泛的应用场景、满足企业核心需求的灵活和易用的区块链系统。 -以下是 JD 区块链用以满足企业核心需求的关键能力,也是显著区别于其它区块链的重要特征: - - - 快速共识 - - 节点分区 - - 并行多账本 - - 大数据伸缩存储 - - 条件检索 - - 面向对象的合约代码编程模型 - - 节点快速部署 - - 多终端灵活接入 - - 分布式自治的账户权限管理模型 - -JD区块链对于关键能力的定义是建立在深入理解和抽象各种多样化需求的基础上的。 - - - 快速共识(Efficient Consensus) - 我们认为,“快速”不仅仅体现在“用更短时间”达成共识,还要体现在交易(Transaction)要得到可靠地执行。 - 需要在算法和实现层面做出保障,确保所有提交的合法的交易会被系统在一个“确定性”和“足够短”的时间之内被严格地执行,系统不主动作出随机性地丢弃(不包括系统故障因素)。注:POW类算法产生的链分叉处理是一种系统随机性地丢弃交易的行为。 - 从使用者的视角来看,这种能力就是区块链系统的“可靠性”,这对于企业、金融场景而言尤其重要。 - - - 节点分区(Peer Partition) - “分区”是一种分布式系统架构原则,通过将大范围目标按照某种相似特征分隔为一个个小范围目标,分别进行更高效地处理,这能从整体上提升整个系统的处理能力。 - 区块链系统也是一种分布式系统,沿用“分区”的思想这点来自以往的系统架构实践的经验,是有可能让区块链系统获得可媲美现有系统的处理能力。这种能力将可以无障碍地把区块链在应用于广泛的企业场景中。 - 在此,我们所说的“节点分区(Peer Partition)” 是共识过程中的物理通讯层面的分区,在共识过程中只有相关的物理节点才会建立通讯链路并复制和存储共识的状态数据。在一个区块链系统中,可以从节点全集中选择一个或多个节点子集,分别组成一个或多个节点分区(Peer Partition) - - - 并行多账本 - 账本(Ledger) - - 大数据伸缩存储 - - 条件检索 - - 面向对象的合约代码编程模型 - - 节点快速部署 - - 多终端灵活接入 - - 分布式自治的账户权限管理模型 - -## 二、对象模型 -JD区块链的核心对象包括: - - - 账本(Ledger) - 一份账本(Ledger)实质上是由两部分组成: - - 一组以“账户(Account)”表示的状态数据; - - 以“区块的链条(Block-chain)”表示的状态数据的变更历史; - - JD区块链的“账本(Ledger)”是一个最顶层的管理数据的逻辑单元。在一个区块链节点构成的共识网络中,可以维护多套并行的“账本(Ledger)”。 - - - 账户(Account) - - 在JD区块链中,账户(Account)被设计为包含“身份(Identity)”、“权限(Privilege)”、“状态(State)”、“控制规则(Control Rule)” 这4种属性的对象。 - - 其中,“身份(Identity)”、“权限(Privilege)”这两种属性是一个面向应用的系统的基本功能; - - “状态(State)”、“控制规则(Control Rule)” 这两种属性这是一个具备“图灵完备性”的系统的基本属性,使系统可以处理任意多样化的任务; - - 在这里,“身份(Identity)”是一个抽象表述,其形式上就是一个“区块链地址(Address)”和相应的“非对称秘钥钥对(KeyPair)”/证书。 简单来说,一个“账户(Account)”就是一个区块链地址、公私钥对以及一套的权限配置. - - 一个“账户(Account)”的“状态(State)”、“控制规则(Control Rule)” 这2种属性则是可选的,这提供了不同于其它区块链的账户/合约的一种新的使用方式,即一种数据和逻辑分离的应用设计思路(这在传统的web应用编程中被称为“贫血模型”)。同时,也意味着一个特定“账户”中的数据状态是可以跨账户/合约代码进行共享访问的。 - - 从应用的视角来看,对“账户”的使用方式可以有几种模式: - - 用于表示业务角色/用户: - - 用于表示业务数据:仅有“状态(State)”没有“控制规则(Control Rule)”的账户,可称为数据账户,就有些类似于关系数据库中的“表(Table)”的作用,不同业务类别的数据则使用不同的账户来管理。 - - 用于表示业务逻辑:仅有“控制规则(Control Rule)”没有“状态(State)”的账户,即所谓的合约账户,可表示某种用于处理数据的通用逻辑,可被授权访问特定的数据账户。 - - - 区块(Block) - 在概念上,与通常所说的区块的概念是一致的。 - 在实现上,一个区块的主要内容是包含了某一时刻提交的所有交易以及交易执行之后的状态数据的快照的hash,而不存储具体的交易操作和状态数据。 - - - 交易(Transaction) - 在概念上,与通常所说的Transaction的概念是一致的,表示一组需要原子执行的操作。 - - - 操作(Operation) - 操作是针对“账户(Account)”的“写”指令,包括以下几类: - - 注册账户 - - 更新账户的状态数据 - - 更新账户的合约代码定义 - - 调用账户的合约代码 - - - 合约代码(Contract Code) - 合约代码是一段用于对“账户(Account)”的状态数据执行操作的代码程序。 - -## 三、部署模型 - - 1. 总体部署 -![deployment architecture](docs/images/deployment.png) - - 2. 系统组件 - - 共识节点 - - 复制节点 - - SDK - - 网关 - - 终端 - - 3. 配置和管理 - -## 四、账本结构 - -### 1. 账户生成算法 - - - 公私钥对 - - 算法:默认ED25519 ,支持 SM2、CA; - - 公钥存储格式:版本 + 算法标识 + 公私钥原始内容 + 校验码 - - 字符编码方式:Base64 - - 地址 - - 算法 - - 给定公钥 P (或由私钥 R 算出公钥 P) - - 中间值 H1 = SHA256( P ) - - 中间值 H2 = RIPEMD-160( H1 ) - - 中间值 X = 版本 + 公钥算法标识 + H2 - - 校验和 C = 前4字节( SHA256( SHA256( X )) ) - - 地址 Address = Base58( X + C ) - -### 2. 区块 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        属性名称说明
        BlockHash当前区块 hash对区块中除此之外的其它所有属性一起进行哈希运算生成
        BlockVersion 区块版本表示区块-交易的属性结构的版本号;
        PreviousBlockHash上一区块 hash
        BlockNumber区块高度区块高度是一个区块在链中的序号;
        创始区块的高度为 0,每个新区块的高度依次递增;
        AccountHash账户树hash账户的 Merkle Tree 根的 hash
        AccountCount账户数量区块生成时账本中的全部账户的总数
        TxTreeHash交易树 hash本区块的交易集合的 Merkle Tree 根的 hash
        TxCount区块交易数量当前区块包含的交易的数量;
        TxTotalCount账本交易总数截止到当前区块为止当前账本的所有交易的总数量;
        CloseTime区块关闭时间生成当前区块时的区块链节点的网络时间;
        - -### 3. 交易 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        属性名称说明
        Hash当前交易 hash对交易中除此之外的其它所有属性一起进行哈希运算生成
        LedgerNumber区块高度交易被包含的区块高度
        BlobHash交易数据块hash交易的数据块是交易的原始数据,包含客户端提交的交易的全部操作及其参数; -
        交易的参与者需要使用私钥对交易数据块进行签名;
        Operations操作列表交易的操作列表;
        Sponsor交易发起人交易发起人的账户地址;
        SequenceNumber交易序号交易序号记录了一个特定的发起人的交易的顺序号,等同于该发起人历史上发起的交易的总数;
        Signatures签名列表由交易发起人和其它参与者对交易数据块的签名的列表;
        Result交易结果0 - 表示执行成功;非零表示执行失败;
        注:最终的账本只包含成功的交易;
        - -### 4. 操作 - - - - - - - - - - - - - - - - - - - - - -
        属性名称说明
        OpType操作类型 - 一级操作类型包括:注册账户、配置权限、写入键值数据、写入对象数据、定义合约代码、调用合约代码;
        - “键值数据写入”操作的子操作类型包括:填入键值、移除键、数值增加、数值减少;
        - “对象数据写入”操作的自操作类型包括:插入对象、更新对象、移除对象; -
        Args参数列表与操作类型相对应的参数列表;
        SubOps子操作列表“子操作”是“操作”的递归定义,由“操作类型”来标识;
        - -### 5. 账户 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        属性名称说明
        Address地址账户的唯一标识
        RegNumber注册号账户被注册到区块链的区块高度;
        TxSquenceNumber交易序列号由账户发起的交易的序列号,初始为 0,账户每发起一个交易则增加1;
        ModelVersion账户模型版本表示构成一个账户结构的属性模型的程序版本号;
        Version账户版本初始为 0,对账户的每一次变更(包括对权限设置、状态和合约代码的变更)都会使账户状态版本增加 1 ;
        注:交易序号的改变不会导致账户版本的增加;
        PrivilegeHash权限 hash权限树的根hash;
        PrivilegeVersion权限版本初始为 0, 每次对权限的变更都导致版本号加 1;
        StateType状态类型账户的状态类型有3种:空类型(NIL);键值类型;对象类型;
        StateVersion状态版本账户的状态类型有3种:空类型(NIL);键值类型;对象类型;
        StateHash状态哈希数据状态的 merkle tree 的根hash;
        CodeHash合约代码哈希由“账户地址+合约代码版本号+合约代码内容”生成的哈希;
        CodeVersion代码版本初始为 0,每次对代码的变更都使版本加 1 ;
        - -## 五、编程接口 - -### 1. 服务连接 - - -```java - //创建服务代理 - public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(); - final String GATEWAY_IP = "127.0.0.1"; - final int GATEWAY_PORT = 80; - final boolean SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IP, GATEWAY_PORT, SECURE, - CLIENT_CERT); - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); -``` +## 一、项目简介 +JD Chain 的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 -### 2. 用户注册 +## 二、部署模型 +JD Chain 主要部署组件包括以下几种: -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); - CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); - BlockchainKeyPair user = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); - - txTemp.users().register(user.getIdentity()); +- 共识节点 - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - prepTx.sign(keyPair); + 共识节点即参与共识的节点,这是系统的核心组件,承担了运行共识协议、管理账本数据、运行智能合约的职责。 - // 提交交易; - prepTx.commit(); -``` + 一个区块链网络由多个共识节点组成,共识节点的数量范围由选择的共识协议决定。 + 共识节点和账本是两个不同的概念,共识节点是个物理上的概念,账本是个逻辑上的概念。JD Chain 是一个多账本区块链系统,一个共识节点上可以装载运行多个账本。账本是数据维度的独立管理单元。共识节点和账本的关系,就像关系数据库系统中,数据库服务器和数据库实例的关系。 -### 3. 数据账户注册 + 共识节点通常都部署在参与方的内部网络中,通过由网络管理员指定的安全的网络出口与其它的共识节点建立通讯连接。 + 共识节点在形态上是服务器中的一个处理进程,背后需要连接一个本地或者内网的NoSQL数据库系统作为账本的存储。当前版本,共识节点目前是单进程的,未来版本将实现多进程以及多服务器集群模式。 -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); - CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); - BlockchainKeyPair dataAccount = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); - - txTemp.dataAccounts().register(dataAccount.getIdentity()); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - prepTx.sign(keyPair); - - // 提交交易; - prepTx.commit(); -``` +- 网关节点 -### 4. 写入数据 + 网关节点是负责终端接入的节点,负责终端连接、协议转换、交易准入、本地密码运算、密钥管理等职责。 -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - - HashDigest ledgerHash = getLedgerHash(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - - // -------------------------------------- - // 将商品信息写入到指定的账户中; - // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; - String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; - Commodity commodity1 = new Commodity(); - txTemp.dataAccount(commodityDataAccount).set("ASSET_CODE", commodity1.getCode().getBytes(), -1); - - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); - - String txHash = ByteArray.toBase64(prepTx.getHash().toBytes()); - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); - prepTx.sign(keyPair); - - // 提交交易; - prepTx.commit(); -``` + 网关节点是一种轻量节点,需要绑定一个特定参与方的密钥对,连接到一个或多个共识节点。 - -### 5. 查询数据 + 网关节点向共识节点的连接是需要通过认证的,绑定的参与方的密钥对必须事先已经注册到区块链账本中,且得到接入授权。 -> 注:详细的查询可参考模块sdk-samples中SDK_GateWay_Query_Test_相关测试用例 +- 终端 -```java - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); - - // 查询区块信息; - // 区块高度; - long ledgerNumber = service.getLedger(LEDGER_HASH).getLatestBlockHeight(); - // 最新区块; - LedgerBlock latestBlock = service.getBlock(LEDGER_HASH, ledgerNumber); - // 区块中的交易的数量; - long txCount = service.getTransactionCount(LEDGER_HASH, latestBlock.getHash()); - // 获取交易列表; - LedgerTransaction[] txList = service.getTransactions(LEDGER_HASH, ledgerNumber, 0, 100); - // 遍历交易列表 - for (LedgerTransaction ledgerTransaction : txList) { - TransactionContent txContent = ledgerTransaction.getTransactionContent(); - Operation[] operations = txContent.getOperations(); - if (operations != null && operations.length > 0) { - for (Operation operation : operations) { - operation = ClientOperationUtil.read(operation); - // 操作类型:数据账户注册操作 - if (operation instanceof DataAccountRegisterOperation) { - DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; - BlockchainIdentity blockchainIdentity = daro.getAccountID(); - } - // 操作类型:用户注册操作 - else if (operation instanceof UserRegisterOperation) { - UserRegisterOperation uro = (UserRegisterOperation) operation; - BlockchainIdentity blockchainIdentity = uro.getUserID(); - } - // 操作类型:账本注册操作 - else if (operation instanceof LedgerInitOperation) { - - LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; - LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); - - ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); - } - // 操作类型:合约发布操作 - else if (operation instanceof ContractCodeDeployOperation) { - ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; - BlockchainIdentity blockchainIdentity = ccdo.getContractID(); - } - // 操作类型:合约执行操作 - else if (operation instanceof ContractEventSendOperation) { - ContractEventSendOperation ceso = (ContractEventSendOperation) operation; - } - // 操作类型:KV存储操作 - else if (operation instanceof DataAccountKVSetOperation) { - DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = - ((DataAccountKVSetOperation) operation).getWriteSet(); - if (kvWriteEntries != null && kvWriteEntries.length > 0) { - for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { - BytesValue bytesValue = kvWriteEntry.getValue(); - DataType dataType = bytesValue.getType(); - Object showVal = ClientOperationUtil.readValueByBytesValue(bytesValue); - System.out.println("writeSet.key=" + kvWriteEntry.getKey()); - System.out.println("writeSet.value=" + showVal); - System.out.println("writeSet.type=" + dataType); - System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); - } - } - } - } - } - } - - // 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; - HashDigest txHash = txList[0].getTransactionContent().getHash(); - Transaction tx = service.getTransactionByContentHash(LEDGER_HASH, txHash); - // 获取数据; - String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; - String[] objKeys = new String[] { "x001", "x002" }; - KVDataEntry[] kvData = service.getDataEntries(LEDGER_HASH, commerceAccount, objKeys); - - long payloadVersion = kvData[0].getVersion(); - - // 获取数据账户下所有的KV列表 - KVDataEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, 0, 100); - if (kvData != null && kvData.length > 0) { - for (KVDataEntry kvDatum : kvData) { - System.out.println("kvData.key=" + kvDatum.getKey()); - System.out.println("kvData.version=" + kvDatum.getVersion()); - System.out.println("kvData.type=" + kvDatum.getType()); - System.out.println("kvData.value=" + kvDatum.getValue()); - } - } -``` + 终端泛指可以提交交易的客户端,典型来说,包括人、自动化设备、链外的信息系统等。 + 终端只能通过网关节点来提交交易。终端提交的交易需要用体现该终端身份的私钥来签署,产生一份电子签名。随后当交易提交给网关节点时,网关节点需要在把交易提交到共识节点之前,对交易请求以网关节点绑定的私钥追加一项“节点签名”。 -### 6. 合约发布 +- 备份节点 -```java - - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); + 仅对账本数据提供备份,但不参与交易共识的节点。(注:目前版本中尚未实现,将在后续版本中提供) - // 在本地定义TX模板 - TransactionTemplate txTemp = service.newTransaction(ledgerHash); - // 合约内容读取 - byte[] contractBytes = FileUtils.readBytes(new File(CONTRACT_FILE)); +![](docs/images/deployment.jpg) - // 生成用户 - BlockchainIdentityData blockchainIdentity = new BlockchainIdentityData(getSponsorKey().getPubKey()); - // 发布合约 - txTemp.contracts().deploy(blockchainIdentity, contractBytes); +## 三、代码下载与编译 - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); + 1. 安装 Maven 环境 + JD Chain 当前版本以 Java 语言开发,需要安装配置 JVM 和 Maven,JDK 版本不低于1.8 。没有特殊的安装要求,请另行搜索安装方法,此处不赘述。 + + 2. 安装 Git 工具 + + 为了能够执行 git clone 命令获取代码仓库。没有特殊的安装要求,请另行搜索安装方法,此处不赘述。 + + 3. 编译过程 - // 使用私钥进行签名; - CryptoKeyPair keyPair = getSponsorKey(); + JD Chain 项目包括 3 个代码仓库 - prepTx.sign(keyPair); + - jdchain + - 这是当前仓库,也是核心仓库,包含了共识节点、网关节点、SDK等一切部署组件。依赖于 explorer 和 bftsmart 这两个仓库先进行编译安装; + + - explorer + - 这是区块链浏览器的前端Web页面的工程,需要编译成静态资源包,由网关节点集成到一起部署。 + - 地址:git@github.com:blockchain-jd-com/explorer.git - // 提交交易; - TransactionResponse transactionResponse = prepTx.commit(); + - bftsmart + - 这是bftsmart共识协议的工程,需要 - assertTrue(transactionResponse.isSuccess()); - // 打印合约地址 - System.out.println(blockchainIdentity.getAddress().toBase58()); +4. 命令操作 -``` +- 编译安装 explorer 到本地 maven 仓库; +```sh + $ git clone git@github.com:blockchain-jd-com/explorer.git explorer -### 7. 合约执行 + $ cd explorer -```java + $ git checkout master - // 创建服务代理; - BlockchainService service = serviceFactory.getBlockchainService(); + $ mvn clean install + +``` +- 编译安装 bftsmart 到本地 maven 仓库; + - 需要手动先安装一个第三方包,位于仓库根目录下 lib/core-0.1.4.jar +```sh + $ git clone git@github.com:blockchain-jd-com/bftsmart.git bftsmart - // 在本地定义TX模板 - TransactionTemplate txTemp = service.newTransaction(ledgerHash); + $ cd bftsmart - // 合约地址 - String contractAddressBase58 = ""; - - // 使用接口方式调用合约 - TransferContract transferContract = txTpl.contract(contractAddress, TransferContract.class); - - // 使用decode方式调用合约内部方法(create方法) - // 返回GenericValueHolder可通过get方法获取结果,但get方法需要在commit调用后执行 - GenericValueHolder result = ContractReturnValue.decode(transferContract.create(address, account, money)); - - PreparedTransaction ptx = txTpl.prepare(); + $ git checkout master - ptx.sign(adminKey); - - TransactionResponse transactionResponse = ptx.commit(); - - String cotractExecResult = result.get(); - - // TransactionResponse也提供了可供查询结果的接口 - OperationResult[] operationResults = transactionResponse.getOperationResults(); - - // 通过OperationResult获取结果 - for (int i = 0; i < operationResults.length; i++) { - OperationResult opResult = operationResults[i]; - System.out.printf("Operation[%s].result = %s \r\n", - opResult.getIndex(), BytesValueEncoding.decode(opResult.getResult())); - } + $ mvn install:install-file -Dfile=lib/core-0.1.4.jar -DgroupId=com.yahoo.ycsb -DartifactId=core -Dversion=0.1.4 -Dpackaging=jar + + $ mvn clean install +``` +- 编译 jdchain 工程; + - 当编译完成后,共识节点的安装包位于 "仓库根目录"/source/deployment/deployment-peer/target/jdchain-peer-1.0.1.RELEASE.zip + - 当编译完成后,网关节点的安装包位于 "仓库根目录"/source/deployment/deployment-gateway/target/jdchain-gateway-1.0.1.RELEASE.zip -``` \ No newline at end of file +```sh + $ git clone git@github.com:blockchain-jd-com/jdchain.git jdchain + + $ cd jdchain/source + + $ git checkout master + + $ mvn clean package + +``` diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 00000000..7cea13d1 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,630 @@ +[TOC] +#JD区块链 + + +[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) + + +------------------------------------------------------------------------ +### 版本修订历史 + + + + + + + + + + + + + + + + + + + + + + + + + +
        版本号作 者修改日期备 注
        0.0.6黄海泉2017-11-10 + 定义JD区块链项目的目标与关键能力;
        + 定义JD区块链的核心对象模型;
        + 定义“账户”的生成算法和“区块/交易/操作/账户”的关键属性;
        + 描述了编程接口的示例代码; +
        0.0.7黄海泉2017-11-17 + 丰富了对“节点共识”、“节点分区”两项关键能力的详细描述; +
        0.0.8黄海泉2018-07-17 + 增加部署图;增加智能合约开发的示例; +
        + +------------------------------------------------------------------------ + +## 一、概述 +JD Chain 的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 + +区块链本质上是一种新的分布式架构,以密码学和分布式技术为核心,旨在实现无需借助“第三方” 就能在多个业务方之间进行安全、可信、直接的信息和价值交换。从点对点的信息和价值交换的角度看,区块链发挥了“协议”的作用。 + + +以下是 JD 区块链用以满足企业核心需求的关键能力,也是显著区别于其它区块链的重要特征: + + - 快速共识 + - 节点分区 + - 并行多账本 + - 大数据伸缩存储 + - 条件检索 + - 面向对象的合约代码编程模型 + - 节点快速部署 + - 多终端灵活接入 + - 分布式自治的账户权限管理模型 + +JD区块链对于关键能力的定义是建立在深入理解和抽象各种多样化需求的基础上的。 + + - 快速共识(Efficient Consensus) + 我们认为,“快速”不仅仅体现在“用更短时间”达成共识,还要体现在交易(Transaction)要得到可靠地执行。 + 需要在算法和实现层面做出保障,确保所有提交的合法的交易会被系统在一个“确定性”和“足够短”的时间之内被严格地执行,系统不主动作出随机性地丢弃(不包括系统故障因素)。注:POW类算法产生的链分叉处理是一种系统随机性地丢弃交易的行为。 + 从使用者的视角来看,这种能力就是区块链系统的“可靠性”,这对于企业、金融场景而言尤其重要。 + + - 节点分区(Peer Partition) + “分区”是一种分布式系统架构原则,通过将大范围目标按照某种相似特征分隔为一个个小范围目标,分别进行更高效地处理,这能从整体上提升整个系统的处理能力。 + 区块链系统也是一种分布式系统,沿用“分区”的思想这点来自以往的系统架构实践的经验,是有可能让区块链系统获得可媲美现有系统的处理能力。这种能力将可以无障碍地把区块链在应用于广泛的企业场景中。 + 在此,我们所说的“节点分区(Peer Partition)” 是共识过程中的物理通讯层面的分区,在共识过程中只有相关的物理节点才会建立通讯链路并复制和存储共识的状态数据。在一个区块链系统中,可以从节点全集中选择一个或多个节点子集,分别组成一个或多个节点分区(Peer Partition) + + - 并行多账本 + 账本(Ledger) + - 大数据伸缩存储 + - 条件检索 + - 面向对象的合约代码编程模型 + - 节点快速部署 + - 多终端灵活接入 + - 分布式自治的账户权限管理模型 + +## 二、对象模型 +JD区块链的核心对象包括: + + - 账本(Ledger) + 一份账本(Ledger)实质上是由两部分组成: + - 一组以“账户(Account)”表示的状态数据; + - 以“区块的链条(Block-chain)”表示的状态数据的变更历史; + + JD区块链的“账本(Ledger)”是一个最顶层的管理数据的逻辑单元。在一个区块链节点构成的共识网络中,可以维护多套并行的“账本(Ledger)”。 + + - 账户(Account) + - 在JD区块链中,账户(Account)被设计为包含“身份(Identity)”、“权限(Privilege)”、“状态(State)”、“控制规则(Control Rule)” 这4种属性的对象。 + - 其中,“身份(Identity)”、“权限(Privilege)”这两种属性是一个面向应用的系统的基本功能; + - “状态(State)”、“控制规则(Control Rule)” 这两种属性这是一个具备“图灵完备性”的系统的基本属性,使系统可以处理任意多样化的任务; + - 在这里,“身份(Identity)”是一个抽象表述,其形式上就是一个“区块链地址(Address)”和相应的“非对称秘钥钥对(KeyPair)”/证书。 简单来说,一个“账户(Account)”就是一个区块链地址、公私钥对以及一套的权限配置. + - 一个“账户(Account)”的“状态(State)”、“控制规则(Control Rule)” 这2种属性则是可选的,这提供了不同于其它区块链的账户/合约的一种新的使用方式,即一种数据和逻辑分离的应用设计思路(这在传统的web应用编程中被称为“贫血模型”)。同时,也意味着一个特定“账户”中的数据状态是可以跨账户/合约代码进行共享访问的。 + - 从应用的视角来看,对“账户”的使用方式可以有几种模式: + - 用于表示业务角色/用户: + - 用于表示业务数据:仅有“状态(State)”没有“控制规则(Control Rule)”的账户,可称为数据账户,就有些类似于关系数据库中的“表(Table)”的作用,不同业务类别的数据则使用不同的账户来管理。 + - 用于表示业务逻辑:仅有“控制规则(Control Rule)”没有“状态(State)”的账户,即所谓的合约账户,可表示某种用于处理数据的通用逻辑,可被授权访问特定的数据账户。 + + - 区块(Block) + 在概念上,与通常所说的区块的概念是一致的。 + 在实现上,一个区块的主要内容是包含了某一时刻提交的所有交易以及交易执行之后的状态数据的快照的hash,而不存储具体的交易操作和状态数据。 + + - 交易(Transaction) + 在概念上,与通常所说的Transaction的概念是一致的,表示一组需要原子执行的操作。 + + - 操作(Operation) + 操作是针对“账户(Account)”的“写”指令,包括以下几类: + - 注册账户 + - 更新账户的状态数据 + - 更新账户的合约代码定义 + - 调用账户的合约代码 + + - 合约代码(Contract Code) + 合约代码是一段用于对“账户(Account)”的状态数据执行操作的代码程序。 + +## 三、部署模型 + + 1. 总体部署 +![deployment architecture](docs/images/deployment.png) + + 2. 系统组件 + - 共识节点 + - 复制节点 + - SDK + - 网关 + - 终端 + + 3. 配置和管理 + +## 四、账本结构 + +### 1. 账户生成算法 + + - 公私钥对 + - 算法:默认ED25519 ,支持 SM2、CA; + - 公钥存储格式:版本 + 算法标识 + 公私钥原始内容 + 校验码 + - 字符编码方式:Base64 + - 地址 + - 算法 + - 给定公钥 P (或由私钥 R 算出公钥 P) + - 中间值 H1 = SHA256( P ) + - 中间值 H2 = RIPEMD-160( H1 ) + - 中间值 X = 版本 + 公钥算法标识 + H2 + - 校验和 C = 前4字节( SHA256( SHA256( X )) ) + - 地址 Address = Base58( X + C ) + +### 2. 区块 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        属性名称说明
        BlockHash当前区块 hash对区块中除此之外的其它所有属性一起进行哈希运算生成
        BlockVersion 区块版本表示区块-交易的属性结构的版本号;
        PreviousBlockHash上一区块 hash
        BlockNumber区块高度区块高度是一个区块在链中的序号;
        创始区块的高度为 0,每个新区块的高度依次递增;
        AccountHash账户树hash账户的 Merkle Tree 根的 hash
        AccountCount账户数量区块生成时账本中的全部账户的总数
        TxTreeHash交易树 hash本区块的交易集合的 Merkle Tree 根的 hash
        TxCount区块交易数量当前区块包含的交易的数量;
        TxTotalCount账本交易总数截止到当前区块为止当前账本的所有交易的总数量;
        CloseTime区块关闭时间生成当前区块时的区块链节点的网络时间;
        + +### 3. 交易 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        属性名称说明
        Hash当前交易 hash对交易中除此之外的其它所有属性一起进行哈希运算生成
        LedgerNumber区块高度交易被包含的区块高度
        BlobHash交易数据块hash交易的数据块是交易的原始数据,包含客户端提交的交易的全部操作及其参数; +
        交易的参与者需要使用私钥对交易数据块进行签名;
        Operations操作列表交易的操作列表;
        Sponsor交易发起人交易发起人的账户地址;
        SequenceNumber交易序号交易序号记录了一个特定的发起人的交易的顺序号,等同于该发起人历史上发起的交易的总数;
        Signatures签名列表由交易发起人和其它参与者对交易数据块的签名的列表;
        Result交易结果0 - 表示执行成功;非零表示执行失败;
        注:最终的账本只包含成功的交易;
        + +### 4. 操作 + + + + + + + + + + + + + + + + + + + + + +
        属性名称说明
        OpType操作类型 + 一级操作类型包括:注册账户、配置权限、写入键值数据、写入对象数据、定义合约代码、调用合约代码;
        + “键值数据写入”操作的子操作类型包括:填入键值、移除键、数值增加、数值减少;
        + “对象数据写入”操作的自操作类型包括:插入对象、更新对象、移除对象; +
        Args参数列表与操作类型相对应的参数列表;
        SubOps子操作列表“子操作”是“操作”的递归定义,由“操作类型”来标识;
        + +### 5. 账户 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        属性名称说明
        Address地址账户的唯一标识
        RegNumber注册号账户被注册到区块链的区块高度;
        TxSquenceNumber交易序列号由账户发起的交易的序列号,初始为 0,账户每发起一个交易则增加1;
        ModelVersion账户模型版本表示构成一个账户结构的属性模型的程序版本号;
        Version账户版本初始为 0,对账户的每一次变更(包括对权限设置、状态和合约代码的变更)都会使账户状态版本增加 1 ;
        注:交易序号的改变不会导致账户版本的增加;
        PrivilegeHash权限 hash权限树的根hash;
        PrivilegeVersion权限版本初始为 0, 每次对权限的变更都导致版本号加 1;
        StateType状态类型账户的状态类型有3种:空类型(NIL);键值类型;对象类型;
        StateVersion状态版本账户的状态类型有3种:空类型(NIL);键值类型;对象类型;
        StateHash状态哈希数据状态的 merkle tree 的根hash;
        CodeHash合约代码哈希由“账户地址+合约代码版本号+合约代码内容”生成的哈希;
        CodeVersion代码版本初始为 0,每次对代码的变更都使版本加 1 ;
        + +## 五、编程接口 + +### 1. 服务连接 + + +```java + //创建服务代理 + public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(); + final String GATEWAY_IP = "127.0.0.1"; + final int GATEWAY_PORT = 80; + final boolean SECURE = false; + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IP, GATEWAY_PORT, SECURE, + CLIENT_CERT); + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); +``` + + +### 2. 用户注册 + + +```java + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHash); + SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); + CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); + BlockchainKeyPair user = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); + + txTemp.users().register(user.getIdentity()); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + // 使用私钥进行签名; + CryptoKeyPair keyPair = getSponsorKey(); + prepTx.sign(keyPair); + + // 提交交易; + prepTx.commit(); +``` + + +### 3. 数据账户注册 + + +```java + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHash); + SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); + CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); + BlockchainKeyPair dataAccount = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); + + txTemp.dataAccounts().register(dataAccount.getIdentity()); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + // 使用私钥进行签名; + CryptoKeyPair keyPair = getSponsorKey(); + prepTx.sign(keyPair); + + // 提交交易; + prepTx.commit(); +``` + +### 4. 写入数据 + +```java + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); + + HashDigest ledgerHash = getLedgerHash(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHash); + + // -------------------------------------- + // 将商品信息写入到指定的账户中; + // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; + String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; + Commodity commodity1 = new Commodity(); + txTemp.dataAccount(commodityDataAccount).set("ASSET_CODE", commodity1.getCode().getBytes(), -1); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + + String txHash = ByteArray.toBase64(prepTx.getHash().toBytes()); + // 使用私钥进行签名; + CryptoKeyPair keyPair = getSponsorKey(); + prepTx.sign(keyPair); + + // 提交交易; + prepTx.commit(); +``` + + +### 5. 查询数据 + +> 注:详细的查询可参考模块sdk-samples中SDK_GateWay_Query_Test_相关测试用例 + +```java + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); + + // 查询区块信息; + // 区块高度; + long ledgerNumber = service.getLedger(LEDGER_HASH).getLatestBlockHeight(); + // 最新区块; + LedgerBlock latestBlock = service.getBlock(LEDGER_HASH, ledgerNumber); + // 区块中的交易的数量; + long txCount = service.getTransactionCount(LEDGER_HASH, latestBlock.getHash()); + // 获取交易列表; + LedgerTransaction[] txList = service.getTransactions(LEDGER_HASH, ledgerNumber, 0, 100); + // 遍历交易列表 + for (LedgerTransaction ledgerTransaction : txList) { + TransactionContent txContent = ledgerTransaction.getTransactionContent(); + Operation[] operations = txContent.getOperations(); + if (operations != null && operations.length > 0) { + for (Operation operation : operations) { + operation = ClientOperationUtil.read(operation); + // 操作类型:数据账户注册操作 + if (operation instanceof DataAccountRegisterOperation) { + DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; + BlockchainIdentity blockchainIdentity = daro.getAccountID(); + } + // 操作类型:用户注册操作 + else if (operation instanceof UserRegisterOperation) { + UserRegisterOperation uro = (UserRegisterOperation) operation; + BlockchainIdentity blockchainIdentity = uro.getUserID(); + } + // 操作类型:账本注册操作 + else if (operation instanceof LedgerInitOperation) { + + LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; + LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); + + ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); + } + // 操作类型:合约发布操作 + else if (operation instanceof ContractCodeDeployOperation) { + ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; + BlockchainIdentity blockchainIdentity = ccdo.getContractID(); + } + // 操作类型:合约执行操作 + else if (operation instanceof ContractEventSendOperation) { + ContractEventSendOperation ceso = (ContractEventSendOperation) operation; + } + // 操作类型:KV存储操作 + else if (operation instanceof DataAccountKVSetOperation) { + DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = + ((DataAccountKVSetOperation) operation).getWriteSet(); + if (kvWriteEntries != null && kvWriteEntries.length > 0) { + for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { + BytesValue bytesValue = kvWriteEntry.getValue(); + DataType dataType = bytesValue.getType(); + Object showVal = ClientOperationUtil.readValueByBytesValue(bytesValue); + System.out.println("writeSet.key=" + kvWriteEntry.getKey()); + System.out.println("writeSet.value=" + showVal); + System.out.println("writeSet.type=" + dataType); + System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); + } + } + } + } + } + } + + // 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; + HashDigest txHash = txList[0].getTransactionContent().getHash(); + Transaction tx = service.getTransactionByContentHash(LEDGER_HASH, txHash); + // 获取数据; + String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; + String[] objKeys = new String[] { "x001", "x002" }; + KVDataEntry[] kvData = service.getDataEntries(LEDGER_HASH, commerceAccount, objKeys); + + long payloadVersion = kvData[0].getVersion(); + + // 获取数据账户下所有的KV列表 + KVDataEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, 0, 100); + if (kvData != null && kvData.length > 0) { + for (KVDataEntry kvDatum : kvData) { + System.out.println("kvData.key=" + kvDatum.getKey()); + System.out.println("kvData.version=" + kvDatum.getVersion()); + System.out.println("kvData.type=" + kvDatum.getType()); + System.out.println("kvData.value=" + kvDatum.getValue()); + } + } +``` + + +### 6. 合约发布 + + +```java + + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); + + // 在本地定义TX模板 + TransactionTemplate txTemp = service.newTransaction(ledgerHash); + + // 合约内容读取 + byte[] contractBytes = FileUtils.readBytes(new File(CONTRACT_FILE)); + + // 生成用户 + BlockchainIdentityData blockchainIdentity = new BlockchainIdentityData(getSponsorKey().getPubKey()); + + // 发布合约 + txTemp.contracts().deploy(blockchainIdentity, contractBytes); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + + // 使用私钥进行签名; + CryptoKeyPair keyPair = getSponsorKey(); + + prepTx.sign(keyPair); + + // 提交交易; + TransactionResponse transactionResponse = prepTx.commit(); + + assertTrue(transactionResponse.isSuccess()); + + // 打印合约地址 + System.out.println(blockchainIdentity.getAddress().toBase58()); + +``` + +### 7. 合约执行 + +```java + + // 创建服务代理; + BlockchainService service = serviceFactory.getBlockchainService(); + + // 在本地定义TX模板 + TransactionTemplate txTemp = service.newTransaction(ledgerHash); + + // 合约地址 + String contractAddressBase58 = ""; + + // 使用接口方式调用合约 + TransferContract transferContract = txTpl.contract(contractAddress, TransferContract.class); + + // 使用decode方式调用合约内部方法(create方法) + // 返回GenericValueHolder可通过get方法获取结果,但get方法需要在commit调用后执行 + GenericValueHolder result = ContractReturnValue.decode(transferContract.create(address, account, money)); + + PreparedTransaction ptx = txTpl.prepare(); + + ptx.sign(adminKey); + + TransactionResponse transactionResponse = ptx.commit(); + + String cotractExecResult = result.get(); + + // TransactionResponse也提供了可供查询结果的接口 + OperationResult[] operationResults = transactionResponse.getOperationResults(); + + // 通过OperationResult获取结果 + for (int i = 0; i < operationResults.length; i++) { + OperationResult opResult = operationResults[i]; + System.out.printf("Operation[%s].result = %s \r\n", + opResult.getIndex(), BytesValueEncoding.decode(opResult.getResult())); + } + + +``` \ No newline at end of file diff --git a/docs/images/deployment.jpg b/docs/images/deployment.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aec66f03d653931de983a14f49dc902983121449 GIT binary patch literal 131011 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 z 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 z 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 JkM`%ce*p^~en$WR literal 0 HcmV?d00001 From 2d9af2231e51a59f5e5f994ed8cfe4401b7f34ca Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 27 Aug 2019 03:22:34 +0800 Subject: [PATCH 054/124] Updated readme; --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f272d14..fc8e42b1 100644 --- a/README.md +++ b/README.md @@ -55,15 +55,16 @@ JD Chain 主要部署组件包括以下几种: ## 三、编译源代码 1. 安装 Maven 环境 + JD Chain 当前版本以 Java 语言开发,需要安装配置 JVM 和 Maven,JDK 版本不低于1.8 。没有特殊的安装要求,请另行搜索安装方法,此处不赘述。 2. 安装 Git 工具 为了能够执行 git clone 命令获取代码仓库。没有特殊的安装要求,请另行搜索安装方法,此处不赘述。 - 3. 编译过程 + 3. 工程代码 - JD Chain 项目包括 3 个代码仓库 + JD Chain 源代码包括 3 个代码仓库 - jdchain - 这是当前仓库,也是核心仓库,包含了共识节点、网关节点、SDK等一切部署组件。依赖于 explorer 和 bftsmart 这两个仓库先进行编译安装; @@ -73,7 +74,7 @@ JD Chain 主要部署组件包括以下几种: - 地址:git@github.com:blockchain-jd-com/explorer.git - bftsmart - - 这是bftsmart共识协议的工程,需要 + - 这是bftsmart共识协议的工程,需要先编译安装到本地 maven 仓库; 4. 命令操作 From 8142c6aaac6efc09d61199ee3cb460b37bfd2867 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 27 Aug 2019 03:30:07 +0800 Subject: [PATCH 055/124] Update readme; --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fc8e42b1..675472d0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ------------------------------------------------------------------------ -## 一、项目简介 +## 一、项目介绍 JD Chain 的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 @@ -56,11 +56,11 @@ JD Chain 主要部署组件包括以下几种: 1. 安装 Maven 环境 - JD Chain 当前版本以 Java 语言开发,需要安装配置 JVM 和 Maven,JDK 版本不低于1.8 。没有特殊的安装要求,请另行搜索安装方法,此处不赘述。 + JD Chain 当前版本以 Java 语言开发,需要安装配置 JVM 和 Maven,JDK 版本不低于1.8 。(没有特殊要求,请按标准方法安装,此处不赘述) 2. 安装 Git 工具 - 为了能够执行 git clone 命令获取代码仓库。没有特殊的安装要求,请另行搜索安装方法,此处不赘述。 + 为了能够执行 git clone 命令获取代码仓库。 (没有特殊要求,请按标准方法安装,此处不赘述) 3. 工程代码 From d425e2cb26ad266c509725ebcd8eeffc2c198b6a Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 27 Aug 2019 11:54:44 +0800 Subject: [PATCH 056/124] Fixed compiling error; --- .../blockchain/ledger/BytesEncodingTest.java | 1 + source/pom.xml | 1 - .../intgr/perf/LedgerPerformanceTest.java | 106 ++++++++++++++++-- .../ledger/LedgerBlockGeneratingTest.java | 9 +- source/tools/tools-mocker/pom.xml | 6 +- .../blockchain/mocker/MockerNodeContext.java | 20 +++- .../handler/MockerContractExeHandle.java | 6 +- 7 files changed, 128 insertions(+), 21 deletions(-) diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java index b2211f08..32d48aba 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/BytesEncodingTest.java @@ -6,6 +6,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import org.junit.Test; +import org.mockito.Mockito; import com.jd.blockchain.utils.io.BytesEncoding; import com.jd.blockchain.utils.io.NumberMask; diff --git a/source/pom.xml b/source/pom.xml index eaaad8c5..13ce9e10 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -130,7 +130,6 @@ org.mockito mockito-core ${mockito.version} - test diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index c227045f..07c05e08 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -7,19 +7,21 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.DoubleStream; -import com.jd.blockchain.crypto.*; -import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; - import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.AsymmetricKeypair; +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; @@ -28,14 +30,21 @@ import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSecurityException; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerSecurityManager; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.DbConnectionFactory; @@ -51,6 +60,7 @@ import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.ArgumentSet; +import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.ConsoleUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; import com.jd.blockchain.utils.io.FileUtils; @@ -90,6 +100,8 @@ public class LedgerPerformanceTest { DataContractRegistry.register(DataAccountKVSetOperation.class); } + public static final LedgerSecurityManager DEFAULT_SECURITY_MANAGER = new FreedomLedgerSecurityManager(); + public static void test(String[] args) { NodeContext[] nodes = null; try { @@ -281,8 +293,8 @@ public class LedgerPerformanceTest { LedgerBlock latestBlock = ledger.getLatestBlock(); LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); LedgerEditor newEditor = ledger.createNextBlock(); - TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, previousDataSet, opHandler, - ledgerManager); + TransactionBatchProcessor txProc = new TransactionBatchProcessor(DEFAULT_SECURITY_MANAGER, newEditor, + previousDataSet, opHandler, ledgerManager); // 准备请求 int totalCount = batchSize * batchCount; @@ -319,8 +331,8 @@ public class LedgerPerformanceTest { long startTs = System.currentTimeMillis(); LedgerEditor newEditor = ledger.createNextBlock(); - TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, previousDataSet, opHandler, - ledgerManager); + TransactionBatchProcessor txProc = new TransactionBatchProcessor(DEFAULT_SECURITY_MANAGER, newEditor, + previousDataSet, opHandler, ledgerManager); testTxExec(txList, i * batchSize, batchSize, txProc); @@ -496,9 +508,8 @@ public class LedgerPerformanceTest { LedgerInitProperties initSetting = loadInitSetting(); Properties props = loadConsensusSetting(config); ConsensusProvider csProvider = getConsensusProvider(provider); - ConsensusSettings csProps = csProvider.getSettingsFactory() - .getConsensusSettingsBuilder() - .createSettings(props, Utils.loadParticipantNodes()); + ConsensusSettings csProps = csProvider.getSettingsFactory().getConsensusSettingsBuilder().createSettings(props, + Utils.loadParticipantNodes()); DBSetting dbsetting0; DBSetting dbsetting1; @@ -632,4 +643,77 @@ public class LedgerPerformanceTest { } } + + private static class FreedomLedgerSecurityManager implements LedgerSecurityManager { + + public static final FreedomLedgerSecurityManager INSTANCE = new FreedomLedgerSecurityManager(); + + @Override + public SecurityPolicy createSecurityPolicy(Set endpoints, Set nodes) { + return new FreedomSecurityPolicy(endpoints, nodes); + } + + } + + private static class FreedomSecurityPolicy implements SecurityPolicy { + + private Set endpoints; + private Set nodes; + + public FreedomSecurityPolicy(Set endpoints, Set nodes) { + this.endpoints = endpoints; + this.nodes = nodes; + } + + @Override + public Set getEndpoints() { + return endpoints; + } + + @Override + public Set getNodes() { + return nodes; + } + + @Override + public boolean isEnableToEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) { + return true; + } + + @Override + public boolean isEnableToEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) { + return true; + } + + @Override + public boolean isEnableToNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) { + return true; + } + + @Override + public boolean isEnableToNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void checkEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) + throws LedgerSecurityException { + } + + @Override + public void checkEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) + throws LedgerSecurityException { + } + + @Override + public void checkNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException { + } + + @Override + public void checkNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) + throws LedgerSecurityException { + } + + } } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index 233af7d1..c3d268fd 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -45,6 +45,7 @@ import com.jd.blockchain.utils.net.NetworkAddress; import test.com.jd.blockchain.intgr.PresetAnswerPrompter; import test.com.jd.blockchain.intgr.initializer.LedgerInitializeTest; import test.com.jd.blockchain.intgr.initializer.LedgerInitializeTest.NodeContext; +import test.com.jd.blockchain.intgr.perf.LedgerPerformanceTest; import test.com.jd.blockchain.intgr.perf.Utils; public class LedgerBlockGeneratingTest { @@ -81,7 +82,8 @@ public class LedgerBlockGeneratingTest { long startTs = System.currentTimeMillis(); LedgerEditor newEditor = ledger.createNextBlock(); - TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, previousDataSet, opHandler, + TransactionBatchProcessor txProc = new TransactionBatchProcessor( + LedgerPerformanceTest.DEFAULT_SECURITY_MANAGER, newEditor, previousDataSet, opHandler, ledgerManager); testTxExec(txList, i * batchSize, batchSize, txProc); @@ -129,9 +131,8 @@ public class LedgerBlockGeneratingTest { LedgerInitProperties initSetting = loadInitSetting(); Properties props = loadConsensusSetting(); ConsensusProvider csProvider = getConsensusProvider(); - ConsensusSettings csProps = csProvider.getSettingsFactory() - .getConsensusSettingsBuilder() - .createSettings(props, Utils.loadParticipantNodes()); + ConsensusSettings csProps = csProvider.getSettingsFactory().getConsensusSettingsBuilder().createSettings(props, + Utils.loadParticipantNodes()); NodeContext node0 = new NodeContext(initSetting.getConsensusParticipant(0).getInitializerAddress(), serviceRegisterMap); diff --git a/source/tools/tools-mocker/pom.xml b/source/tools/tools-mocker/pom.xml index 3b79b5b9..c78dcd31 100644 --- a/source/tools/tools-mocker/pom.xml +++ b/source/tools/tools-mocker/pom.xml @@ -39,7 +39,11 @@ crypto-classic ${project.version} - + + org.mockito + mockito-core + provided + diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index f48e59c0..e29cd904 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -1,6 +1,8 @@ package com.jd.blockchain.mocker; import static java.lang.reflect.Proxy.newProxyInstance; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; import java.text.SimpleDateFormat; import java.util.Date; @@ -8,6 +10,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import org.mockito.Mockito; + import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.consensus.ClientIdentification; import com.jd.blockchain.consensus.ClientIdentifications; @@ -443,11 +447,25 @@ public class MockerNodeContext implements BlockchainQueryService { return reqBuilder.buildRequest(); } + private static LedgerSecurityManager getSecurityManager() { + LedgerSecurityManager securityManager = Mockito.mock(LedgerSecurityManager.class); + + SecurityPolicy securityPolicy = Mockito.mock(SecurityPolicy.class); + when(securityPolicy.isEnableToEndpoints(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToEndpoints(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); + + when(securityManager.createSecurityPolicy(any(), any())).thenReturn(securityPolicy); + + return securityManager; + } + public OperationResult[] txProcess(TransactionRequest txRequest) { LedgerEditor newEditor = ledgerRepository.createNextBlock(); LedgerBlock latestBlock = ledgerRepository.getLatestBlock(); LedgerDataset previousDataSet = ledgerRepository.getDataSet(latestBlock); - TransactionBatchProcessor txProc = new TransactionBatchProcessor(newEditor, + TransactionBatchProcessor txProc = new TransactionBatchProcessor(getSecurityManager(), newEditor, previousDataSet, opHandler, ledgerManager); TransactionResponse txResp = txProc.schedule(txRequest); TransactionBatchResultHandle handle = txProc.prepare(); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index e4db1c39..7683c3db 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -29,11 +29,11 @@ public class MockerContractExeHandle implements OperationHandle { private HashDigest ledgerHash; @Override - public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestExtension requestContext, + public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestExtension request, LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { ContractEventSendOperation contractOP = (ContractEventSendOperation) op; - HashDigest txHash = requestContext.getRequest().getTransactionContent().getHash(); + HashDigest txHash = request.getTransactionContent().getHash(); ExecutorProxy executorProxy = executorProxyMap.get(txHash); @@ -43,7 +43,7 @@ public class MockerContractExeHandle implements OperationHandle { ContractLedgerContext ledgerContext = new ContractLedgerContext(queryService, opHandleContext); MockerContractEventContext contractEventContext = new MockerContractEventContext(ledgerHash, - contractOP.getEvent(), requestContext.getRequest(), ledgerContext); + contractOP.getEvent(), request, ledgerContext); Object instance = executorProxy.getInstance(); EventProcessingAware awire = null; From 712e27366262552221829d12ce50a90c114ae09f Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 27 Aug 2019 15:20:20 +0800 Subject: [PATCH 057/124] bug modify for regist participant --- .../BftsmartConsensusSettingsBuilder.java | 14 +++++++------- .../mq/MsgQueueConsensusSettingsBuilder.java | 6 +++--- .../ParticipantRegisterOperationHandle.java | 2 -- .../intgr/IntegrationTest4Bftsmart.java | 19 +++++++++++++++++++ .../blockchain/intgr/IntegrationTest4MQ.java | 18 ++++++++++++++++++ .../initializer/LedgerInitProperties.java | 2 +- 6 files changed, 48 insertions(+), 13 deletions(-) diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java index af57f726..9d6637db 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/BftsmartConsensusSettingsBuilder.java @@ -185,13 +185,13 @@ public class BftsmartConsensusSettingsBuilder implements ConsensusSettingsBuilde BftsmartConsensusConfig bftsmartConsensusConfig = new BftsmartConsensusConfig(nodeSettings, systemConfigs); - for(int i = 0 ;i < bftsmartConsensusConfig.getNodes().length; i++) { - System.out.printf("id = %d, host = %s, port = %d\r\n", bftsmartConsensusConfig.getNodes()[i].getId(), bftsmartConsensusConfig.getNodes()[i].getNetworkAddress().getHost(), bftsmartConsensusConfig.getNodes()[i].getNetworkAddress().getPort()); - } - - for(int i = 0 ;i < bftsmartConsensusConfig.getSystemConfigs().length; i++) { - System.out.printf("property name = %s, property value = %s\r\n",bftsmartConsensusConfig.getSystemConfigs()[i].getName(), bftsmartConsensusConfig.getSystemConfigs()[i].getValue()); - } +// for(int i = 0 ;i < bftsmartConsensusConfig.getNodes().length; i++) { +// System.out.printf("id = %d, host = %s, port = %d\r\n", bftsmartConsensusConfig.getNodes()[i].getId(), bftsmartConsensusConfig.getNodes()[i].getNetworkAddress().getHost(), bftsmartConsensusConfig.getNodes()[i].getNetworkAddress().getPort()); +// } +// +// for(int i = 0 ;i < bftsmartConsensusConfig.getSystemConfigs().length; i++) { +// System.out.printf("property name = %s, property value = %s\r\n",bftsmartConsensusConfig.getSystemConfigs()[i].getName(), bftsmartConsensusConfig.getSystemConfigs()[i].getValue()); +// } return new Bytes(ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().encode(bftsmartConsensusConfig)); diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java index 9eba0389..5790bae3 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java @@ -184,9 +184,9 @@ public class MsgQueueConsensusSettingsBuilder implements ConsensusSettingsBuilde msgQueueConsensusConfig.setNetworkSettings(consensusSettings.getNetworkSettings()); - for(int i = 0 ;i < msgQueueConsensusConfig.getNodes().length; i++) { - System.out.printf("node addr = %s\r\n", msgQueueConsensusConfig.getNodes()[i].getAddress()); - } +// for(int i = 0 ;i < msgQueueConsensusConfig.getNodes().length; i++) { +// System.out.printf("node addr = %s\r\n", msgQueueConsensusConfig.getNodes()[i].getAddress()); +// } return new Bytes(consensusEncoder.encode(msgQueueConsensusConfig)); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java index e06d813c..255a8eb9 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java @@ -16,8 +16,6 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { ParticipantRegisterOperation participantRegOp = (ParticipantRegisterOperation) op; - System.out.println("ParticipantRegisterOperationHandle start\r\n"); - LedgerAdminAccount adminAccount = dataset.getAdminAccount(); ParticipantInfo participantInfo = participantRegOp.getParticipantInfo(); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index 9b7ae2ba..fd93a2c0 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -30,6 +30,8 @@ public class IntegrationTest4Bftsmart { private static final boolean isRegisterDataAccount = true; + private static final boolean isRegisterParticipant = true; + private static final boolean isWriteKv = true; private static final String DB_TYPE_MEM = "mem"; @@ -140,6 +142,23 @@ public class IntegrationTest4Bftsmart { } } + long participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + + long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + + System.out.printf("before add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + + if (isRegisterParticipant) { + IntegrationBase.KeyPairResponse participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); + } + + participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + + userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + + System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + + try { System.out.println("----------------- Init Completed -----------------"); Thread.sleep(Integer.MAX_VALUE); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index 6e8c7f1a..f7315d17 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -30,6 +30,8 @@ public class IntegrationTest4MQ { private static final boolean isRegisterDataAccount = true; + private static final boolean isRegisterParticipant = true; + private static final boolean isWriteKv = true; private static final boolean isContract = false; @@ -138,6 +140,22 @@ public class IntegrationTest4MQ { integrationBase.testSDK_Contract(adminKey, ledgerHash, blockchainService,ledgerRepository); } + long participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + + long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + + System.out.printf("before add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + + if (isRegisterParticipant) { + IntegrationBase.KeyPairResponse participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); + } + + participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + + userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + + System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + IntegrationBase.testConsistencyAmongNodes(ledgers); if(isOnline){ diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index db75db26..f1d0c93b 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -242,7 +242,7 @@ public class LedgerInitProperties { .parseBoolean(PropertiesUtils.getRequiredProperty(props, initializerSecureKey)); NetworkAddress initializerAddress = new NetworkAddress(initializerHost, initializerPort, initializerSecure); parti.setInitializerAddress(initializerAddress); - + parti.setParticipantNodeState(ParticipantNodeState.CONSENSUSED); initProps.addConsensusParticipant(parti); } From bd59999f8322f8cf9b3269e840a8591d83403bb0 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 27 Aug 2019 16:23:50 +0800 Subject: [PATCH 058/124] solve pubkey bug --- .../test-integration/src/test/resources/mq.config | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/source/test/test-integration/src/test/resources/mq.config b/source/test/test-integration/src/test/resources/mq.config index 33eefafe..0ff20f39 100644 --- a/source/test/test-integration/src/test/resources/mq.config +++ b/source/test/test-integration/src/test/resources/mq.config @@ -6,7 +6,12 @@ system.msg.queue.block.txsize=1000 system.msg.queue.block.maxdelay=2000 system.servers.num=4 -system.server.0.pubkey=endPsK36koyFr1D245Sa9j83vt6pZUdFBJoJRB3xAsWM6cwhRbna -system.server.1.pubkey=endPsK36sC5JdPCDPDAXUwZtS3sxEmqEhFcC4whayAsTTh8Z6eoZ -system.server.2.pubkey=endPsK36jEG281HMHeh6oSqzqLkT95DTnCM6REDURjdb2c67uR3R -system.server.3.pubkey=endPsK36nse1dck4uF19zPvAMijCV336Y3zWdgb4rQG8QoRj5ktR \ No newline at end of file +#system.server.0.pubkey=endPsK36koyFr1D245Sa9j83vt6pZUdFBJoJRB3xAsWM6cwhRbna +#system.server.1.pubkey=endPsK36sC5JdPCDPDAXUwZtS3sxEmqEhFcC4whayAsTTh8Z6eoZ +#system.server.2.pubkey=endPsK36jEG281HMHeh6oSqzqLkT95DTnCM6REDURjdb2c67uR3R +#system.server.3.pubkey=endPsK36nse1dck4uF19zPvAMijCV336Y3zWdgb4rQG8QoRj5ktR + +system.server.0.pubkey=3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9 +system.server.1.pubkey=3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX +system.server.2.pubkey=3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x +system.server.3.pubkey=3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk From f7315d3271cad153cdd1b7f4644944f459f2075f Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 27 Aug 2019 21:44:16 +0800 Subject: [PATCH 059/124] Add security init settings to ledger init settings; --- .../com/jd/blockchain/consts/DataCodes.java | 13 +- .../ledger/core/LedgerAdminDatasetTest.java | 4 +- .../ledger/core/LedgerEditorTest.java | 2 +- .../ledger/core/LedgerInitOperationTest.java | 4 +- .../core/LedgerInitSettingSerializeTest.java | 4 +- .../ledger/core/LedgerManagerTest.java | 4 +- .../ledger/core/LedgerTestUtils.java | 4 +- .../jd/blockchain/ledger/RoleInitData.java | 48 +++++ .../blockchain/ledger/RoleInitSettings.java | 41 ++++ .../blockchain/ledger/SecurityInitData.java | 28 +++ .../ledger/SecurityInitSettings.java | 24 +++ ...itSettingData.java => LedgerInitData.java} | 2 +- .../ledger/SecurityInitDataTest.java | 82 ++++++++ .../jd/blockchain/sdk/LedgerInitSettings.java | 192 +++++++++--------- .../sdk/converters/ClientResolveUtil.java | 4 +- .../web/LedgerInitializeWebController.java | 2 +- .../mocker/MockerLedgerInitializer.java | 2 +- 17 files changed, 343 insertions(+), 117 deletions(-) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java rename source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/{LedgerInitSettingData.java => LedgerInitData.java} (95%) create mode 100644 source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index d83a311e..d1487f73 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -21,7 +21,7 @@ public interface DataCodes { public static final int BLOCK_GENESIS = 0x120; public static final int DATA_SNAPSHOT = 0x130; - + // public static final int LEDGER_ADMIN_DATA = 0x131; public static final int TX = 0x200; @@ -59,16 +59,15 @@ public interface DataCodes { public static final int ENUM_TX_PERMISSION = 0x401; public static final int ENUM_LEDGER_PERMISSION = 0x402; public static final int ENUM_MULTI_ROLES_POLICY = 0x403; - + public static final int PRIVILEGE_SET = 0x410; - + public static final int ROLE_SET = 0x411; - // contract types of metadata; public static final int METADATA = 0x600; public static final int METADATA_V2 = 0x601; - + public static final int METADATA_INIT_SETTING = 0x610; public static final int METADATA_INIT_PROPOSAL = 0x611; @@ -79,6 +78,10 @@ public interface DataCodes { public static final int METADATA_CONSENSUS_PARTICIPANT = 0x621; + public static final int METADATA_SECURITY_INIT_SETTING = 0x622; + + public static final int METADATA_ROLE_INIT_SETTING = 0x623; + // public static final int METADATA_CONSENSUS_NODE = 0x630; // // public static final int METADATA_CONSENSUS_SETTING = 0x631; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index 3c31a02e..bce41009 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -37,7 +37,7 @@ import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.net.NetworkAddress; @@ -51,7 +51,7 @@ public class LedgerAdminDatasetTest { @Test public void testSerialization() { String keyPrefix = ""; - LedgerInitSettingData initSetting = new LedgerInitSettingData(); + LedgerInitData initSetting = new LedgerInitData(); ConsensusParticipantData[] parties = new ConsensusParticipantData[5]; BlockchainKeypair[] bckeys = new BlockchainKeypair[parties.length]; for (int i = 0; i < parties.length; i++) { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java index c73badb1..97979205 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerEditorTest.java @@ -34,7 +34,7 @@ import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.net.NetworkAddress; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java index 672ba12a..8c245fa2 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java @@ -25,7 +25,7 @@ import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.ParticipantCertData; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitOpTemplate; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.net.NetworkAddress; @@ -36,7 +36,7 @@ public class LedgerInitOperationTest { byte[] seed = null; byte[] csSysSettingBytes = null; - LedgerInitSettingData ledgerInitSettingData = new LedgerInitSettingData(); + LedgerInitData ledgerInitSettingData = new LedgerInitData(); @Before public void initCfg() { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java index 1914f17b..46c96f65 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java @@ -24,14 +24,14 @@ import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.ParticipantCertData; import com.jd.blockchain.transaction.ConsensusParticipantData; import com.jd.blockchain.transaction.LedgerInitOpTemplate; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.net.NetworkAddress; public class LedgerInitSettingSerializeTest { byte[] seed = null; byte[] csSysSettingBytes = null; - LedgerInitSettingData ledgerInitSettingData = new LedgerInitSettingData(); + LedgerInitData ledgerInitSettingData = new LedgerInitData(); LedgerInitOpTemplate template = new LedgerInitOpTemplate(); private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java index fe2a1f19..cf3a294d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java @@ -44,7 +44,7 @@ import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountSet; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.ConsensusParticipantData; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.io.BytesUtils; @@ -202,7 +202,7 @@ public class LedgerManagerTest { defCryptoSetting.setAutoVerifyHash(true); defCryptoSetting.setHashAlgorithm(ClassicAlgorithm.SHA256); - LedgerInitSettingData initSetting = new LedgerInitSettingData(); + LedgerInitData initSetting = new LedgerInitData(); initSetting.setLedgerSeed(BytesUtils.toBytes("A Test Ledger seed!", "UTF-8")); initSetting.setCryptoSetting(defCryptoSetting); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java index 44c15b92..eb6835b5 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java @@ -21,7 +21,7 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; import com.jd.blockchain.transaction.ConsensusParticipantData; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.transaction.TransactionService; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; @@ -62,7 +62,7 @@ public class LedgerTestUtils { defCryptoSetting.setAutoVerifyHash(true); defCryptoSetting.setHashAlgorithm(ClassicAlgorithm.SHA256); - LedgerInitSettingData initSetting = new LedgerInitSettingData(); + LedgerInitData initSetting = new LedgerInitData(); initSetting.setLedgerSeed(BytesUtils.toBytes("A Test Ledger seed!", "UTF-8")); initSetting.setCryptoSetting(defCryptoSetting); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java new file mode 100644 index 00000000..ea1822e2 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java @@ -0,0 +1,48 @@ +package com.jd.blockchain.ledger; + +public class RoleInitData implements RoleInitSettings { + + private String roleName; + + private LedgerPermission[] ledgerPermissions; + + private TransactionPermission[] transactionPermissions; + + public RoleInitData() { + } + + public RoleInitData(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] transactionPermissions) { + this.roleName = roleName; + this.ledgerPermissions = ledgerPermissions; + this.transactionPermissions = transactionPermissions; + } + + @Override + public String getRoleName() { + return roleName; + } + + @Override + public LedgerPermission[] getLedgerPermissions() { + return ledgerPermissions; + } + + @Override + public TransactionPermission[] getTransactionPermissions() { + return transactionPermissions; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public void setLedgerPermissions(LedgerPermission[] ledgerPermissions) { + this.ledgerPermissions = ledgerPermissions; + } + + public void setTransactionPermissions(TransactionPermission[] transactionPermissions) { + this.transactionPermissions = transactionPermissions; + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java new file mode 100644 index 00000000..bdfddb52 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java @@ -0,0 +1,41 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +/** + * 角色参数设置; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.METADATA_ROLE_INIT_SETTING) +public interface RoleInitSettings { + + /** + * 角色名称; + * + * @return + */ + @DataField(order = 0, primitiveType = PrimitiveType.TEXT) + String getRoleName(); + + /** + * 角色的账本权限; + * + * @return + */ + @DataField(order = 1, refEnum = true, list = true) + LedgerPermission[] getLedgerPermissions(); + + /** + * 角色的交易权限; + * + * @return + */ + @DataField(order = 2, refEnum = true, list = true) + TransactionPermission[] getTransactionPermissions(); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java new file mode 100644 index 00000000..9e4a5d55 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java @@ -0,0 +1,28 @@ +package com.jd.blockchain.ledger; + +import java.util.ArrayList; +import java.util.List; + +public class SecurityInitData implements SecurityInitSettings { + + private List roles = new ArrayList(); + + @Override + public RoleInitData[] getRoles() { + return roles.toArray(new RoleInitData[roles.size()]); + } + + public void setRoles(RoleInitData[] roles) { + List list = new ArrayList(); + for (RoleInitSettings r : roles) { + list.add(r); + } + this.roles = list; + } + + public void add(String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] transactionPermissions) { + RoleInitData roleInitData = new RoleInitData(roleName, ledgerPermissions, transactionPermissions); + roles.add(roleInitData); + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java new file mode 100644 index 00000000..32e5838e --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java @@ -0,0 +1,24 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.DataCodes; + +/** + * 安全权限的初始化; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.METADATA_SECURITY_INIT_SETTING) +public interface SecurityInitSettings { + + /** + * 角色列表; + * + * @return + */ + @DataField(order = 0, refContract = true, list = true) + RoleInitSettings[] getRoles(); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitSettingData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java similarity index 95% rename from source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitSettingData.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java index a6f6045a..35215e53 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitSettingData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java @@ -5,7 +5,7 @@ import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerInitSetting; -public class LedgerInitSettingData implements LedgerInitSetting { +public class LedgerInitData implements LedgerInitSetting { private byte[] ledgerSeed; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java new file mode 100644 index 00000000..fab4c57a --- /dev/null +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java @@ -0,0 +1,82 @@ +package test.com.jd.blockchain.ledger; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.SecurityInitData; +import com.jd.blockchain.ledger.SecurityInitSettings; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.utils.serialize.json.JSONSerializeUtils; + +public class SecurityInitDataTest { + + @Test + public void testEnumsSerialization() { + LedgerPermission[] permissions = JSONSerializeUtils.deserializeFromJSON("[\"REGISTER_USER\",\"REGISTER_DATA_ACCOUNT\"]", LedgerPermission[].class); + assertNotNull(permissions); + assertEquals(2, permissions.length); + assertEquals(LedgerPermission.REGISTER_USER, permissions[0]); + assertEquals(LedgerPermission.REGISTER_DATA_ACCOUNT, permissions[1]); + + LedgerPermission[] permissions2 = JSONSerializeUtils.deserializeFromJSON("['REGISTER_USER', 'REGISTER_DATA_ACCOUNT']", LedgerPermission[].class); + assertNotNull(permissions2); + assertEquals(2, permissions2.length); + assertEquals(LedgerPermission.REGISTER_USER, permissions2[0]); + assertEquals(LedgerPermission.REGISTER_DATA_ACCOUNT, permissions2[1]); + + } + + @Test + public void testSecurityInitDataSerialization() { + + SecurityInitData securityInitData = new SecurityInitData(); + + securityInitData.add("DEFAULT", + new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, + new TransactionPermission[] { TransactionPermission.CONTRACT_OPERATION }); + securityInitData.add("ADMIN", + new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, + new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }); + securityInitData.add("R1", + new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, + null); + securityInitData.add("R2", null, new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }); + + String json = JSONSerializeUtils.serializeToJSON(securityInitData, true); + System.out.println("----------- JSON ------------"); + System.out.println(json); + System.out.println("-----------------------"); + + SecurityInitData desSecurityInitData = JSONSerializeUtils.deserializeFromJSON(json, SecurityInitData.class); + + String json2 = JSONSerializeUtils.serializeToJSON(desSecurityInitData, true); + System.out.println("----------- JSON2 ------------"); + System.out.println(json2); + System.out.println("-----------------------"); + + assertEquals(json, json2); + + byte[] bytes = BinaryProtocol.encode(securityInitData, SecurityInitSettings.class); + + SecurityInitSettings securityInitData2 = BinaryProtocol.decode(bytes); + + byte[] bytes2 = BinaryProtocol.encode(securityInitData2, SecurityInitSettings.class); + + assertArrayEquals(bytes, bytes2); + + assertEquals(4, securityInitData2.getRoles().length); + assertEquals(securityInitData.getRoles().length, securityInitData2.getRoles().length); + assertEquals(LedgerPermission.REGISTER_USER, securityInitData2.getRoles()[1].getLedgerPermissions()[0]); + assertEquals(securityInitData.getRoles()[1].getLedgerPermissions()[0], securityInitData2.getRoles()[1].getLedgerPermissions()[0]); + assertEquals(securityInitData.getRoles()[1].getLedgerPermissions()[1], securityInitData2.getRoles()[1].getLedgerPermissions()[1]); + + } + +} diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java index d4f287ab..cc6ca054 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java @@ -1,96 +1,96 @@ -package com.jd.blockchain.sdk; - - -import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.ParticipantNode; - -/** - * 账本初始化配置 - * - * @author shaozhuguang - * @date 2019-04-23 - * @since 1.0.0 - * - */ -public class LedgerInitSettings { - - /** - * 账本初始化种子 - */ - private String seed; - - /** - * 共识参与方的默克尔树的根; - */ - private HashDigest participantsHash; - - /** - * 算法配置 - */ - private CryptoSetting cryptoSetting; - - /** - * 共识协议 - */ - private String consensusProtocol; - - /** - * 共识配置 - */ - private ConsensusSettings consensusSettings; - - /** - * 共识参与方 - */ - private ParticipantNode[] participantNodes; - - public void setSeed(String seed) { - this.seed = seed; - } - - public String getSeed() { - return seed; - } - - public HashDigest getParticipantsHash() { - return participantsHash; - } - - public void setParticipantsHash(HashDigest participantsHash) { - this.participantsHash = participantsHash; - } - - public CryptoSetting getCryptoSetting() { - return cryptoSetting; - } - - public void setCryptoSetting(CryptoSetting cryptoSetting) { - this.cryptoSetting = cryptoSetting; - } - - public String getConsensusProtocol() { - return consensusProtocol; - } - - public void setConsensusProtocol(String consensusProtocol) { - this.consensusProtocol = consensusProtocol; - } - - public ConsensusSettings getConsensusSettings() { - return consensusSettings; - } - - public void setConsensusSettings(ConsensusSettings consensusSettings) { - this.consensusSettings = consensusSettings; - } - - public ParticipantNode[] getParticipantNodes() { - return participantNodes; - } - - public void setParticipantNodes(ParticipantNode[] participantNodes) { - this.participantNodes = participantNodes; - } -} +//package com.jd.blockchain.sdk; +// +// +//import com.jd.blockchain.consensus.ConsensusSettings; +//import com.jd.blockchain.crypto.HashDigest; +//import com.jd.blockchain.ledger.CryptoSetting; +//import com.jd.blockchain.ledger.ParticipantNode; +// +///** +// * 账本初始化配置 +// * +// * @author shaozhuguang +// * @date 2019-04-23 +// * @since 1.0.0 +// * +// */ +//public class LedgerInitSettings { +// +// /** +// * 账本初始化种子 +// */ +// private String seed; +// +// /** +// * 共识参与方的默克尔树的根; +// */ +// private HashDigest participantsHash; +// +// /** +// * 算法配置 +// */ +// private CryptoSetting cryptoSetting; +// +// /** +// * 共识协议 +// */ +// private String consensusProtocol; +// +// /** +// * 共识配置 +// */ +// private ConsensusSettings consensusSettings; +// +// /** +// * 共识参与方 +// */ +// private ParticipantNode[] participantNodes; +// +// public void setSeed(String seed) { +// this.seed = seed; +// } +// +// public String getSeed() { +// return seed; +// } +// +// public HashDigest getParticipantsHash() { +// return participantsHash; +// } +// +// public void setParticipantsHash(HashDigest participantsHash) { +// this.participantsHash = participantsHash; +// } +// +// public CryptoSetting getCryptoSetting() { +// return cryptoSetting; +// } +// +// public void setCryptoSetting(CryptoSetting cryptoSetting) { +// this.cryptoSetting = cryptoSetting; +// } +// +// public String getConsensusProtocol() { +// return consensusProtocol; +// } +// +// public void setConsensusProtocol(String consensusProtocol) { +// this.consensusProtocol = consensusProtocol; +// } +// +// public ConsensusSettings getConsensusSettings() { +// return consensusSettings; +// } +// +// public void setConsensusSettings(ConsensusSettings consensusSettings) { +// this.consensusSettings = consensusSettings; +// } +// +// public ParticipantNode[] getParticipantNodes() { +// return participantNodes; +// } +// +// public void setParticipantNodes(ParticipantNode[] participantNodes) { +// this.participantNodes = participantNodes; +// } +//} diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java index 86011bd7..68b82f14 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java @@ -37,7 +37,7 @@ import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; import com.jd.blockchain.transaction.DataAccountRegisterOpTemplate; import com.jd.blockchain.transaction.KVData; import com.jd.blockchain.transaction.LedgerInitOpTemplate; -import com.jd.blockchain.transaction.LedgerInitSettingData; +import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.transaction.UserRegisterOpTemplate; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.Base58Utils; @@ -173,7 +173,7 @@ public class ClientResolveUtil { public static LedgerInitOperation convertLedgerInitOperation(JSONObject jsonObject) { JSONObject legerInitObj = jsonObject.getJSONObject("initSetting"); - LedgerInitSettingData ledgerInitSettingData = new LedgerInitSettingData(); + LedgerInitData ledgerInitSettingData = new LedgerInitData(); String ledgerSeedStr = legerInitObj.getString("ledgerSeed"); // 种子需要做Base64转换 diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index 4fdc4f85..eab29145 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -328,7 +328,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, ConsensusSettings csSettings, CryptoSetting cryptoSetting) { // 创建初始化配置; - LedgerInitSettingData initSetting = new LedgerInitSettingData(); + LedgerInitData initSetting = new LedgerInitData(); initSetting.setLedgerSeed(ledgerProps.getLedgerSeed()); initSetting.setCryptoSetting(cryptoSetting); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java index d8bd2c14..fe9f332b 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java @@ -203,7 +203,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon public LedgerInitProposal prepareLocalProposal(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, ConsensusSettings csSettings, CryptoSetting cryptoSetting) { // 创建初始化配置; - LedgerInitSettingData initSetting = new LedgerInitSettingData(); + LedgerInitData initSetting = new LedgerInitData(); initSetting.setLedgerSeed(ledgerProps.getLedgerSeed()); initSetting.setCryptoSetting(cryptoSetting); From ca7cfbbedc95d590a5242b37862f9494239baf5a Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 27 Aug 2019 22:33:41 +0800 Subject: [PATCH 060/124] Fixed some errors of test cases; --- .../sdk/converters/ClientResolveUtil.java | 723 +++++++++--------- .../src/test/resources/ledger_init_test.init | 2 +- .../tools/initializer/LedgerInitCommand.java | 7 +- .../initializer/LedgerInitProperties.java | 7 +- .../src/test/resources/ledger-binding.conf | 4 + 5 files changed, 385 insertions(+), 358 deletions(-) diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java index 3c251a66..2c44ea1e 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java @@ -8,19 +8,41 @@ */ package com.jd.blockchain.sdk.converters; +import java.lang.reflect.Field; + +import org.apache.commons.codec.binary.Base64; + import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.transaction.*; +import com.jd.blockchain.ledger.BlockchainIdentityData; +import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.BytesValueEncoding; +import com.jd.blockchain.ledger.ContractCodeDeployOperation; +import com.jd.blockchain.ledger.ContractEventSendOperation; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.DataAccountKVSetOperation; +import com.jd.blockchain.ledger.DataAccountRegisterOperation; +import com.jd.blockchain.ledger.DataType; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.transaction.ContractCodeDeployOpTemplate; +import com.jd.blockchain.transaction.ContractEventSendOpTemplate; +import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; +import com.jd.blockchain.transaction.DataAccountRegisterOpTemplate; +import com.jd.blockchain.transaction.KVData; +import com.jd.blockchain.transaction.LedgerInitData; +import com.jd.blockchain.transaction.LedgerInitOpTemplate; +import com.jd.blockchain.transaction.UserRegisterOpTemplate; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.Base58Utils; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.io.BytesUtils; -import org.apache.commons.codec.binary.Base64; - -import java.lang.reflect.Field; /** * @@ -31,349 +53,350 @@ import java.lang.reflect.Field; public class ClientResolveUtil { - public static KVDataEntry[] read(KVDataEntry[] kvDataEntries) { - if (kvDataEntries == null || kvDataEntries.length == 0) { - return kvDataEntries; - } - KVDataEntry[] resolveKvDataEntries = new KVDataEntry[kvDataEntries.length]; - // kvDataEntries是代理对象,需要处理 - for (int i = 0; i < kvDataEntries.length; i++) { - KVDataEntry kvDataEntry = kvDataEntries[i]; - String key = kvDataEntry.getKey(); - long version = kvDataEntry.getVersion(); - DataType dataType = kvDataEntry.getType(); - KvData innerKvData = new KvData(key, version, dataType); - Object valueObj = kvDataEntry.getValue(); - switch (dataType) { - case NIL: - break; - case BYTES: - case TEXT: - case JSON: - case XML: - innerKvData.setValue(valueObj.toString()); - break; - case INT32: - innerKvData.setValue(Integer.parseInt(valueObj.toString())); - break; - case INT64: - innerKvData.setValue(Long.parseLong(valueObj.toString())); - break; - default: - throw new IllegalStateException("Unsupported value type[" + dataType + "] to resolve!"); - } - resolveKvDataEntries[i] = innerKvData; - } - return resolveKvDataEntries; - } - - public static Operation read(Operation operation) { - - try { - // Class - Class clazz = operation.getClass(); - Field field = clazz.getSuperclass().getDeclaredField("h"); - field.setAccessible(true); - Object object = field.get(operation); - if (object instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) object; - if (jsonObject.containsKey("accountID")) { - return convertDataAccountRegisterOperation(jsonObject); - } else if (jsonObject.containsKey("userID")) { - return convertUserRegisterOperation(jsonObject); - } else if (jsonObject.containsKey("contractID")) { - return convertContractCodeDeployOperation(jsonObject); - } else if (jsonObject.containsKey("writeSet")) { - return convertDataAccountKVSetOperation(jsonObject); - } else if (jsonObject.containsKey("initSetting")) { - return convertLedgerInitOperation(jsonObject); - } else if (jsonObject.containsKey("contractAddress")) { - return convertContractEventSendOperation(jsonObject); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - return null; - } - - public static Object readValueByBytesValue(BytesValue bytesValue) { - DataType dataType = bytesValue.getType(); - Bytes saveVal = bytesValue.getValue(); - Object showVal; - switch (dataType) { - case BYTES: - // return hex - showVal = HexUtils.encode(saveVal.toBytes()); - break; - case TEXT: - case JSON: - showVal = saveVal.toUTF8String(); - break; - case INT64: - showVal = BytesUtils.toLong(saveVal.toBytes()); - break; - default: - showVal = HexUtils.encode(saveVal.toBytes()); - break; - } - return showVal; - } - - public static DataAccountRegisterOperation convertDataAccountRegisterOperation(JSONObject jsonObject) { - JSONObject account = jsonObject.getJSONObject("accountID"); - return new DataAccountRegisterOpTemplate(blockchainIdentity(account)); - } - - public static DataAccountKVSetOperation convertDataAccountKVSetOperation(JSONObject jsonObject) { - // 写入集合处理 - JSONArray writeSetObj = jsonObject.getJSONArray("writeSet"); - JSONObject accountAddrObj = jsonObject.getJSONObject("accountAddress"); - String addressBase58 = accountAddrObj.getString("value"); - Bytes address = Bytes.fromBase58(addressBase58); - - DataAccountKVSetOpTemplate kvOperation = new DataAccountKVSetOpTemplate(address); - for (int i = 0; i clazz = operation.getClass(); + Field field = clazz.getSuperclass().getDeclaredField("h"); + field.setAccessible(true); + Object object = field.get(operation); + if (object instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) object; + if (jsonObject.containsKey("accountID")) { + return convertDataAccountRegisterOperation(jsonObject); + } else if (jsonObject.containsKey("userID")) { + return convertUserRegisterOperation(jsonObject); + } else if (jsonObject.containsKey("contractID")) { + return convertContractCodeDeployOperation(jsonObject); + } else if (jsonObject.containsKey("writeSet")) { + return convertDataAccountKVSetOperation(jsonObject); + } else if (jsonObject.containsKey("initSetting")) { + return convertLedgerInitOperation(jsonObject); + } else if (jsonObject.containsKey("contractAddress")) { + return convertContractEventSendOperation(jsonObject); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + return null; + } + + public static Object readValueByBytesValue(BytesValue bytesValue) { + DataType dataType = bytesValue.getType(); + Bytes saveVal = bytesValue.getValue(); + Object showVal; + switch (dataType) { + case BYTES: + // return hex + showVal = HexUtils.encode(saveVal.toBytes()); + break; + case TEXT: + case JSON: + showVal = saveVal.toUTF8String(); + break; + case INT64: + showVal = BytesUtils.toLong(saveVal.toBytes()); + break; + default: + showVal = HexUtils.encode(saveVal.toBytes()); + break; + } + return showVal; + } + + public static DataAccountRegisterOperation convertDataAccountRegisterOperation(JSONObject jsonObject) { + JSONObject account = jsonObject.getJSONObject("accountID"); + return new DataAccountRegisterOpTemplate(blockchainIdentity(account)); + } + + public static DataAccountKVSetOperation convertDataAccountKVSetOperation(JSONObject jsonObject) { + // 写入集合处理 + JSONArray writeSetObj = jsonObject.getJSONArray("writeSet"); + JSONObject accountAddrObj = jsonObject.getJSONObject("accountAddress"); + String addressBase58 = accountAddrObj.getString("value"); + Bytes address = Bytes.fromBase58(addressBase58); + + DataAccountKVSetOpTemplate kvOperation = new DataAccountKVSetOpTemplate(address); + for (int i = 0; i < writeSetObj.size(); i++) { + JSONObject currWriteSetObj = writeSetObj.getJSONObject(i); + long expectedVersion = currWriteSetObj.getLong("expectedVersion"); + JSONObject valueObj = currWriteSetObj.getJSONObject("value"); + String typeStr = valueObj.getString("type"); + // Base58Utils.decode(valueObj.getJSONObject("value").getString("value")) + String realValBase58 = valueObj.getJSONObject("value").getString("value"); + String key = currWriteSetObj.getString("key"); + DataType dataType = DataType.valueOf(typeStr); + BytesValue bytesValue = BytesData.fromType(dataType, Base58Utils.decode(realValBase58)); + KVData kvData = new KVData(key, bytesValue, expectedVersion); + kvOperation.set(kvData); + } + + return kvOperation; + } + + public static LedgerInitOperation convertLedgerInitOperation(JSONObject jsonObject) { + JSONObject legerInitObj = jsonObject.getJSONObject("initSetting"); + LedgerInitData ledgerInitSettingData = new LedgerInitData(); + String ledgerSeedStr = legerInitObj.getString("ledgerSeed"); + + // 种子需要做Base64转换 + ledgerInitSettingData.setLedgerSeed(Base64.decodeBase64(BytesUtils.toBytes(ledgerSeedStr))); + + String consensusProvider = legerInitObj.getString("consensusProvider"); + + ledgerInitSettingData.setConsensusProvider(consensusProvider); + + JSONObject cryptoSettingObj = legerInitObj.getJSONObject("cryptoSetting"); + boolean autoVerifyHash = cryptoSettingObj.getBoolean("autoVerifyHash"); + short hashAlgorithm = cryptoSettingObj.getShort("hashAlgorithm"); + + CryptoConfig cryptoConfig = new CryptoConfig(); + + cryptoConfig.setAutoVerifyHash(autoVerifyHash); + + cryptoConfig.setHashAlgorithm(hashAlgorithm); + + ledgerInitSettingData.setCryptoSetting(cryptoConfig); + + JSONObject consensusSettingsObj = legerInitObj.getJSONObject("consensusSettings"); + Bytes consensusSettings = Bytes.fromBase58(consensusSettingsObj.getString("value")); + + ledgerInitSettingData.setConsensusSettings(consensusSettings); + + JSONArray consensusParticipantsArray = legerInitObj.getJSONArray("consensusParticipants"); + + if (!consensusParticipantsArray.isEmpty()) { + ParticipantNode[] participantNodes = new ParticipantNode[consensusParticipantsArray.size()]; + for (int i = 0; i < consensusParticipantsArray.size(); i++) { + JSONObject currConsensusParticipant = consensusParticipantsArray.getJSONObject(i); + Bytes address = Bytes.fromBase58(currConsensusParticipant.getString("address")); + String name = currConsensusParticipant.getString("name"); + int id = currConsensusParticipant.getInteger("id"); + JSONObject pubKeyObj = currConsensusParticipant.getJSONObject("pubKey"); + String pubKeyBase58 = pubKeyObj.getString("value"); + // 生成ParticipantNode对象 + ParticipantCertData participantCertData = new ParticipantCertData(id, address, name, + new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes())); + participantNodes[i] = participantCertData; + } + ledgerInitSettingData.setConsensusParticipants(participantNodes); + } + + return new LedgerInitOpTemplate(ledgerInitSettingData); + } + + public static UserRegisterOperation convertUserRegisterOperation(JSONObject jsonObject) { + JSONObject user = jsonObject.getJSONObject("userID"); + return new UserRegisterOpTemplate(blockchainIdentity(user)); + } + + public static ContractCodeDeployOperation convertContractCodeDeployOperation(JSONObject jsonObject) { + JSONObject contract = jsonObject.getJSONObject("contractID"); + BlockchainIdentityData blockchainIdentity = blockchainIdentity(contract); + + String chainCodeStr = jsonObject.getString("chainCode"); + ContractCodeDeployOpTemplate contractCodeDeployOpTemplate = new ContractCodeDeployOpTemplate(blockchainIdentity, + BytesUtils.toBytes(chainCodeStr)); + return contractCodeDeployOpTemplate; + } + + public static ContractEventSendOperation convertContractEventSendOperation(JSONObject jsonObject) { + JSONObject contractAddressObj = jsonObject.getJSONObject("contractAddress"); + String contractAddress = contractAddressObj.getString("value"); + String argsStr = jsonObject.getString("args"); + String event = jsonObject.getString("event"); + return new ContractEventSendOpTemplate(Bytes.fromBase58(contractAddress), event, + BytesValueEncoding.encodeArray(new Object[] { argsStr }, null)); + } + + private static BlockchainIdentityData blockchainIdentity(JSONObject jsonObject) { + JSONObject addressObj = jsonObject.getJSONObject("address"); + // base58值 + String addressBase58 = addressObj.getString("value"); + Bytes address = Bytes.fromBase58(addressBase58); + + JSONObject pubKeyObj = jsonObject.getJSONObject("pubKey"); + // base58值 + String pubKeyBase58 = pubKeyObj.getString("value"); + PubKey pubKey = new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes()); + + // 生成对应的对象 + return new BlockchainIdentityData(address, pubKey); + } + + public static class CryptoConfig implements CryptoSetting { + + private short hashAlgorithm; + + private boolean autoVerifyHash; + + @Override + public CryptoProvider[] getSupportedProviders() { + return new CryptoProvider[0]; + } + + @Override + public short getHashAlgorithm() { + return hashAlgorithm; + } + + @Override + public boolean getAutoVerifyHash() { + return autoVerifyHash; + } + + public void setHashAlgorithm(short hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + } + + public void setAutoVerifyHash(boolean autoVerifyHash) { + this.autoVerifyHash = autoVerifyHash; + } + } + + public static class ParticipantCertData implements ParticipantNode { + private int id; + private Bytes address; + private String name; + private PubKey pubKey; + + public ParticipantCertData() { + } + + public ParticipantCertData(ParticipantNode participantNode) { + this.address = participantNode.getAddress(); + this.name = participantNode.getName(); + this.pubKey = participantNode.getPubKey(); + } + + public ParticipantCertData(int id, Bytes address, String name, PubKey pubKey) { + this.id = id; + this.address = address; + this.name = name; + this.pubKey = pubKey; + } + + @Override + public Bytes getAddress() { + return address; + } + + @Override + public String getName() { + return name; + } + + @Override + public PubKey getPubKey() { + return pubKey; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + } + + public static class KvData implements KVDataEntry { + + private String key; + + private long version; + + private DataType dataType; + + private Object value; + + public KvData() { + } + + public KvData(String key, long version, DataType dataType) { + this(key, version, dataType, null); + } + + public KvData(String key, long version, DataType dataType, Object value) { + this.key = key; + this.version = version; + this.dataType = dataType; + this.value = value; + } + + public void setKey(String key) { + this.key = key; + } + + public void setVersion(long version) { + this.version = version; + } + + public void setDataType(DataType dataType) { + this.dataType = dataType; + } + + public void setValue(Object value) { + this.value = value; + } + + @Override + public String getKey() { + return key; + } + + @Override + public long getVersion() { + return version; + } + + @Override + public DataType getType() { + return dataType; + } + + @Override + public Object getValue() { + return value; + } + } } \ No newline at end of file diff --git a/source/test/test-integration/src/test/resources/ledger_init_test.init b/source/test/test-integration/src/test/resources/ledger_init_test.init index a1dde11f..4c4bf2d9 100644 --- a/source/test/test-integration/src/test/resources/ledger_init_test.init +++ b/source/test/test-integration/src/test/resources/ledger_init_test.init @@ -3,7 +3,7 @@ ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe #账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; -ledger.name= +ledger.name=TEST-LEDGER #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index 4617076a..0c59c1cc 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -2,8 +2,6 @@ package com.jd.blockchain.tools.initializer; import java.io.File; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -16,7 +14,7 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.core.impl.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.tools.initializer.LedgerBindingConfig.BindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.tools.keygen.KeyGenCommand; @@ -186,7 +184,8 @@ public class LedgerInitCommand { // 设置账本名称 bindingConf.setLedgerName(ledgerInitProperties.getLedgerName()); - bindingConf.getParticipant().setAddress(ledgerInitProperties.getConsensusParticipant(currId).getAddress()); + bindingConf.getParticipant() + .setAddress(ledgerInitProperties.getConsensusParticipant(currId).getAddress().toBase58()); // 设置参与方名称 bindingConf.getParticipant().setName(ledgerInitProperties.getConsensusParticipant(currId).getName()); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index 7b48cec6..407501e9 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -14,6 +14,7 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.io.FileUtils; @@ -262,7 +263,7 @@ public class LedgerInitProperties { private int id; - private String address; + private Bytes address; private String name; @@ -283,7 +284,7 @@ public class LedgerInitProperties { } @Override - public String getAddress() { + public Bytes getAddress() { return address; } @@ -317,7 +318,7 @@ public class LedgerInitProperties { public void setPubKey(PubKey pubKey) { this.pubKey = pubKey; - this.address = AddressEncoding.generateAddress(pubKey).toBase58(); + this.address = AddressEncoding.generateAddress(pubKey); } } diff --git a/source/tools/tools-initializer/src/test/resources/ledger-binding.conf b/source/tools/tools-initializer/src/test/resources/ledger-binding.conf index e4cf6c2b..82b1ba3d 100644 --- a/source/tools/tools-initializer/src/test/resources/ledger-binding.conf +++ b/source/tools/tools-initializer/src/test/resources/ledger-binding.conf @@ -6,7 +6,9 @@ j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf #第1个账本[j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk]的配置; #账本的当前共识参与方的ID; +binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.name= PRODUCT-INFO binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.address=1 +binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.name=PARTI-01 #账本的当前共识参与方的私钥文件的保存路径; binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.pk-path=keys/jd-com.priv #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; @@ -21,7 +23,9 @@ binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.db.pwd=kksfweffj #第2个账本[j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf]的配置; #账本的当前共识参与方的ID; +binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.name= BASIC-INFO binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.address=2 +binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.name=PARTI-02 #账本的当前共识参与方的私钥文件的保存路径; binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.pk-path=keys/jd-com-1.priv #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; From b848e92d0c4dd451f6f5931663a577b987d7aaff Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Wed, 28 Aug 2019 09:01:15 +0800 Subject: [PATCH 061/124] delete error pubkey --- source/test/test-integration/src/test/resources/mq.config | 5 ----- 1 file changed, 5 deletions(-) diff --git a/source/test/test-integration/src/test/resources/mq.config b/source/test/test-integration/src/test/resources/mq.config index 0ff20f39..9bd3238e 100644 --- a/source/test/test-integration/src/test/resources/mq.config +++ b/source/test/test-integration/src/test/resources/mq.config @@ -6,11 +6,6 @@ system.msg.queue.block.txsize=1000 system.msg.queue.block.maxdelay=2000 system.servers.num=4 -#system.server.0.pubkey=endPsK36koyFr1D245Sa9j83vt6pZUdFBJoJRB3xAsWM6cwhRbna -#system.server.1.pubkey=endPsK36sC5JdPCDPDAXUwZtS3sxEmqEhFcC4whayAsTTh8Z6eoZ -#system.server.2.pubkey=endPsK36jEG281HMHeh6oSqzqLkT95DTnCM6REDURjdb2c67uR3R -#system.server.3.pubkey=endPsK36nse1dck4uF19zPvAMijCV336Y3zWdgb4rQG8QoRj5ktR - system.server.0.pubkey=3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9 system.server.1.pubkey=3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX system.server.2.pubkey=3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x From 81c52e4750fb636479ff7732f69fede2586ae918 Mon Sep 17 00:00:00 2001 From: zhanglin33 Date: Wed, 28 Aug 2019 16:32:05 +0800 Subject: [PATCH 062/124] replace invoked curves api in both ecdsa and sm2, and modify junit tests in crypto-pki --- .../crypto/utils/classic/ECDSAUtils.java | 12 +- .../blockchain/crypto/utils/CSRBuilder.java | 75 +++++-- .../blockchain/crypto/utils/CertParser.java | 16 ++ .../SHA1WITHRSA2048SignatureFunctionTest.java | 145 ++++++++++++++ .../SHA1WITHRSA4096SignatureFunctionTest.java | 186 ++++++++++++++++++ .../pki/SM3WITHSM2SignatureFunctionTest.java | 80 ++++++++ .../crypto/utils/CertParserTest.java | 12 +- .../blockchain/crypto/utils/sm/SM2Utils.java | 19 +- 8 files changed, 509 insertions(+), 36 deletions(-) diff --git a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java index 097ce957..73b9e7b3 100644 --- a/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java +++ b/source/crypto/crypto-classic/src/main/java/com/jd/blockchain/crypto/utils/classic/ECDSAUtils.java @@ -7,6 +7,8 @@ import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECMultiplier; import org.bouncycastle.math.ec.ECPoint; @@ -28,12 +30,10 @@ public class ECDSAUtils { // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 // the curve equation is y^2 = x^3 + 7. - private static final X9ECParameters X9_PARAMS = SECNamedCurves.getByName("secp256k1"); - private static final ECCurve CURVE = X9_PARAMS.getCurve(); - private static final ECPoint ECDSA_G = X9_PARAMS.getG(); - private static final BigInteger ECDSA_H = X9_PARAMS.getH(); - private static final BigInteger ECDSA_N = X9_PARAMS.getN(); - private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, ECDSA_G,ECDSA_N,ECDSA_H); + private static final ECNamedCurveParameterSpec PARAMS = ECNamedCurveTable.getParameterSpec("secp256k1"); + private static final ECCurve CURVE = PARAMS.getCurve(); + private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters( + CURVE, PARAMS.getG(), PARAMS.getN(), PARAMS.getH()); //-----------------Key Generation Algorithm----------------- diff --git a/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CSRBuilder.java b/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CSRBuilder.java index 790cb7db..3de606db 100644 --- a/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CSRBuilder.java +++ b/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CSRBuilder.java @@ -16,6 +16,9 @@ import org.bouncycastle.util.encoders.Base64; import java.io.IOException; import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; /** * @author zhanglin33 @@ -25,16 +28,18 @@ import java.security.*; */ public class CSRBuilder { - private String BC = BouncyCastleProvider.PROVIDER_NAME; + private final String BC = BouncyCastleProvider.PROVIDER_NAME; private PublicKey pubKey; private PrivateKey privKey; private String algoName; + private int keyLength; public void init() { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); algoName = "SHA1withRSA"; + keyLength = 2048; KeyPairGenerator generator; try { generator = KeyPairGenerator.getInstance("RSA", BC); @@ -47,10 +52,11 @@ public class CSRBuilder { } } - public void init(String algoName, int KeyLength) { + public void init(String algoName, int keyLength) { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); - this.algoName = algoName; + this.algoName = algoName; + this.keyLength = keyLength; KeyPairGenerator generator; KeyPair keyPair; @@ -60,34 +66,69 @@ public class CSRBuilder { switch (hashAndSignature[1]) { case "RSA": { generator = KeyPairGenerator.getInstance("RSA", BC); - generator.initialize(KeyLength); - keyPair = generator.generateKeyPair(); - pubKey = keyPair.getPublic(); - privKey = keyPair.getPrivate(); + generator.initialize(keyLength); break; } case "SM2": { generator = KeyPairGenerator.getInstance("EC", BC); - if (KeyLength != 256) { + if (keyLength != 256) { throw new CryptoException("SM3withSM2 with unsupported key length [" + - KeyLength +"] in CSR!"); + keyLength +"] in CSR!"); } generator.initialize(new ECNamedCurveGenParameterSpec("sm2p256v1")); - keyPair = generator.generateKeyPair(); - pubKey = keyPair.getPublic(); - privKey = keyPair.getPrivate(); break; } default: throw new CryptoException("Unsupported algorithm [" + algoName + "] with key length [" + - KeyLength +"] in CSR!"); + keyLength +"] in CSR!"); } + keyPair = generator.generateKeyPair(); + pubKey = keyPair.getPublic(); + privKey = keyPair.getPrivate(); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) { throw new CryptoException(e.getMessage(), e); } } + public void init(String algoName, byte[] pubKeyBytes, byte[] privKeyBytes) { + + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + this.algoName = algoName; + String[] hashAndSignature = algoName.split("with"); + + X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes); + PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes); + + KeyFactory keyFactory; + + try { + switch (hashAndSignature[1]) { + case "RSA": { + keyFactory = KeyFactory.getInstance("RSA"); + privKey = keyFactory.generatePrivate(privKeySpec); + pubKey = keyFactory.generatePublic(pubKeySpec); + keyLength = (pubKey.getEncoded().length < 4096 / 8)? 2048: 4096; + break; + } + + case "SM2": { + keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); + privKey = keyFactory.generatePrivate(privKeySpec); + pubKey = keyFactory.generatePublic(pubKeySpec); + keyLength = 256; + break; + } + + default: throw new CryptoException("Unsupported algorithm [" + algoName + "] with the given key pair!"); + } + } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) { + throw new CryptoException(e.getMessage(), e); + } + + + } + public String buildRequest(String countryName, String stateName, String cityName, String organizationName, String departmentName, String domainName, String emailName) { @@ -126,4 +167,12 @@ public class CSRBuilder { public PrivateKey getPrivKey() { return privKey; } + + public String getAlgoName() { + return algoName; + } + + public int getKeyLength() { + return keyLength; + } } diff --git a/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CertParser.java b/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CertParser.java index 8267d28b..e54c17db 100644 --- a/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CertParser.java +++ b/source/crypto/crypto-pki/src/main/java/com/jd/blockchain/crypto/utils/CertParser.java @@ -25,6 +25,7 @@ public class CertParser { private String sigAlgName; private String userName; private String issuerName; + private int keyLength; private Date startTime; private Date endTime; @@ -71,6 +72,17 @@ public class CertParser { sigAlgName = userCert.getSigAlgName(); issuerName = userCert.getIssuerX500Principal().getName(); userName = userCert.getSubjectX500Principal().getName(); + + switch (sigAlgName) { + case "SM3WITHSM2": { + keyLength = 256; + break; + } + case "SHA1WITHRSA": { + keyLength = (pubKey.getEncoded().length < 4096 / 8)? 2048: 4096; + break; + } + } } // certificate string in Base64 format @@ -121,6 +133,10 @@ public class CertParser { return issuerName; } + public int getKeyLength() { + return keyLength; + } + public Date getStartTime() { return startTime; } diff --git a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA2048SignatureFunctionTest.java b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA2048SignatureFunctionTest.java index b7c7d831..64ca7eb5 100644 --- a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA2048SignatureFunctionTest.java +++ b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA2048SignatureFunctionTest.java @@ -1,9 +1,13 @@ package com.jd.blockchain.crypto.service.pki; import com.jd.blockchain.crypto.*; +import com.jd.blockchain.crypto.utils.CSRBuilder; +import com.jd.blockchain.crypto.utils.CertParser; import com.jd.blockchain.utils.io.BytesUtils; +import org.bouncycastle.util.encoders.Hex; import org.junit.Test; +import java.security.*; import java.util.Random; import static com.jd.blockchain.crypto.CryptoAlgorithm.*; @@ -125,4 +129,145 @@ public class SHA1WITHRSA2048SignatureFunctionTest { resolvedSignatureDigest.getAlgorithm()); assertArrayEquals(signatureDigestBytes, resolvedSignatureDigest.toBytes()); } + + @Test + public void testWithCSRAndCert() { + + String countryName = "CN"; + String stateName = "Beijing"; + String cityName = "Beijing"; + String organizationName = "JD.com"; + String departmentName = "Blockchain Department"; + String domainName = "ledger.jd.com"; + String emailName = "zhanglin33@jd.com"; + + + String publicKeyStr = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100c91e978897a36b" + + "30a57d265807441b0eff40c5572ecf029cd0cb2999b715edd2d5345c7b60c003075b1352629d03b943d08b25bfc5245" + + "a400f9ecbc1757394eac452d6316bf90cfcff5edfc427e277aa5266e89b1b2daa2e69dad5575515f49417c9ff332c83" + + "0dcd5537381f08e00b11123ad947bb11b18666d264ab5d8cdc92d0967fd7e0e6677746e2f01270d0594f74cda4e9d77" + + "4c6f3824499896bfb6424684af260d71b57a1366b741104fc647d9e38de055568daad60c116686e4afa1e9c83e9e30c" + + "7216e61353da2f75b2dde2c0ae870cf0ccc90490d1e22ecccbf3985d30933689847e6faf946993f930f83ac5b816075" + + "793a9a6df20727552a21be30203010001"; + + String privateKeyStr = "308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100c91e" + + "978897a36b30a57d265807441b0eff40c5572ecf029cd0cb2999b715edd2d5345c7b60c003075b1352629d03b943d08" + + "b25bfc5245a400f9ecbc1757394eac452d6316bf90cfcff5edfc427e277aa5266e89b1b2daa2e69dad5575515f49417" + + "c9ff332c830dcd5537381f08e00b11123ad947bb11b18666d264ab5d8cdc92d0967fd7e0e6677746e2f01270d0594f7" + + "4cda4e9d774c6f3824499896bfb6424684af260d71b57a1366b741104fc647d9e38de055568daad60c116686e4afa1e" + + "9c83e9e30c7216e61353da2f75b2dde2c0ae870cf0ccc90490d1e22ecccbf3985d30933689847e6faf946993f930f83" + + "ac5b816075793a9a6df20727552a21be30203010001028201003975637e93300d8a2ee573e47762f6461117cca96d46" + + "982cfc1be6ed3318f142a845d6dc2ad680a703d69fd56b9d6a3b1d23fbeb6f63c4e303736f2bfca5c2585631825f494" + + "534783d6f3a07bd0b5efbcaa1faf7814ac9118c8d8820f4be9a8b0ac6db819fc86b538bf284369d9f009a667668a82d" + + "224f7122041eddb492ef5b02d462aa11bc550bf3785a5d7538043586cfb0cd5c313a4746f22a5d4a89a412b279a7517" + + "176b1858a9677a1a60605aa0b2a7d260cf2e9e23a0e67c4a23ac046c62973239e84874c3695cc3f34073a62bd50b597" + + "ee06092a93fa6e41303ab4d2293ffad4c8db06e6516ae0a26a4d681305c3b7d535b540a638ca6cff1ce483750281810" + + "0e39967c5b287bd319b448255a1033591c74f4d688eb71f89a674cdb1be999e9abcf40e6b7f0bb3054262d7625002da" + + "7d34e56055ee130b66d682a0b9f8ea240a7e2dd1ff3f4b32f3bd352ba0a5dffc0315da34922493c267597787222213b" + + "f87d99fbd2a809c2647a1937cd1e303d746373db7c409a2ef33f8c06bb838bc612702818100e2374c71db5f0b4a199f" + + "f9d646a1785a84383a14aceb506f515b380c69ff45b51d0e941f55f0a234d8a3ee30bed656142bb5b985e462c44d234" + + "cfd424ecd6ca5fc70503862978ffe42b45bd698a99091d6fc65e2d85652e0ef56c52e8e05a6c5e879922c1d794e22f3" + + "51998217b5c6637a6abb716bf90cd1f23a2eedcdfface50281805d28a872224e2f2183e539d7e4ccd47b73f240c4004" + + "e72493c69e8dbcd2141eb22565f249edee20ad00e770c95a5655b0470b2cad964d030eab293292bfa62802cff824a10" + + "d52de8d8545024346106dd186fb53ef05bcea1d0dbfce2fac1cc8ec583fdc0ccdd9d498a983cea081ac55dc734aae84" + + "1ed802d6caf0e285c88b6d702818068b2a752daf1364c6967bd3e0b1a98956c3489cd1feb19232c4847bc97226aa4d4" + + "79f6dc39ee51649c0fe321f471470db6dd38ac5b73caded8c3bd437f2d5c67c65a450693bb0a0de7d989d7dc783e4d0" + + "16f77c871d02233b1123bd8bc2aa97157934cafd6445a819a93ddb4743cd141215b5cbdb5f7629398c48d0bcb17d671" + + "028181009282554329b89bcb2236a7e2a5bff53627e3ca7cc792d65236085741bc62a3f5767654722af561eff175664" + + "1af60e3d3959f63f0fec00cb29443ffca4ff751a76ecc6fa192ebe08ec9f643b9bb0d11bc90c1e850f0528ef223ea5f" + + "e4b4c107cc3a9eb26e6b84d74d87acbf7cc760cd788cbc30f95f8399077b25cdd924604c01"; + + String expectedCSR = "MIIC4jCCAcoCAQAwgZwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW" + + "5nMQ8wDQYDVQQKDAZKRC5jb20xHjAcBgNVBAsMFUJsb2NrY2hhaW4gRGVwYXJ0bWVudDEWMBQGA1UEAwwNbGVkZ2VyLmpkL" + + "mNvbTEgMB4GCSqGSIb3DQEJARYRemhhbmdsaW4zM0BqZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ" + + "HpeIl6NrMKV9JlgHRBsO/0DFVy7PApzQyymZtxXt0tU0XHtgwAMHWxNSYp0DuUPQiyW/xSRaQA+ey8F1c5TqxFLWMWv5DPz" + + "/Xt/EJ+J3qlJm6JsbLaouadrVV1UV9JQXyf8zLIMNzVU3OB8I4AsREjrZR7sRsYZm0mSrXYzcktCWf9fg5md3RuLwEnDQWU" + + "90zaTp13TG84JEmYlr+2QkaEryYNcbV6E2a3QRBPxkfZ443gVVaNqtYMEWaG5K+h6cg+njDHIW5hNT2i91st3iwK6HDPDMy" + + "QSQ0eIuzMvzmF0wkzaJhH5vr5Rpk/kw+DrFuBYHV5Oppt8gcnVSohvjAgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEALKHw" + + "BrlppbN+CSJwHxO99rv2XVD7L8lwwiMqdMzoNHxZUoob4AcGS3Iyxy6obX8fZh7DCA4nN0AmvrJCmWD3hCLclWKlwXTwvFe" + + "WIeB4xEB9MVPV6/Hs/QaI9Rjhd+O/Alfzov8y+KyYHEWmwxHj2RtQm1ZeulELzMvRjsqP+m5tOM1hLnPMU+AF1tZ9Fc7Jbm" + + "7Dwh6TAPpmaH1aVbHsnlZyCp8K4nMozVogcXJqg3qsbeJ6c/TofL0fURqiTJxLKMC0aD0TwVcNwDWEmc8cpqGheG1g6UF5l" + + "QBpeR2Br4f3LXJgr1Op1RxRy6I+X7h1IL38Q3jAOoU4y04O/+an7g=="; + + String expectedUserCert = "MIIERjCCAy6gAwIBAgIFIChmYJgwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVB" + + "AoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4X" + + "DTE5MDgyODA2MzEyNVoXDTIxMDgyODA2MzEyNVoweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTE" + + "RMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTElMCMGA1UEAxQcMDUxQHpoYW5nbGluIUBaMTg2MT" + + "IyMjkyOTVANTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkel4iXo2swpX0mWAdEGw7/QMVXLs8CnNDLKZm3F" + + "e3S1TRce2DAAwdbE1JinQO5Q9CLJb/FJFpAD57LwXVzlOrEUtYxa/kM/P9e38Qn4neqUmbomxstqi5p2tVXVRX0lBfJ/zMs" + + "gw3NVTc4HwjgCxESOtlHuxGxhmbSZKtdjNyS0JZ/1+DmZ3dG4vAScNBZT3TNpOnXdMbzgkSZiWv7ZCRoSvJg1xtXoTZrdBE" + + "E/GR9njjeBVVo2q1gwRZobkr6HpyD6eMMchbmE1PaL3Wy3eLArocM8MzJBJDR4i7My/OYXTCTNomEfm+vlGmT+TD4OsW4Fg" + + "dXk6mm3yBydVKiG+MCAwEAAaOB9TCB8jAfBgNVHSMEGDAWgBT8C7xEmg4xoYOpgYcnHgVCxr9W+DBIBgNVHSAEQTA/MD0GC" + + "GCBHIbvKgECMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20uY24vdXMvdXMtMTUuaHRtMDoGA1UdHwQzMDEw" + + "L6AtoCuGKWh0dHA6Ly8yMTAuNzQuNDIuMy9PQ0ExMS9SU0EvY3JsMjY2NTAuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQ" + + "UOTy45HymVDivPwA83lRoMpShasQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBBQUAA4IBAQ" + + "A7NFkDMV1mRV/07DJUdZJzvkm+QihhpLp7D2dUblCCEt0jjY5RdLiWG7HvJnlPTThiSkiejEO0Fy1cQzA5jYRhwp+70X8X9" + + "bt5jEBP/V4PyRXKKEvKZMdppLIeVI6rZk/gJzPh2FQYv3qWaINilxLOBP8Qa0kdMBwo6D6/MYwnSGv5zP4NLFUysLUJiKoM" + + "lAzEQSPNnkYRX6nogpkdN91/xgH3GA7fiihrjm5oxMAupCli9LQqvlUvRtv5EKIN9c+ixCAYFagG9IIjMDXbDne77n15i01" + + "420N8sjfTlr9v3W0v1gBSzjzFOT+TTTUtrjfO/Vm8iqq+z22QKIXgYjSF"; + + String issuerCert = + "-----BEGIN CERTIFICATE-----\n" + + "MIIDzzCCAregAwIBAgIKUalCR1Mt5ZSK8jANBgkqhkiG9w0BAQUFADBZMQswCQYD\n" + + "VQQGEwJDTjEwMC4GA1UEChMnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24g\n" + + "QXV0aG9yaXR5MRgwFgYDVQQDEw9DRkNBIFRFU1QgQ1MgQ0EwHhcNMTIwODI5MDU1\n" + + "NDM2WhcNMzIwODI0MDU1NDM2WjBZMQswCQYDVQQGEwJDTjEwMC4GA1UEChMnQ2hp\n" + + "bmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRgwFgYDVQQDEw9D\n" + + "RkNBIFRFU1QgT0NBMTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8\n" + + "jn0n8Fp6hcRdACdn1+Y6GAkC6KGgNdKyHPrmsdmhCjnd/i4qUFwnG8cp3D4lFw1G\n" + + "jmjSO5yVYbik/NbS6lbNpRgTK3fDfMFvLJpRIC+IFhG9SdAC2hwjsH9qTpL9cK2M\n" + + "bSdrC6pBdDgnbzaM9AGBF4Y6vXhj5nah4ZMsBvDp19LzXjlGuTPLuAgv9ZlWknsd\n" + + "RN70PIAmvomd10uqX4GIJ4Jq/FdKXOLZ2DNK/NhRyN6Yq71L3ham6tutXeZow5t5\n" + + "0254AnUlo1u6SeH9F8itER653o/oMLFwp+63qXAcqrHUlOQPX+JI8fkumSqZ4F2F\n" + + "t/HfVMnqdtFNCnh5+eIBAgMBAAGjgZgwgZUwHwYDVR0jBBgwFoAUdN7FjQp9EBqq\n" + + "aYNbTSHOhpvMcTgwDAYDVR0TBAUwAwEB/zA4BgNVHR8EMTAvMC2gK6AphidodHRw\n" + + "Oi8vMjEwLjc0LjQyLjMvdGVzdHJjYS9SU0EvY3JsMS5jcmwwCwYDVR0PBAQDAgEG\n" + + "MB0GA1UdDgQWBBT8C7xEmg4xoYOpgYcnHgVCxr9W+DANBgkqhkiG9w0BAQUFAAOC\n" + + "AQEAb7W0K9fZPA+JPw6lRiMDaUJ0oh052yEXreMBfoPulxkBj439qombDiFggRLc\n" + + "3g8wIEKzMOzOKXTWtnzYwN3y/JQSuJb/M1QqOEEM2PZwCxI4AkBuH6jg03RjlkHg\n" + + "/kTtuIFp9ItBCC2/KkKlp0ENfn4XgVg2KtAjZ7lpyVU0LPnhEqqUVY/xthjlCSa7\n" + + "/XHNStRxsfCTIBUWJ8n2FZyQhfV/UkMNHDBIiJR0v6C4Ai0/290WvbPEIAq+03Si\n" + + "fsHzBeA0C8lP5VzfAr6wWePaZMCpStpLaoXNcAqReKxQllElOqAhRxC5VKH+rnIQ\n" + + "OMRZvB7FRyE9IfwKApngcZbA5g==\n" + + "-----END CERTIFICATE-----"; + + byte[] rawPublicKeyBytes = Hex.decode(publicKeyStr); + byte[] rawPrivateKeyBytes = Hex.decode(privateKeyStr); + + CSRBuilder builder = new CSRBuilder(); + builder.init("SHA1withRSA", rawPublicKeyBytes, rawPrivateKeyBytes); + + String csr = builder.buildRequest(countryName,stateName,cityName, + organizationName,departmentName,domainName, + emailName); + + assertEquals(expectedCSR,csr); + + CertParser parser = new CertParser(); + parser.parse(expectedUserCert,issuerCert); + + PublicKey rawPublicKeyInCert = parser.getPubKey(); + // check that the public key in inputs and the public key in certificate are consistent + assertArrayEquals(rawPublicKeyBytes, rawPublicKeyInCert.getEncoded()); + + String algoName = parser.getSigAlgName(); + int keyLength = parser.getKeyLength(); + String length = String.valueOf(keyLength); + String algo = (algoName.contains("RSA")? (algoName + length).toUpperCase(): algoName.toUpperCase()); + + CryptoAlgorithm algorithm = Crypto.getAlgorithm(algo); + assertNotNull(algorithm); + SignatureFunction signatureFunction = Crypto.getSignatureFunction(algorithm); + + PubKey pubKey = new PubKey(algorithm, rawPublicKeyBytes); + PrivKey privKey = new PrivKey(algorithm, rawPrivateKeyBytes); + + // signTest + byte[] data = new byte[1024]; + Random random = new Random(); + random.nextBytes(data); + + SignatureDigest signature = signatureFunction.sign(privKey, data); + assertTrue(signatureFunction.verify(signature, pubKey, data)); + } } diff --git a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA4096SignatureFunctionTest.java b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA4096SignatureFunctionTest.java index 9444771b..466e245d 100644 --- a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA4096SignatureFunctionTest.java +++ b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SHA1WITHRSA4096SignatureFunctionTest.java @@ -1,9 +1,13 @@ package com.jd.blockchain.crypto.service.pki; import com.jd.blockchain.crypto.*; +import com.jd.blockchain.crypto.utils.CSRBuilder; +import com.jd.blockchain.crypto.utils.CertParser; import com.jd.blockchain.utils.io.BytesUtils; +import org.bouncycastle.util.encoders.Hex; import org.junit.Test; +import java.security.PublicKey; import java.util.Random; import static com.jd.blockchain.crypto.CryptoAlgorithm.ASYMMETRIC_KEY; @@ -125,4 +129,186 @@ public class SHA1WITHRSA4096SignatureFunctionTest { resolvedSignatureDigest.getAlgorithm()); assertArrayEquals(signatureDigestBytes, resolvedSignatureDigest.toBytes()); } + + @Test + public void testWithCSRAndCert() { + + String countryName = "CN"; + String stateName = "Beijing"; + String cityName = "Beijing"; + String organizationName = "JD.com"; + String departmentName = "Blockchain Department"; + String domainName = "ledger.jd.com"; + String emailName = "zhanglin33@jd.com"; + + + String publicKeyStr = "30820222300d06092a864886f70d01010105000382020f003082020a0282020100b40edee83b609e" + + "aa001f2496f95d2f3302513306ec9a8e7fce0d2e141fce7ee357a7465314c3f5f4b08cb6c95803c368ddbfedba483cb" + + "5c45914037ceee5783fc971a12ef9b0e4158dc379b59499eee629324a9beb350c4c10e50837345be128b91f43a03381" + + "758bbefe41c45712e4b5fdd5bde780167283706b24e37dd753db65b4c6b3e49cd8be825665d9a29a24b77e76473df02" + + "2327873555aa33ba2ffcc766cbefedc46ec868d10f817822540eaf5754c074dd6d428355ce24a058a4c9ce41e48aad5" + + "92e7955cf93d779d03d3acf25ae271346a9e4255e4ed902ae016032b04efbee98f43cd767653e089b37540e537aede9" + + "dbc04f8f1a858b2764b9eedac80b6a8da5ff02aab4be94e071c70718fde7227cdefec31600a1c55bac16f4de9dea8ab" + + "7824c1ec783b818dfe005f040a3f6872b1c7a6c31a66c1b06eb8d872a23d1b4fdadf9eed58f93b2a2bc145638a79a81" + + "904d39b22128ced18a2556c21888ed1ec8ad59bd6764f1ea16eb7f3574c53166f0827b5072d23017bd725adeb63eeb2" + + "29a4e78d4d7426e936753902bb51e3cd90630314f4ab41272a9e36cb668b2ba9c2ebc02e9ef0c377c88482e839f2f4d" + + "5c8efcfbe1280e52c6bdf80aa487ae03ff9dd9fd981f78172bc1141ce6031b0b8915658d830c696662507d3cf4ddba8" + + "7daabf97c15cbef58b15e84f16f879328c7c65076d94fc6b4514549831850203010001"; + + String privateKeyStr = "30820942020100300d06092a864886f70d01010105000482092c308209280201000282020100b40e" + + "dee83b609eaa001f2496f95d2f3302513306ec9a8e7fce0d2e141fce7ee357a7465314c3f5f4b08cb6c95803c368ddb" + + "fedba483cb5c45914037ceee5783fc971a12ef9b0e4158dc379b59499eee629324a9beb350c4c10e50837345be128b9" + + "1f43a03381758bbefe41c45712e4b5fdd5bde780167283706b24e37dd753db65b4c6b3e49cd8be825665d9a29a24b77" + + "e76473df022327873555aa33ba2ffcc766cbefedc46ec868d10f817822540eaf5754c074dd6d428355ce24a058a4c9c" + + "e41e48aad592e7955cf93d779d03d3acf25ae271346a9e4255e4ed902ae016032b04efbee98f43cd767653e089b3754" + + "0e537aede9dbc04f8f1a858b2764b9eedac80b6a8da5ff02aab4be94e071c70718fde7227cdefec31600a1c55bac16f" + + "4de9dea8ab7824c1ec783b818dfe005f040a3f6872b1c7a6c31a66c1b06eb8d872a23d1b4fdadf9eed58f93b2a2bc14" + + "5638a79a81904d39b22128ced18a2556c21888ed1ec8ad59bd6764f1ea16eb7f3574c53166f0827b5072d23017bd725" + + "adeb63eeb229a4e78d4d7426e936753902bb51e3cd90630314f4ab41272a9e36cb668b2ba9c2ebc02e9ef0c377c8848" + + "2e839f2f4d5c8efcfbe1280e52c6bdf80aa487ae03ff9dd9fd981f78172bc1141ce6031b0b8915658d830c696662507" + + "d3cf4ddba87daabf97c15cbef58b15e84f16f879328c7c65076d94fc6b451454983185020301000102820200063fece" + + "3452a579f817513454d3efb842afcac077dbf689a4d89de13533e4cdfb1bb6be0b6dc0d65b29a13bf1dd7b598e67782" + + "b6204b4128e149a54c59136c6ed45c661296169a78180d54a46595c939c26ccd33a7c095de6f08b01610726ef885a26" + + "cebbad5efc14bbe1204d15be5c5de5b64a5cc279b4e6e20bded4a8126973b2ac0e9de11c6a1282f7d060693909a30e0" + + "c49cc500bedd38ed99c18830a26dd39f772aabf527410d54ed338db022d674f21f1332d3b5d5f67234a58a97300d130" + + "aed0d46effc2b4e4895665934188d0c75749e26ca5b97645957989530657b332b4ef202b3d70fe2e07d0d526240fbe1" + + "68e320357be0f54e18008a233a8137e23ca1c54074b31c57eebee49bf2f1c66ea97a2c846a8d26680b97e1240d6763e" + + "60bcd8d696c806362b18bf0504a39e4d465a9548091dbe97c36f6d8e038d95a72c0a88ff524dc81fe0ed2afd69f4251" + + "1a90687a4c632c812234a19a7312b2dea3fcd4515800eca700733b0f83509184fe8d3cd21385f0ef0cec37c433d354f" + + "aed61662a62902c8708c81e2af20898f649c1bafd600baa0409c943cf82bc90ea20a8972da7ab3a252f4f08df2509de" + + "e1dacfe787eef4d60c0c92ec7a3c6277d7be39deed7704ac721d8efd138a410d632a32535142cf977b09d9fb680bc96" + + "c538c00440d5e1ed71f8510e6524e564d69a24d03f1a9a0c326b421e32550801c890282010100e8d72f8f1fbeada724" + + "3e6df25337a2e7fc43e3d4f39877f89abb3b5e453f20f339a1f35e0a2847122f0bd835e6b43fd276f447da04f85cfca" + + "0fa8bf49b313239e36f9595ef1bfb9b8247bf01cc407dfc444421161a5b2e96d4d1d90cea185945e9447d21a2d8a461" + + "f43dff9ba58043feeb49552a3bf2472eea59b2aa8631048c76a15898065be0ef957d4802c827e30339d66ad7aed5851" + + "a5896f12d33f45800dcb10859531c590af7a75e9fa81f1d937a287fb6b066d58720584af2ae161e083681655ae77f48" + + "34bfe0accf1d12bbdd8c2644f78b207cfcb2dedf9fe7d29e1ae5c2a5623f5a1770db27d2636450c79fd39bce39f009b" + + "598e0298e1f77bf8d3d0282010100c5f7af9258a42da93204cecb71c544397bc16691cf0d41308fbd3b88eca7d101d1" + + "377dfe3b7e66707d3e5543b21033ab6c5b0de577740c6e335f512eeb2a839c3f2baeca4ce80e3fbd8fb93fa983e1719" + + "5c5fee63bc163df334a80f2767871e3434adc0bc7030dea44fce414a08da7e918e6f030cfb20b2d29033e9ef1be3e08" + + "ef1f50df0c9325a20ff01d0b7a06761403ae3498aea8bad93110d61a6386d470e630990029e2cb098de40fafa330911" + + "6c3c6de180b7fa41ae14553e891148ab53e970ce372b1777826983baaccac08290e343761d8daa2505f1dc45b8511a3" + + "6e2d0909237be9ae7ebbaa00b31de1224f32959e4e6f3428140ca8e1e5580789e902820100674075609c8d2be880940" + + "6a17cf1a1160ab1f8684895862e023fa0f60ef30da38e1d1914cca04bd3ee74ec2e0ade47a70705108fc7c0734bbbff" + + "1eed1b9cd74f00624d0d2df954bc032bd9b1ec6774f6d736f70d1c26ef2407bffee65130f6f59f99b57ba3013af40d2" + + "12926565fe8c734835276e61a6c228bddb6f3138acd1f94c3bbcbbe9623cb5a9931c3ba0aa60a9a2d5137cfd9f3aa59" + + "3aa63c8b5b8162f07ab8df1391f092827bffe400e3bb73d8a9f8e88495357f3482b2c9a7153bc01c9b88dca4e7b6975" + + "db73e2aa213daa7462cfa4c63afc67d30bcd0a1d2657da323dc0b06e45d09240cab3e0ac1436922a0ede8a79ca0519d" + + "375a7621d23269690282010100bbc1299716d2bf2b94f0d260494ada65da6596adfb3d8af24fa11d71c36175eccf4c5" + + "e065cce88c16f474afea546907aa88dc3243aa2a9976ac99fe96bc82a8269b738534d9558ce432ea87724829bb26a66" + + "1a56a99dc4e6cf727dd17762cc40ca759934e24e9747f49e14832bb2ade97960adb4dd86f2eaa5d719f10d3d6d00742" + + "9b33d98638671a9c40507f9775f4da41ff86a465c68b9ccbb371458086c3b9755c8064bb378f55ac94dc73a72b96869" + + "cd969e1f69b36e7af091a024d8e2a4faf3af999811904937f171c58fd028fd272786cf1a286180f074fee1fdd6b8b5a" + + "9a8c42e0f3b95ef4474fbace54dbc887865467b0524e64dfda3be7b117e34e1028201001a7e846231c34400c1f704e7" + + "fec6e46c87d61f269b71942bc9cc72005ba30eea3db5d0e5b0b754f7a00b96c883399982b5b3a9916765c5ed9129e44" + + "a791ce6892f85758c637bc040da132b8f0cd0ac36ba3aae9334414a77f0b50c0aa03643bfd59b9a621342a4807e46fc" + + "52a5a12fd3ff6762e181c40c2baf3653043c836b14700463af5d68a2a2897897edb5f217d655d5bcd24e7910062f40e" + + "00f19e2f94b45efbbf60cbf734830756baf72dcfca8d2858ca5df63336999474945f3744a96e4ce23f9067bbca849ef" + + "1048cba3a4aad73ed73b0fcd8c2e9f6d06aa768548d7107aa58d9d296f853543f6569e4dd33270540d983460773794f" + + "e9196fc5a54cd"; + + String expectedCSR = "MIIE4jCCAsoCAQAwgZwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW" + + "5nMQ8wDQYDVQQKDAZKRC5jb20xHjAcBgNVBAsMFUJsb2NrY2hhaW4gRGVwYXJ0bWVudDEWMBQGA1UEAwwNbGVkZ2VyLmpkL" + + "mNvbTEgMB4GCSqGSIb3DQEJARYRemhhbmdsaW4zM0BqZC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0" + + "Dt7oO2CeqgAfJJb5XS8zAlEzBuyajn/ODS4UH85+41enRlMUw/X0sIy2yVgDw2jdv+26SDy1xFkUA3zu5Xg/yXGhLvmw5BW" + + "Nw3m1lJnu5ikySpvrNQxMEOUINzRb4Si5H0OgM4F1i77+QcRXEuS1/dW954AWcoNwayTjfddT22W0xrPknNi+glZl2aKaJL" + + "d+dkc98CIyeHNVWqM7ov/Mdmy+/txG7IaNEPgXgiVA6vV1TAdN1tQoNVziSgWKTJzkHkiq1ZLnlVz5PXedA9Os8lricTRqn" + + "kJV5O2QKuAWAysE777pj0PNdnZT4ImzdUDlN67enbwE+PGoWLJ2S57trIC2qNpf8CqrS+lOBxxwcY/ecifN7+wxYAocVbrB" + + "b03p3qireCTB7Hg7gY3+AF8ECj9ocrHHpsMaZsGwbrjYcqI9G0/a357tWPk7KivBRWOKeagZBNObIhKM7RiiVWwhiI7R7Ir" + + "Vm9Z2Tx6hbrfzV0xTFm8IJ7UHLSMBe9clretj7rIppOeNTXQm6TZ1OQK7UePNkGMDFPSrQScqnjbLZosrqcLrwC6e8MN3yI" + + "SC6Dny9NXI78++EoDlLGvfgKpIeuA/+d2f2YH3gXK8EUHOYDGwuJFWWNgwxpZmJQfTz03bqH2qv5fBXL71ixXoTxb4eTKMf" + + "GUHbZT8a0UUVJgxhQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggIBAKowJTG9mZLbDHRndmDUd7PWrq1AiCdk6DStV39B/REF" + + "OrMYcW9X4Ak69fhXQDtD4gu2lKtumDY0oJ8xleM2FHUSzdooTWb7P/QtCIBy27sH6nvlefRWi7ngSTNJlDmwgr0l07UzZU1" + + "Yl/ZULn0XlNAFal+qt+4ZNdiulNwkL6IofXV/8vqOeQw5iICDBYHItyY/mqD8IIaClVd8yNpEuE/W9GdJIDNXQjpug+BxL/" + + "FbjAs6P3ZzJboedJE5urbru2jjb7atl3w/eDo4r6+XNSD8d1PgVmVhzN2WpUWsZNeH2jd9AA6436GjsBssgSRKEc3FTJ+lO" + + "0Jw2d8GewXXkIv8CT4L3BFwqZhGQt27wlb87+W4dIC05JIaJx52869dvu1ky1CL73GROXeS8rVYJsPwVmK2xy3QTaeHGEQh" + + "kiVNeV1cc3mll2z7fgbkjPD8zDNBWUdzSXQMzecY1CBD02iz6LaHfvkI7gXXoiIf1cJrnLtYhv3lG45jKr0E45/Wn7oXmYk" + + "RM4/zHO4KnY7Pp3b3QTgkRJaPnZiG7aiCrdnTIopDSGpTWSPWDLjgwgaCPPz5Pd2Fk+SAE98o7Cu8O0vxMASpd4liaedASE" + + "n6hnrvcTkFLLG2ecZzJZ0aEqPi4es0FqlqVZrLPH2UYpECgfhkGQQKAx4eRTAZFhz1GSkd"; + + String expectedUserCert = "MIIFRjCCBC6gAwIBAgIFIChmYWIwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVB" + + "AoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4X" + + "DTE5MDgyODA4MDAxMVoXDTIxMDgyODA4MDAxMVoweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTE" + + "RMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTElMCMGA1UEAxQcMDUxQHpoYW5nbGluIUBaMTg2MT" + + "IyMjkyOTVANzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQO3ug7YJ6qAB8klvldLzMCUTMG7JqOf84NLhQfz" + + "n7jV6dGUxTD9fSwjLbJWAPDaN2/7bpIPLXEWRQDfO7leD/JcaEu+bDkFY3DebWUme7mKTJKm+s1DEwQ5Qg3NFvhKLkfQ6Az" + + "gXWLvv5BxFcS5LX91b3ngBZyg3BrJON911PbZbTGs+Sc2L6CVmXZopokt352Rz3wIjJ4c1Vaozui/8x2bL7+3Ebsho0Q+Be" + + "CJUDq9XVMB03W1Cg1XOJKBYpMnOQeSKrVkueVXPk9d50D06zyWuJxNGqeQlXk7ZAq4BYDKwTvvumPQ812dlPgibN1QOU3rt" + + "6dvAT48ahYsnZLnu2sgLao2l/wKqtL6U4HHHBxj95yJ83v7DFgChxVusFvTeneqKt4JMHseDuBjf4AXwQKP2hyscemwxpmw" + + "bBuuNhyoj0bT9rfnu1Y+TsqK8FFY4p5qBkE05siEoztGKJVbCGIjtHsitWb1nZPHqFut/NXTFMWbwgntQctIwF71yWt62Pu" + + "simk541NdCbpNnU5ArtR482QYwMU9KtBJyqeNstmiyupwuvALp7ww3fIhILoOfL01cjvz74SgOUsa9+Aqkh64D/53Z/Zgfe" + + "BcrwRQc5gMbC4kVZY2DDGlmYlB9PPTduofaq/l8FcvvWLFehPFvh5Mox8ZQdtlPxrRRRUmDGFAgMBAAGjgfUwgfIwHwYDVR" + + "0jBBgwFoAU/Au8RJoOMaGDqYGHJx4FQsa/VvgwSAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwO" + + "i8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh0bTA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vMjEwLjc0LjQyLjMvT0NB" + + "MTEvUlNBL2NybDI2NjU1LmNybDALBgNVHQ8EBAMCA+gwHQYDVR0OBBYEFBl1Gmb89bqbEKyFcTU3eOY/5NmKMB0GA1UdJQQ" + + "WMBQGCCsGAQUFBwMCBggrBgEFBQcDBDANBgkqhkiG9w0BAQUFAAOCAQEADEU//9rnWN1s3/ariMHIUmgzRUdz3fWYiDRzGC" + + "mcnnETlXDstGmoYmwCM+QwHw6cyKXkwkg9zV7c7CgM471ZuF00gq115d432Ps3RXGCpfQ2fn3gs+91ky/YqJOOyBb8KL0IP" + + "r/Zh56/y3XX0gORn4GLqaj+oVZrFcmKrPtVhySlXNiD5BRMq39mUbuLBweGsgNVQ9VxiWc8ZBGjlJ6OVsngbvWrtl3zgkKb" + + "X9lhr8Bxq3G+jOV8jvr1Dkn4a65g2TWcFquxmPvRc5UwN29CimbC7RViCL3Jp+zrGasqbjycuqu5eSXb6gG4/aV0/K9yn5k" + + "YlZMIBlbsXSEi5J26pg=="; + + String issuerCert = + "-----BEGIN CERTIFICATE-----\n" + + "MIIDzzCCAregAwIBAgIKUalCR1Mt5ZSK8jANBgkqhkiG9w0BAQUFADBZMQswCQYD\n" + + "VQQGEwJDTjEwMC4GA1UEChMnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24g\n" + + "QXV0aG9yaXR5MRgwFgYDVQQDEw9DRkNBIFRFU1QgQ1MgQ0EwHhcNMTIwODI5MDU1\n" + + "NDM2WhcNMzIwODI0MDU1NDM2WjBZMQswCQYDVQQGEwJDTjEwMC4GA1UEChMnQ2hp\n" + + "bmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRgwFgYDVQQDEw9D\n" + + "RkNBIFRFU1QgT0NBMTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8\n" + + "jn0n8Fp6hcRdACdn1+Y6GAkC6KGgNdKyHPrmsdmhCjnd/i4qUFwnG8cp3D4lFw1G\n" + + "jmjSO5yVYbik/NbS6lbNpRgTK3fDfMFvLJpRIC+IFhG9SdAC2hwjsH9qTpL9cK2M\n" + + "bSdrC6pBdDgnbzaM9AGBF4Y6vXhj5nah4ZMsBvDp19LzXjlGuTPLuAgv9ZlWknsd\n" + + "RN70PIAmvomd10uqX4GIJ4Jq/FdKXOLZ2DNK/NhRyN6Yq71L3ham6tutXeZow5t5\n" + + "0254AnUlo1u6SeH9F8itER653o/oMLFwp+63qXAcqrHUlOQPX+JI8fkumSqZ4F2F\n" + + "t/HfVMnqdtFNCnh5+eIBAgMBAAGjgZgwgZUwHwYDVR0jBBgwFoAUdN7FjQp9EBqq\n" + + "aYNbTSHOhpvMcTgwDAYDVR0TBAUwAwEB/zA4BgNVHR8EMTAvMC2gK6AphidodHRw\n" + + "Oi8vMjEwLjc0LjQyLjMvdGVzdHJjYS9SU0EvY3JsMS5jcmwwCwYDVR0PBAQDAgEG\n" + + "MB0GA1UdDgQWBBT8C7xEmg4xoYOpgYcnHgVCxr9W+DANBgkqhkiG9w0BAQUFAAOC\n" + + "AQEAb7W0K9fZPA+JPw6lRiMDaUJ0oh052yEXreMBfoPulxkBj439qombDiFggRLc\n" + + "3g8wIEKzMOzOKXTWtnzYwN3y/JQSuJb/M1QqOEEM2PZwCxI4AkBuH6jg03RjlkHg\n" + + "/kTtuIFp9ItBCC2/KkKlp0ENfn4XgVg2KtAjZ7lpyVU0LPnhEqqUVY/xthjlCSa7\n" + + "/XHNStRxsfCTIBUWJ8n2FZyQhfV/UkMNHDBIiJR0v6C4Ai0/290WvbPEIAq+03Si\n" + + "fsHzBeA0C8lP5VzfAr6wWePaZMCpStpLaoXNcAqReKxQllElOqAhRxC5VKH+rnIQ\n" + + "OMRZvB7FRyE9IfwKApngcZbA5g==\n" + + "-----END CERTIFICATE-----"; + + byte[] rawPublicKeyBytes = Hex.decode(publicKeyStr); + byte[] rawPrivateKeyBytes = Hex.decode(privateKeyStr); + + CSRBuilder builder = new CSRBuilder(); + builder.init("SHA1withRSA", rawPublicKeyBytes, rawPrivateKeyBytes); + + String csr = builder.buildRequest(countryName,stateName,cityName, + organizationName,departmentName,domainName, + emailName); + + assertEquals(expectedCSR,csr); + + CertParser parser = new CertParser(); + parser.parse(expectedUserCert,issuerCert); + + PublicKey rawPublicKeyInCert = parser.getPubKey(); + // check that the public key in inputs and the public key in certificate are consistent + assertArrayEquals(rawPublicKeyBytes, rawPublicKeyInCert.getEncoded()); + + String algoName = parser.getSigAlgName(); + int keyLength = parser.getKeyLength(); + String length = String.valueOf(keyLength); + String algo = (algoName.contains("RSA")? (algoName + length).toUpperCase(): algoName.toUpperCase()); + + CryptoAlgorithm algorithm = Crypto.getAlgorithm(algo); + assertNotNull(algorithm); + SignatureFunction signatureFunction = Crypto.getSignatureFunction(algorithm); + + PubKey pubKey = new PubKey(algorithm, rawPublicKeyBytes); + PrivKey privKey = new PrivKey(algorithm, rawPrivateKeyBytes); + + // signTest + byte[] data = new byte[1024]; + Random random = new Random(); + random.nextBytes(data); + + SignatureDigest signature = signatureFunction.sign(privKey, data); + assertTrue(signatureFunction.verify(signature, pubKey, data)); + } } diff --git a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SM3WITHSM2SignatureFunctionTest.java b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SM3WITHSM2SignatureFunctionTest.java index 0b8063a9..54969745 100644 --- a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SM3WITHSM2SignatureFunctionTest.java +++ b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/service/pki/SM3WITHSM2SignatureFunctionTest.java @@ -1,9 +1,13 @@ package com.jd.blockchain.crypto.service.pki; import com.jd.blockchain.crypto.*; +import com.jd.blockchain.crypto.utils.CSRBuilder; +import com.jd.blockchain.crypto.utils.CertParser; import com.jd.blockchain.utils.io.BytesUtils; +import org.bouncycastle.util.encoders.Hex; import org.junit.Test; +import java.security.PublicKey; import java.util.Random; import static com.jd.blockchain.crypto.CryptoAlgorithm.ASYMMETRIC_KEY; @@ -270,4 +274,80 @@ public class SM3WITHSM2SignatureFunctionTest { resolvedSignatureDigest.getAlgorithm()); assertArrayEquals(signatureDigestBytes, resolvedSignatureDigest.toBytes()); } + + + @Test + public void testWithCSRAndCert() { + + String publicKeyStr = "3059301306072a8648ce3d020106082a811ccf5501822d03420004aa6586478be879504fdd02892f" + + "b4cf2bdb3d96a316f41ff7bcadfabef4ea836678984f9ba6931a609391426ca7164592f5fccd062be56fc32b3eec3a5" + + "0400971"; + + String privateKeyStr = "308193020100301306072a8648ce3d020106082a811ccf5501822d0479307702010104207abd2953" + + "84f193abe4f3715c26bc15225061f007131755d94ca12c7e0ce0b3a0a00a06082a811ccf5501822da14403420004aa6" + + "586478be879504fdd02892fb4cf2bdb3d96a316f41ff7bcadfabef4ea836678984f9ba6931a609391426ca7164592f5" + + "fccd062be56fc32b3eec3a50400971"; + + String expectedUserCert = "MIICwjCCAmWgAwIBAgIFIChnMlIwDAYIKoEcz1UBg3UFADBdMQswCQYDVQQGEwJDTjEwMC4GA1UEC" + + "gwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRwwGgYDVQQDDBNDRkNBIFRFU1QgU00yIE9DQTEx" + + "MB4XDTE5MDgyODA4MTUzNloXDTIxMDgyODA4MTUzNloweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoMD0NGQ0EgVEVTVCBPQ0E" + + "xMTERMA8GA1UECwwITG9jYWwgUkExFTATBgNVBAsMDEluZGl2aWR1YWwtMTElMCMGA1UEAwwcMDUxQHpoYW5nbGluIUBaMT" + + "g2MTIyMjkyOTVAODBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABKplhkeL6HlQT90CiS+0zyvbPZajFvQf97yt+r706oNme" + + "JhPm6aTGmCTkUJspxZFkvX8zQYr5W/DKz7sOlBACXGjgfQwgfEwHwYDVR0jBBgwFoAUvqZ+TT18j6BV5sEvCS4sIEOzQn8w" + + "SAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh" + + "0bTA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vMjEwLjc0LjQyLjMvT0NBMTEvU00yL2NybDI0OTkuY3JsMAsGA1UdDwQEAw" + + "ID6DAdBgNVHQ4EFgQU07tMtbs5PWkwN33OQVH116xd1kowHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMAwGCCqBH" + + "M9VAYN1BQADSQAwRgIhAPuuInppVhugw6EIG4wgkouxX/MX2dTLe478wx7LFXSQAiEAneiz51vrurICNCfeecaBHgzaj7+3" + + "kmrdIZJBoxYGbso="; + + String issuerCert = + "-----BEGIN CERTIFICATE-----\n" + + "MIICTzCCAfOgAwIBAgIKJFSZ4SRVDndYUTAMBggqgRzPVQGDdQUAMF0xCzAJBgNV\n" + + "BAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBB\n" + + "dXRob3JpdHkxHDAaBgNVBAMME0NGQ0EgVEVTVCBDUyBTTTIgQ0EwHhcNMTIwODI5\n" + + "MDU0ODQ3WhcNMzIwODI0MDU0ODQ3WjBdMQswCQYDVQQGEwJDTjEwMC4GA1UECgwn\n" + + "Q2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRwwGgYDVQQD\n" + + "DBNDRkNBIFRFU1QgU00yIE9DQTExMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE\n" + + "L1mx4wriQUojGsIkNL14kslv9nwiqsiVpELOZauzghrbccNlPYKNYKZOCvXwIIqU\n" + + "9QY02d4weqKqo/JMcNsKEaOBmDCBlTAfBgNVHSMEGDAWgBS12JBvXPDYM9JjvX6y\n" + + "w43GTxJ6YTAMBgNVHRMEBTADAQH/MDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6Ly8y\n" + + "MTAuNzQuNDIuMy90ZXN0cmNhL1NNMi9jcmwxLmNybDALBgNVHQ8EBAMCAQYwHQYD\n" + + "VR0OBBYEFL6mfk09fI+gVebBLwkuLCBDs0J/MAwGCCqBHM9VAYN1BQADSAAwRQIh\n" + + "AKuk7s3eYCZDck5NWU0eNQmLhBN/1zmKs517qFrDrkJWAiAP4cVfLtdza/OkwU9P\n" + + "PrIDl+E4aL3FypntFXHG3T+Keg==\n" + + "-----END CERTIFICATE-----"; + + byte[] rawPublicKeyBytes = Hex.decode(publicKeyStr); + byte[] rawPrivateKeyBytes = Hex.decode(privateKeyStr); + + CSRBuilder builder = new CSRBuilder(); + builder.init("SM3withSM2", rawPublicKeyBytes, rawPrivateKeyBytes); + + CertParser parser = new CertParser(); + parser.parse(expectedUserCert,issuerCert); + + PublicKey rawPublicKeyInCert = parser.getPubKey(); + // check that the public key in inputs and the public key in certificate are consistent + assertArrayEquals(rawPublicKeyBytes, rawPublicKeyInCert.getEncoded()); + + String algoName = parser.getSigAlgName(); + int keyLength = parser.getKeyLength(); + String length = String.valueOf(keyLength); + String algo = (algoName.contains("RSA")? (algoName + length).toUpperCase(): algoName.toUpperCase()); + + CryptoAlgorithm algorithm = Crypto.getAlgorithm(algo); + assertNotNull(algorithm); + SignatureFunction signatureFunction = Crypto.getSignatureFunction(algorithm); + + PubKey pubKey = new PubKey(algorithm, rawPublicKeyBytes); + PrivKey privKey = new PrivKey(algorithm, rawPrivateKeyBytes); + + // signTest + byte[] data = new byte[1024]; + Random random = new Random(); + random.nextBytes(data); + + SignatureDigest signature = signatureFunction.sign(privKey, data); + assertTrue(signatureFunction.verify(signature, pubKey, data)); + } } diff --git a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/utils/CertParserTest.java b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/utils/CertParserTest.java index 24cdc414..263d3e88 100644 --- a/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/utils/CertParserTest.java +++ b/source/crypto/crypto-pki/src/test/java/com/jd/blockchain/crypto/utils/CertParserTest.java @@ -43,7 +43,8 @@ public class CertParserTest { String userCert = "MIIEQDCCAyigAwIBAgIFICdVYzEwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4XDTE5MDUxMDExMjAyNFoXDTIxMDUxMDExMjAyNFowcjELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTERMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTEfMB0GA1UEAxQWMDUxQGFhYWFhQFpIMDkzNTgwMjhAMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJx3F2WD1dJPzK/nRHO7d1TJ1hTjzGTmv0PQ7ECsJAh3U3BtnGTpCB+b4+JMI4LO8nHkKIBQ3P9XnF+Bf1iXdWNAQ4aWCxa2nV7lCp4w0GliPu/EMgIfmsSDUtgqbM3cr8sR8r9m1xG3gt2TIQJ+jT7sAiguU/kyNzpjaccOUIgUFa8IDFq9UeB76MXtCuhlERRZQCl47e+9w7ZoxmE7e6IZORxPp7rQWVBHlR9ntWjJfNDTm3gMP5ehP+yIZnKx1LudxkBLQxpMmspzOyH1zqx5nkKe49AfWWpDxxRvYkriyYC3aE81qLsU/bhLwNEKOju7BGDF/mhJLZUedojM0gMCAwEAAaOB9TCB8jAfBgNVHSMEGDAWgBT8C7xEmg4xoYOpgYcnHgVCxr9W+DBIBgNVHSAEQTA/MD0GCGCBHIbvKgECMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20uY24vdXMvdXMtMTUuaHRtMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly8yMTAuNzQuNDIuMy9PQ0ExMS9SU0EvY3JsMjU2OTMuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQU5oKGaQs7Jt5Gfbt1XhFTWAySEKswHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBBQUAA4IBAQAlmPRaImZV51iKjtpMKuyLMw7dX8L0lY3tl+pVZZSxHuwsN4GCCtV0Ej50up+/6EbfL4NUTiuHVAjCroKKvb+94CrdEwdnQGM5IbGSjT78nQpeASXbIWuUwA+ImjvZOzvq/0b56AzonNzBxOMGko/bj5smM6X8jrgJ0NQppo2KNSVNC4JbuoNWI4FM94SE4DUi9H7EYl4JdOtDaDtCsq49o/A1CZyYrmoOPCgxpQQXmuB3lGq/jyoOlW2aW8uee/hYG1JJcSHLBjF0WBwdxssgbBotA5f1PebiIMSbFgjk57bd4M80hhU/rI4Hkn9pcp5R7NsX95TtyDIg90LboBnW"; parser.parse(userCert, issuerCert); - assertEquals("SHA1WITHRSA",parser.getSigAlgName()); + assertEquals("SHA1WITHRSA", parser.getSigAlgName()); + assertEquals(2048, parser.getKeyLength()); } @Test @@ -77,7 +78,8 @@ public class CertParserTest { String userCert = "MIIFRjCCBC6gAwIBAgIFICdWiDMwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4XDTE5MDUxNjA3MDcyMloXDTIxMDUxNjA3MDcyMloweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTERMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTElMCMGA1UEAxQcMDUxQHpoYW5nbGluIUBaMTg2MTIyMjkyOTVAMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL0rTOxd8rsjPtFJ0aGVh9bZPy5Xo0SADaP7BbJsG4+ykLQMZHg9hTf/6fv1OsD2HEKFoMpIkW2gwCJW2nvicHcVql/shCoktc6ZBW6Dr/DxOgbO9tpoGxZ50xdI4Q0NsrxqtbCldW4ozPHdjgRJ83i1KSFh7evNrVByN/mB+jchrVGLWyJ1uIRgUUgpRZmZPoOHaizVJqrqWJGGk6xbDLR2gUQ1hTzetQaz1OeKtelHDk9FY08XSmNGssSMpuSjrxn78S888VW5WIxyo4cwrXSXFk3J7LNTy70Oga16HZjJD/vLTM6a4riPa8+uivPinKxK38/++nlBPNwhx6n46uYkd9Zvw+SJiJgpnuPJLtMZpKpJx7V1BDVEydKPUthilTdsmJtkBFSlFw0G1aKfuciBGzzJ3SKngJF/JqJAWIonVAFBGb6Gokp1Sw+T4KqXrdbjxYxiyyjZ++8O1vydgFAkx/NjsuwJnpKETiRKFJmY7YawcUvC4ixF7XQc0luFWRDYcbxOppir+ieMqhGXyaFhLUuB4WXv+rFxfa3NmkBW8q5TPzt/PwWcXpITsYTZYla/E/grB+OeZLYgjigT5YlgytPHG6Gt1ySCCd8WXFWpkBbQfXzqcvtU27RCcAUgfXk5NLb7NZCQg7heGjgzOdYJCPsa1d3m7l04+VIKGCZdAgMBAAGjgfUwgfIwHwYDVR0jBBgwFoAU/Au8RJoOMaGDqYGHJx4FQsa/VvgwSAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh0bTA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vMjEwLjc0LjQyLjMvT0NBMTEvUlNBL2NybDI1NzE3LmNybDALBgNVHQ8EBAMCA+gwHQYDVR0OBBYEFMjh6AzDCuNkD+pqQfiS6bqPGpI4MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDANBgkqhkiG9w0BAQUFAAOCAQEApZaLXS6/6FudPA3l2xom5U7nJUOpQ1E6DO/ic9dFGtLE0WgyAqB3JVPvXEntppX55x/dAV7rvvz9NaEdiAe8DAj7qyoPDvC8ZWQK8U4n9l8N78QwALOURxzQNs+CBatJQzbu2w1RKVwkfE6xEIVnu+wjiAtymfwdLHMagHxDIC/eOPbTnbbtITJk2ukFfoc0WJ6Awg5lW+x7nGokEn/XAjKyRHCpkRUFGQm4ww41zlrqCqQqnVGVABJtjbdtFf7nh33QHl0fkj19nfMox9eGuntPyM0bNA0XqPMA+FWSCqeDT6uLbyaOKWxlhv53U/NCJl76U3tssMEWsm9amEDDQg=="; parser.parse(userCert, issuerCert); - assertEquals("SHA1WITHRSA",parser.getSigAlgName()); + assertEquals("SHA1WITHRSA", parser.getSigAlgName()); + assertEquals(4096, parser.getKeyLength()); } @Test @@ -103,7 +105,8 @@ public class CertParserTest { String userCert = "MIICwDCCAmWgAwIBAgIFICdWkWgwDAYIKoEcz1UBg3UFADBdMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRwwGgYDVQQDDBNDRkNBIFRFU1QgU00yIE9DQTExMB4XDTE5MDUxNjA4MTA1MVoXDTIxMDUxNjA4MTA1MVoweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoMD0NGQ0EgVEVTVCBPQ0ExMTERMA8GA1UECwwITG9jYWwgUkExFTATBgNVBAsMDEluZGl2aWR1YWwtMTElMCMGA1UEAwwcMDUxQHpoYW5nbGluIUBaMTg2MTIyMjkyOTVAMzBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABPvNXpdZ4/4g+wx5qKS94CPkMqpEDhlnXYYW7ZzsbNI4d28sVBz5Ji6dTT1Zx627Kvw4tdUaUt7BVMvZsu3BFlyjgfQwgfEwHwYDVR0jBBgwFoAUvqZ+TT18j6BV5sEvCS4sIEOzQn8wSAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh0bTA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vMjEwLjc0LjQyLjMvT0NBMTEvU00yL2NybDIxMDkuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQUxR5C/VjASus5zrAAFS4ulMpRjKgwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMAwGCCqBHM9VAYN1BQADRwAwRAIgVBzVi/fgkknr+2BH2wXeGMXC+Pa6p7rbldUsYMOYoyUCIAmQ4KEk2U1xJZSBpOPy5jN9kmRb+0YH6x04O/2tqCgq"; parser.parse(userCert, issuerCert); - assertEquals("SM3WITHSM2",parser.getSigAlgName()); + assertEquals("SM3WITHSM2", parser.getSigAlgName()); + assertEquals(256, parser.getKeyLength()); } @Test @@ -141,6 +144,7 @@ public class CertParserTest { "PrIDl+E4aL3FypntFXHG3T+Keg==\n" + "-----END CERTIFICATE-----"; parser.parse(issuerCert, CACert); - assertEquals("SM3WITHSM2",parser.getSigAlgName()); + assertEquals("SM3WITHSM2", parser.getSigAlgName()); + assertEquals(256, parser.getKeyLength()); } } diff --git a/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java index 94c0cf32..b3a65b18 100644 --- a/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java +++ b/source/crypto/crypto-sm/src/main/java/com/jd/blockchain/crypto/utils/sm/SM2Utils.java @@ -13,6 +13,8 @@ import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.math.ec.*; import java.io.IOException; @@ -29,19 +31,10 @@ public class SM2Utils { // The length of sm3 output is 32 bytes private static final int SM3DIGEST_LENGTH = 32; - private static final BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); - private static final BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); - private static final BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); - private static final BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); - private static final BigInteger SM2_H = ECConstants.ONE; - private static final BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); - private static final BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); - - // To get the curve from the equation y^2=x^3+ax+b according the coefficient a and b, - // with the big prime p, the order n, the cofactor h, and obtain the generator g and the domain's parameters - private static final ECCurve CURVE = new ECCurve.Fp(SM2_P, SM2_A, SM2_B, SM2_N, SM2_H); - private static final ECPoint G = CURVE.createPoint(SM2_GX, SM2_GY); - private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G, SM2_N); + private static final ECNamedCurveParameterSpec PARAMS = ECNamedCurveTable.getParameterSpec("sm2p256v1"); + private static final ECCurve CURVE = PARAMS.getCurve(); + private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters( + CURVE, PARAMS.getG(), PARAMS.getN(), PARAMS.getH()); //-----------------Key Generation Algorithm----------------- From 13bacf9f5a7b550f2ea3b6f766009b202a7d79d6 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 28 Aug 2019 22:22:51 +0800 Subject: [PATCH 063/124] Completed feature that setting roles in ledger's initialization configuration; --- .../blockchain/ledger/LedgerPermission.java | 49 +++--- .../blockchain/ledger/SecurityInitData.java | 6 +- .../ledger/SecurityInitDataTest.java | 12 ++ .../initializer/LedgerInitializeTest.java | 2 - .../initializer/LedgerInitProperties.java | 153 ++++++++++++++++-- .../initializer/LedgerInitPropertiesTest.java | 89 +++++++++- .../src/test/resources/ledger.init | 61 ++++++- .../jd/blockchain/utils/PropertiesUtils.java | 4 + .../com/jd/blockchain/utils/StringUtils.java | 83 ++++++++-- 9 files changed, 390 insertions(+), 69 deletions(-) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java index 18d0fdfd..82793392 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java @@ -36,59 +36,54 @@ public enum LedgerPermission { REGISTER_PARTICIPANT((byte) 0x04), /** - * 设置参与方的权限;
        + * 注册用户;
        * - * 如果不具备此项权限,则无法设置参与方的“提交交易”、“参与共识”的权限; + * 如果不具备此项权限,则无法注册用户; */ - SET_PARTICIPANT_PERMISSION((byte) 0x05), + REGISTER_USER((byte) 0x05), /** - * 参与方核准交易;
        - * - * 如果不具备此项权限,则无法作为节点签署由终端提交的交易; - *

        - * 只对交易请求的节点签名列表{@link TransactionRequest#getNodeSignatures()}的用户产生影响; + * 注册数据账户;
        */ - APPROVE_TX((byte) 0x06), + REGISTER_DATA_ACCOUNT((byte) 0x06), /** - * 参与方共识交易;
        - * - * 如果不具备此项权限,则无法作为共识节点接入并对交易进行共识; + * 注册合约;
        */ - CONSENSUS_TX((byte) 0x07), + REGISTER_CONTRACT((byte) 0x07), /** - * 注册用户;
        - * - * 如果不具备此项权限,则无法注册用户; + * 升级合约 */ - REGISTER_USER((byte) 0x08), + UPGRADE_CONTRACT((byte) 0x08), /** * 设置用户属性;
        */ SET_USER_ATTRIBUTES((byte) 0x09), - /** - * 注册数据账户;
        - */ - REGISTER_DATA_ACCOUNT((byte) 0x0A), - /** * 写入数据账户;
        */ - WRITE_DATA_ACCOUNT((byte) 0x0B), + WRITE_DATA_ACCOUNT((byte) 0x0A), /** - * 注册合约;
        + * 参与方核准交易;
        + * + * 如果不具备此项权限,则无法作为节点签署由终端提交的交易; + *

        + * 只对交易请求的节点签名列表{@link TransactionRequest#getNodeSignatures()}的用户产生影响; */ - REGISTER_CONTRACT((byte) 0x0C), + APPROVE_TX((byte) 0x0B), /** - * 升级合约 + * 参与方共识交易;
        + * + * 如果不具备此项权限,则无法作为共识节点接入并对交易进行共识; */ - UPGRADE_CONTRACT((byte) 0x0D); + CONSENSUS_TX((byte) 0x0C); + + @EnumField(type = PrimitiveType.INT8) public final byte CODE; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java index 9e4a5d55..9ba2e250 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java @@ -5,7 +5,7 @@ import java.util.List; public class SecurityInitData implements SecurityInitSettings { - private List roles = new ArrayList(); + private List roles = new ArrayList(); @Override public RoleInitData[] getRoles() { @@ -13,8 +13,8 @@ public class SecurityInitData implements SecurityInitSettings { } public void setRoles(RoleInitData[] roles) { - List list = new ArrayList(); - for (RoleInitSettings r : roles) { + List list = new ArrayList(); + for (RoleInitData r : roles) { list.add(r); } this.roles = list; diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java index fab4c57a..6bf9d1f5 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java @@ -29,6 +29,18 @@ public class SecurityInitDataTest { assertEquals(LedgerPermission.REGISTER_USER, permissions2[0]); assertEquals(LedgerPermission.REGISTER_DATA_ACCOUNT, permissions2[1]); + LedgerPermission[] allLedgerPermissions = LedgerPermission.values(); + String jsonLedgerPersioms = JSONSerializeUtils.serializeToJSON(allLedgerPermissions); + + TransactionPermission[] allTransactionPermissions = TransactionPermission.values(); + String jsonTransactionPersioms = JSONSerializeUtils.serializeToJSON(allTransactionPermissions); + + System.out.println("----------- Ledger Permissions JSON ------------"); + System.out.println(jsonLedgerPersioms); + System.out.println("-----------------------"); + System.out.println("----------- Transaction Permissions JSON ------------"); + System.out.println(jsonTransactionPersioms); + System.out.println("-----------------------"); } @Test diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index d01de50f..101c874b 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -13,8 +13,6 @@ import org.junit.Test; import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.consensus.ConsensusProvider; -import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java index 407501e9..ccc97362 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java @@ -7,15 +7,23 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.TreeMap; import com.jd.blockchain.consts.Global; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.RoleInitData; +import com.jd.blockchain.ledger.RoleInitSettings; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; +import com.jd.blockchain.utils.StringUtils; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.io.FileUtils; import com.jd.blockchain.utils.net.NetworkAddress; @@ -33,6 +41,13 @@ public class LedgerInitProperties { // 创建时间的格式; public static final String CREATED_TIME_FORMAT = Global.DEFAULT_TIME_FORMAT; + // 角色清单; + public static final String ROLES = "security.roles"; + // 角色的账本权限;用角色名称替代占位符; + public static final String ROLE_LEDGER_PRIVILEGES_PATTERN = "security.role.%s.ledger-privileges"; + // 角色的交易权限;用角色名称替代占位符; + public static final String ROLE_TX_PRIVILEGES_PATTERN = "security.role.%s.tx-privileges"; + // 共识参与方的个数,后续以 part.id 分别标识每一个参与方的配置; public static final String PART_COUNT = "cons_parti.count"; // 共识参与方的名称的模式; @@ -43,6 +58,10 @@ public class LedgerInitProperties { public static final String PART_PUBKEY_PATH = "pubkey-path"; // 参与方的公钥文件路径; public static final String PART_PUBKEY = "pubkey"; + // 参与方的角色清单; + public static final String PART_ROLES = "roles"; + // 参与方的角色权限策略; + public static final String PART_ROLES_POLICY = "roles-policy"; // 共识参与方的账本初始服务的主机; public static final String PART_INITIALIZER_HOST = "initializer.host"; @@ -66,6 +85,8 @@ public class LedgerInitProperties { private String ledgerName; + private RoleInitData[] roles; + private List consensusParticipants = new ArrayList<>(); private String consensusProvider; @@ -143,7 +164,7 @@ public class LedgerInitProperties { consensusParticipants.add(participant); } - private static String getKeyOfCsParti(int partId, String partPropKey) { + private static String getKeyOfParticipant(int partId, String partPropKey) { String partAddrStr = String.format(PART_ID_PATTERN, partId); return String.format("%s.%s", partAddrStr, partPropKey); } @@ -162,12 +183,20 @@ public class LedgerInitProperties { public static LedgerInitProperties resolve(Properties props) { return resolve(null, props); } - - public static LedgerInitProperties resolve(String dir, Properties props) { + + /** + * 从属性表解析账本初始化参数; + * + * @param baseDirectory 基础路径;属性中涉及文件位置的相对路径以此参数指定的目录为父目录; + * @param props 要解析的属性表; + * @return + */ + public static LedgerInitProperties resolve(String baseDirectory, Properties props) { String hexLedgerSeed = PropertiesUtils.getRequiredProperty(props, LEDGER_SEED).replace("-", ""); byte[] ledgerSeed = HexUtils.decode(hexLedgerSeed); LedgerInitProperties initProps = new LedgerInitProperties(ledgerSeed); + // 解析账本信息; // 账本名称 String ledgerName = PropertiesUtils.getRequiredProperty(props, LEDGER_NAME); initProps.ledgerName = ledgerName; @@ -180,11 +209,35 @@ public class LedgerInitProperties { throw new IllegalArgumentException(ex.getMessage(), ex); } + // 解析角色清单; + String strRoleNames = PropertiesUtils.getOptionalProperty(props, ROLES); + String[] roles = StringUtils.splitToArray(strRoleNames, ","); + + Map rolesInitSettingMap = new TreeMap(); + // 解析角色权限表; + for (String role : roles) { + String ledgerPrivilegeKey = getKeyOfRoleLedgerPrivilege(role); + String strLedgerPermissions = PropertiesUtils.getOptionalProperty(props, ledgerPrivilegeKey); + LedgerPermission[] ledgerPermissions = resolveLedgerPermissions(strLedgerPermissions); + + String txPrivilegeKey = getKeyOfRoleTxPrivilege(role); + String strTxPermissions = PropertiesUtils.getOptionalProperty(props, txPrivilegeKey); + TransactionPermission[] txPermissions = resolveTransactionPermissions(strTxPermissions); + + if (ledgerPermissions.length > 0 || txPermissions.length > 0) { + RoleInitData rolesSettings = new RoleInitData(role, ledgerPermissions, txPermissions); + rolesInitSettingMap.put(role, rolesSettings); + } + } + RoleInitData[] rolesInitDatas = rolesInitSettingMap.values() + .toArray(new RoleInitData[rolesInitSettingMap.size()]); + initProps.setRoles(rolesInitDatas); + // 解析共识相关的属性; initProps.consensusProvider = PropertiesUtils.getRequiredProperty(props, CONSENSUS_SERVICE_PROVIDER); String consensusConfigFilePath = PropertiesUtils.getRequiredProperty(props, CONSENSUS_CONFIG); try { - File consensusConfigFile = FileUtils.getFile(dir, consensusConfigFilePath); + File consensusConfigFile = FileUtils.getFile(baseDirectory, consensusConfigFilePath); initProps.consensusConfig = FileUtils.readProperties(consensusConfigFile); } catch (FileNotFoundException e) { throw new IllegalArgumentException( @@ -212,13 +265,13 @@ public class LedgerInitProperties { parti.setId(i); - String nameKey = getKeyOfCsParti(i, PART_NAME); + String nameKey = getKeyOfParticipant(i, PART_NAME); parti.setName(PropertiesUtils.getRequiredProperty(props, nameKey)); - String pubkeyPathKey = getKeyOfCsParti(i, PART_PUBKEY_PATH); + String pubkeyPathKey = getKeyOfParticipant(i, PART_PUBKEY_PATH); String pubkeyPath = PropertiesUtils.getProperty(props, pubkeyPathKey, false); - String pubkeyKey = getKeyOfCsParti(i, PART_PUBKEY); + String pubkeyKey = getKeyOfParticipant(i, PART_PUBKEY); String base58PubKey = PropertiesUtils.getProperty(props, pubkeyKey, false); if (base58PubKey != null) { PubKey pubKey = KeyGenCommand.decodePubKey(base58PubKey); @@ -231,13 +284,27 @@ public class LedgerInitProperties { String.format("Property[%s] and property[%s] are all empty!", pubkeyKey, pubkeyPathKey)); } - String initializerHostKey = getKeyOfCsParti(i, PART_INITIALIZER_HOST); + // 解析参与方的角色权限配置; + String partiRolesKey = getKeyOfParticipant(i, PART_ROLES); + String strPartiRoles = PropertiesUtils.getOptionalProperty(props, partiRolesKey); + String[] partiRoles = StringUtils.splitToArray(strPartiRoles, ","); + parti.setRoles(partiRoles); + + String partiRolePolicyKey = getKeyOfParticipant(i, PART_ROLES_POLICY); + String strPartiPolicy = PropertiesUtils.getOptionalProperty(props, partiRolePolicyKey); + RolesPolicy policy = strPartiPolicy == null ? RolesPolicy.UNION + : RolesPolicy.valueOf(strPartiPolicy.trim()); + policy = policy == null ? RolesPolicy.UNION : policy; + parti.setRolesPolicy(policy); + + // 解析参与方的网络配置参数; + String initializerHostKey = getKeyOfParticipant(i, PART_INITIALIZER_HOST); String initializerHost = PropertiesUtils.getRequiredProperty(props, initializerHostKey); - String initializerPortKey = getKeyOfCsParti(i, PART_INITIALIZER_PORT); + String initializerPortKey = getKeyOfParticipant(i, PART_INITIALIZER_PORT); int initializerPort = getInt(PropertiesUtils.getRequiredProperty(props, initializerPortKey)); - String initializerSecureKey = getKeyOfCsParti(i, PART_INITIALIZER_SECURE); + String initializerSecureKey = getKeyOfParticipant(i, PART_INITIALIZER_SECURE); boolean initializerSecure = Boolean .parseBoolean(PropertiesUtils.getRequiredProperty(props, initializerSecureKey)); NetworkAddress initializerAddress = new NetworkAddress(initializerHost, initializerPort, initializerSecure); @@ -249,10 +316,54 @@ public class LedgerInitProperties { return initProps; } + private static TransactionPermission[] resolveTransactionPermissions(String strTxPermissions) { + String[] strPermissions = StringUtils.splitToArray(strTxPermissions, ","); + List permissions = new ArrayList(); + if (strPermissions != null) { + for (String pm : strPermissions) { + TransactionPermission permission = TransactionPermission.valueOf(pm); + if (permission != null) { + permissions.add(permission); + } + } + } + return permissions.toArray(new TransactionPermission[permissions.size()]); + } + + private static LedgerPermission[] resolveLedgerPermissions(String strLedgerPermissions) { + String[] strPermissions = StringUtils.splitToArray(strLedgerPermissions, ","); + List permissions = new ArrayList(); + if (strPermissions != null) { + for (String pm : strPermissions) { + LedgerPermission permission = LedgerPermission.valueOf(pm); + if (permission != null) { + permissions.add(permission); + } + } + } + return permissions.toArray(new LedgerPermission[permissions.size()]); + } + + private static String getKeyOfRoleLedgerPrivilege(String role) { + return String.format(ROLE_LEDGER_PRIVILEGES_PATTERN, role); + } + + private static String getKeyOfRoleTxPrivilege(String role) { + return String.format(ROLE_TX_PRIVILEGES_PATTERN, role); + } + private static int getInt(String strInt) { return Integer.parseInt(strInt.trim()); } + public RoleInitData[] getRoles() { + return roles; + } + + public void setRoles(RoleInitData[] roles) { + this.roles = roles; + } + /** * 参与方配置信息; * @@ -267,10 +378,12 @@ public class LedgerInitProperties { private String name; -// private String pubKeyPath; - private PubKey pubKey; + private String[] roles; + + private RolesPolicy rolesPolicy; + // private NetworkAddress consensusAddress; private NetworkAddress initializerAddress; @@ -321,6 +434,22 @@ public class LedgerInitProperties { this.address = AddressEncoding.generateAddress(pubKey); } + public String[] getRoles() { + return roles; + } + + public void setRoles(String[] roles) { + this.roles = roles; + } + + public RolesPolicy getRolesPolicy() { + return rolesPolicy; + } + + public void setRolesPolicy(RolesPolicy rolesPolicy) { + this.rolesPolicy = rolesPolicy; + } + } } diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java index 3e0a004e..6e890a52 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java @@ -1,12 +1,18 @@ package test.com.jd.blockchain.tools.initializer; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.TimeZone; import org.junit.Test; @@ -14,6 +20,10 @@ import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.RoleInitData; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.tools.keygen.KeyGenCommand; @@ -22,19 +32,20 @@ import com.jd.blockchain.utils.codec.HexUtils; public class LedgerInitPropertiesTest { private static String expectedCreatedTimeStr = "2019-08-01 14:26:58.069+0800"; - + private static String expectedCreatedTimeStr1 = "2019-08-01 13:26:58.069+0700"; - + @Test public void testTimeFormat() throws ParseException { SimpleDateFormat timeFormat = new SimpleDateFormat(LedgerInitProperties.CREATED_TIME_FORMAT); -// timeFormat.setTimeZone(TimeZone.getTimeZone("GMT+08:00")); - TimeZone.setDefault(TimeZone.getTimeZone("GMT+08:00")); - + timeFormat.setTimeZone(TimeZone.getTimeZone("GMT+08:00")); + // 或者设置全局的默认时区; + // TimeZone.setDefault(TimeZone.getTimeZone("GMT+08:00")); + Date time = timeFormat.parse(expectedCreatedTimeStr); String actualTimeStr = timeFormat.format(time); assertEquals(expectedCreatedTimeStr, actualTimeStr); - + Date time1 = timeFormat.parse(expectedCreatedTimeStr1); String actualTimeStr1 = timeFormat.format(time1); assertEquals(expectedCreatedTimeStr, actualTimeStr1); @@ -42,11 +53,13 @@ public class LedgerInitPropertiesTest { @Test public void testProperties() throws IOException, ParseException { + // 加载用于测试的账本初始化配置; ClassPathResource ledgerInitSettingResource = new ClassPathResource("ledger.init"); InputStream in = ledgerInitSettingResource.getInputStream(); try { LedgerInitProperties initProps = LedgerInitProperties.resolve(in); - assertEquals(4, initProps.getConsensusParticipantCount()); + + // 验证账本信息; String expectedLedgerSeed = "932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe" .replace("-", ""); String actualLedgerSeed = HexUtils.encode(initProps.getLedgerSeed()); @@ -56,10 +69,54 @@ public class LedgerInitPropertiesTest { timeFormat.setTimeZone(TimeZone.getTimeZone("GMT+08:00")); long expectedTs = timeFormat.parse(expectedCreatedTimeStr).getTime(); assertEquals(expectedTs, initProps.getCreatedTime()); - + String createdTimeStr = timeFormat.format(new Date(initProps.getCreatedTime())); assertEquals(expectedCreatedTimeStr, createdTimeStr); + // 验证角色配置; + RoleInitData[] roles = initProps.getRoles(); + assertEquals(4, roles.length); + Map rolesInitDatas = new HashMap(); + for (RoleInitData r : roles) { + rolesInitDatas.put(r.getRoleName(), r); + } + // 初始化配置的角色最终也是有序排列的,按照角色名称的自然顺序; + String[] expectedRolesNames = { "DEFAULT", "ADMIN", "MANAGER", "GUEST" }; + Arrays.sort(expectedRolesNames); + assertEquals(expectedRolesNames[0], roles[0].getRoleName()); + assertEquals(expectedRolesNames[1], roles[1].getRoleName()); + assertEquals(expectedRolesNames[2], roles[2].getRoleName()); + assertEquals(expectedRolesNames[3], roles[3].getRoleName()); + + RoleInitData roleDefault = rolesInitDatas.get("DEFAULT"); + assertArrayEquals( + new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, + roleDefault.getLedgerPermissions()); + assertArrayEquals(new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }, roleDefault.getTransactionPermissions()); + + RoleInitData roleAdmin = rolesInitDatas.get("ADMIN"); + assertArrayEquals(new LedgerPermission[] { LedgerPermission.AUTHORIZE_ROLES, LedgerPermission.SET_CONSENSUS, + LedgerPermission.SET_CRYPTO, LedgerPermission.REGISTER_PARTICIPANT, + LedgerPermission.REGISTER_USER }, roleAdmin.getLedgerPermissions()); + assertArrayEquals(new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION }, + roleAdmin.getTransactionPermissions()); + + RoleInitData roleManager = rolesInitDatas.get("MANAGER"); + assertArrayEquals( + new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT, + LedgerPermission.REGISTER_CONTRACT, LedgerPermission.UPGRADE_CONTRACT, + LedgerPermission.SET_USER_ATTRIBUTES, LedgerPermission.WRITE_DATA_ACCOUNT }, + roleManager.getLedgerPermissions()); + assertArrayEquals(new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }, roleManager.getTransactionPermissions()); + + RoleInitData roleGuest = rolesInitDatas.get("GUEST"); + assertTrue(roleGuest.getLedgerPermissions() == null || roleGuest.getLedgerPermissions().length == 0); + assertArrayEquals(new TransactionPermission[] { TransactionPermission.CONTRACT_OPERATION }, + roleGuest.getTransactionPermissions()); + + // 验证共识和密码配置; assertEquals("com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider", initProps.getConsensusProvider()); @@ -68,6 +125,9 @@ public class LedgerInitPropertiesTest { assertEquals("com.jd.blockchain.crypto.service.classic.ClassicCryptoService", cryptoProviders[0]); assertEquals("com.jd.blockchain.crypto.service.sm.SMCryptoService", cryptoProviders[1]); + // 验证参与方信息; + assertEquals(4, initProps.getConsensusParticipantCount()); + ConsensusParticipantConfig part0 = initProps.getConsensusParticipant(0); assertEquals("jd.com", part0.getName()); PubKey pubKey0 = KeyGenCommand.decodePubKey("3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9"); @@ -75,14 +135,27 @@ public class LedgerInitPropertiesTest { assertEquals("127.0.0.1", part0.getInitializerAddress().getHost()); assertEquals(8800, part0.getInitializerAddress().getPort()); assertEquals(true, part0.getInitializerAddress().isSecure()); + assertArrayEquals(new String[] {"ADMIN", "MANAGER"}, part0.getRoles()); + assertEquals(RolesPolicy.UNION, part0.getRolesPolicy()); + ConsensusParticipantConfig part1 = initProps.getConsensusParticipant(1); assertEquals(false, part1.getInitializerAddress().isSecure()); PubKey pubKey1 = KeyGenCommand.decodePubKey("3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX"); assertEquals(pubKey1, part1.getPubKey()); + assertArrayEquals(new String[] { "MANAGER"}, part1.getRoles()); + assertEquals(RolesPolicy.UNION, part1.getRolesPolicy()); ConsensusParticipantConfig part2 = initProps.getConsensusParticipant(2); assertEquals("7VeRAr3dSbi1xatq11ZcF7sEPkaMmtZhV9shonGJWk9T4pLe", part2.getPubKey().toBase58()); + assertArrayEquals(new String[] { "MANAGER"}, part2.getRoles()); + assertEquals(RolesPolicy.UNION, part2.getRolesPolicy()); + + ConsensusParticipantConfig part3 = initProps.getConsensusParticipant(3); + PubKey pubKey3 = KeyGenCommand.decodePubKey("3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"); + assertEquals(pubKey3, part3.getPubKey()); + assertArrayEquals(new String[] { "GUEST"}, part3.getRoles()); + assertEquals(RolesPolicy.INTERSECT, part3.getRolesPolicy()); } finally { in.close(); diff --git a/source/tools/tools-initializer/src/test/resources/ledger.init b/source/tools/tools-initializer/src/test/resources/ledger.init index 2d574f93..275778b7 100644 --- a/source/tools/tools-initializer/src/test/resources/ledger.init +++ b/source/tools/tools-initializer/src/test/resources/ledger.init @@ -5,9 +5,52 @@ ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323 #账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; ledger.name=test -#声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 +#声明账本的创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 + +#----------------------------------------------- +# 初始的角色名称列表;可选项; +# 角色名称不区分大小写,最长不超过20个字符;多个角色名称之间用半角的逗点“,”分隔; +# 系统会预置一个默认角色“DEFAULT”,所有未指定角色的用户都以赋予该角色的权限;若初始化时未配置默认角色的权限,则为默认角色分配所有权限; +# +# 注:如果声明了角色,但未声明角色对应的权限清单,这会忽略该角色的初始化; +# +security.roles=DEFAULT, ADMIN, MANAGER, GUEST + +# 赋予角色的账本权限清单;可选项; +# 可选的权限如下; +# AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, +# REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, +# SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +# APPROVE_TX, CONSENSUS_TX +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT + +# 赋予角色的交易权限清单;可选项; +# 可选的权限如下; +# DIRECT_OPERATION, CONTRACT_OPERATION +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 其它角色的配置示例; +# 系统管理员角色:只能操作全局性的参数配置和用户注册,只能执行直接操作指令; +security.role.ADMIN.ledger-privileges=AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER +security.role.ADMIN.tx-privileges=DIRECT_OPERATION + +# 业务主管角色:只能够执行账本数据相关的操作,包括注册用户、注册数据账户、注册合约、升级合约、写入数据等;能够执行直接操作指令和调用合约; +security.role.MANAGER.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +security.role.MANAGER.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 访客角色:不具备任何的账本权限,只有数据读取的操作;也只能够通过调用合约来读取数据; +security.role.GUEST.ledger-privileges= +security.role.GUEST.tx-privileges=CONTRACT_OPERATION + + + +#----------------------------------------------- #共识服务提供者;必须; consensus.service-provider=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider @@ -28,6 +71,10 @@ cons_parti.0.name=jd.com cons_parti.0.pubkey-path=keys/jd-com.pub #第0个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.0.pubkey=3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9 +#第0个参与方的角色清单;可选项; +cons_parti.0.roles=ADMIN, MANAGER +#第0个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.0.roles-policy=UNION #第0个参与方的共识服务的主机地址; cons_parti.0.consensus.host=127.0.0.1 #第0个参与方的共识服务的端口; @@ -47,6 +94,10 @@ cons_parti.1.name=at.com cons_parti.1.pubkey-path=keys/at-com.pub #第1个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.1.pubkey=3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX +#第1个参与方的角色清单;可选项; +cons_parti.1.roles=MANAGER +#第1个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.1.roles-policy=UNION #第1个参与方的共识服务的主机地址; cons_parti.1.consensus.host=127.0.0.1 #第1个参与方的共识服务的端口; @@ -66,6 +117,10 @@ cons_parti.2.name=bt.com cons_parti.2.pubkey-path=classpath:keys/parti2.pub #第2个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.2.pubkey= +#第2个参与方的角色清单;可选项; +cons_parti.2.roles=MANAGER +#第2个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.2.roles-policy=UNION #第2个参与方的共识服务的主机地址; cons_parti.2.consensus.host=127.0.0.1 #第2个参与方的共识服务的端口; @@ -85,6 +140,10 @@ cons_parti.3.name=xt.com cons_parti.3.pubkey-path=keys/xt-com.pub #第3个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.3.pubkey=3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk +#第3个参与方的角色清单;可选项; +cons_parti.3.roles=GUEST +#第3个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.3.roles-policy=INTERSECT #第3个参与方的共识服务的主机地址; cons_parti.3.consensus.host=127.0.0.1 #第3个参与方的共识服务的端口; diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java index 994272b0..f1fba9b1 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java @@ -263,6 +263,10 @@ public abstract class PropertiesUtils { public static String getRequiredProperty(Properties props, String key) { return getProperty(props, key, true); } + + public static String getOptionalProperty(Properties props, String key) { + return getProperty(props, key, false); + } /** * 返回指定的属性;
        diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java index 5ae1a856..fea16ace 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java @@ -1,24 +1,75 @@ package com.jd.blockchain.utils; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; import java.util.regex.Pattern; /** - * @Author zhaogw - * date 2018/11/26 20:46 + * @Author zhaogw date 2018/11/26 20:46 */ public class StringUtils { - public static boolean isEmpty(Object str) { - return str == null || "".equals(str); - } - - /* - * 判断是否为整数 - * @param str 传入的字符串 - * @return 是整数返回true,否则返回false - */ - - public static boolean isNumber(String str) { - Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$"); - return pattern.matcher(str).matches(); - } + + public static final String[] EMPTY_ARRAY = {}; + + public static boolean isEmpty(Object str) { + return str == null || "".equals(str); + } + + /* + * 判断是否为整数 + * + * @param str 传入的字符串 + * + * @return 是整数返回true,否则返回false + */ + + public static boolean isNumber(String str) { + Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$"); + return pattern.matcher(str).matches(); + } + + /** + * 按照指定的分隔符把字符串分解为字符数组,同时截掉每一个元素两端的空白字符,并忽略掉空字符元素; + * + * @param str 要被截断的字符串; + * @param delimiter 分隔符; + * @return + */ + public static String[] splitToArray(String str, String delimiter) { + return splitToArray(str, delimiter, true, true); + } + + /** + * 按照指定的分隔符把字符串分解为字符数组 + * + * @param str 要被截断的字符串; + * @param delimiter 分隔符; + * @param trimElement 是否截断元素两端的空白字符; + * @param ignoreEmptyElement 是否忽略空字符元素; + * @return + */ + public static String[] splitToArray(String str, String delimiter, boolean trimElement, boolean ignoreEmptyElement) { + if (str == null) { + return null; + } + if (trimElement) { + str = str.trim(); + } + if (str.length() == 0) { + return EMPTY_ARRAY; + } + StringTokenizer tokenizer = new StringTokenizer(str, delimiter); + List tokens = new ArrayList<>(); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (trimElement) { + token = token.trim(); + } + if ((!ignoreEmptyElement) || token.length() > 0) { + tokens.add(token); + } + } + return tokens.toArray(new String[tokens.size()]); + } } \ No newline at end of file From a572df5c98c53ecddf6618d36f2dba250bf9ebde Mon Sep 17 00:00:00 2001 From: zhaoguangwei Date: Thu, 29 Aug 2019 14:55:41 +0800 Subject: [PATCH 064/124] use the 512m --- .../deployment-peer/src/main/resources/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh b/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh index d0fdd3d5..c316ed7d 100644 --- a/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh +++ b/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh @@ -5,5 +5,5 @@ PEER=$(ls $HOME/system | grep deployment-peer-) if [ ! -n "$PEER" ]; then echo "Peer Is Null !!!" else - nohup java -jar -server -Xmx2g -Xms2g -Dpeer.log=$HOME $HOME/system/$PEER -home=$HOME -c $HOME/config/ledger-binding.conf -p 7080 $* >$HOME/bin/peer.out 2>&1 & + nohup java -jar -server -Xmx512m -Xms512m -Dpeer.log=$HOME $HOME/system/$PEER -home=$HOME -c $HOME/config/ledger-binding.conf -p 7080 $* >$HOME/bin/peer.out 2>&1 & fi \ No newline at end of file From 13ae4f71a8b65a88a2ca8f10ceb5de76a9be95e4 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Thu, 29 Aug 2019 17:31:34 +0800 Subject: [PATCH 065/124] add update participant state interface --- .../com/jd/blockchain/consts/DataCodes.java | 5 +- .../ledger/core/LedgerAdminAccount.java | 9 ++ .../ledger/core/ParticipantDataSet.java | 19 ++++ .../DefaultOperationHandleRegisteration.java | 1 + ...ParticipantStateUpdateOperationHandle.java | 84 +++++++++++++++++ .../ledger/ParticipantStateUpdateInfo.java | 31 +++++++ .../ParticipantStateUpdateInfoData.java | 31 +++++++ .../ParticipantStateUpdateOperation.java | 11 +++ .../BlockchainOperationFactory.java | 16 ++++ .../transaction/ClientOperator.java | 2 +- .../transaction/ParticipantStateOperator.java | 10 ++ .../ParticipantStateUpdateOpTemplate.java | 23 +++++ ...articipantStateUpdateOperationBuilder.java | 18 ++++ ...cipantStateUpdateOperationBuilderImpl.java | 12 +++ .../jd/blockchain/transaction/TxBuilder.java | 3 + .../jd/blockchain/transaction/TxTemplate.java | 6 ++ ...SDK_GateWay_Participant_Regist_Test_.java} | 2 +- ...ateWay_Participant_State_Update_Test_.java | 91 +++++++++++++++++++ .../jd/blockchain/intgr/IntegrationBase.java | 27 ++++++ .../intgr/IntegrationTest4Bftsmart.java | 23 ++++- .../blockchain/intgr/IntegrationTest4MQ.java | 23 ++++- 21 files changed, 442 insertions(+), 5 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateOperator.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java rename source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/{SDK_GateWay_Participant_Test_.java => SDK_GateWay_Participant_Regist_Test_.java} (98%) create mode 100644 source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 8f0d8d3d..600053e8 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -50,6 +50,7 @@ public interface DataCodes { public static final int TX_OP_CONTRACT_EVENT_SEND = 0x340; public static final int TX_OP_PARTICIPANT_REG = 0x350; + public static final int TX_OP_PARTICIPANT_STATE_UPDATE = 0x351; public static final int TX_RESPONSE = 0x360; @@ -71,7 +72,9 @@ public interface DataCodes { public static final int METADATA_CONSENSUS_SETTING = 0x631; - public static final int METADATA_PARTICIPANT_INFO = 0x640; + public static final int METADATA_PARTICIPANT_INFO = 0x640; + + public static final int METADATA_PARTICIPANT_STATE_INFO = 0x641; public static final int METADATA_CRYPTO_SETTING = 0x642; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java index c22a4ad2..87219e84 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java @@ -249,6 +249,15 @@ public class LedgerAdminAccount implements Transactional, LedgerAdministration { participants.addConsensusParticipant(participant); } + /** + * 更新参与方的状态参数; + * + * @param participant + */ + public void updateParticipant(ParticipantNode participant) { + participants.updateConsensusParticipant(participant); + } + @Override public boolean isUpdated() { return updated || participants.isUpdated(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java index bd84185d..44f82e21 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataSet.java @@ -73,6 +73,25 @@ public class ParticipantDataSet implements Transactional, MerkleProvable { } } + /** + * 更新共识参与方的状态信息;
        + * + * @param participant + */ + public void updateConsensusParticipant(ParticipantNode participant) { + Bytes key = encodeKey(participant.getAddress()); + byte[] participantBytes = BinaryProtocol.encode(participant, ParticipantNode.class); + long version = dataset.getVersion(key); + if (version < 0) { + throw new LedgerException("Participant not exist, update failed!"); + } + + long nv = dataset.setValue(key, participantBytes, version); + if (nv < 0) { + throw new LedgerException("Participant update failed!"); + } + } + private Bytes encodeKey(String address) { // return id + ""; return Bytes.fromString(address); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java index d7db478d..fc888b29 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java @@ -28,6 +28,7 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis opHandles.add(new ParticipantRegisterOperationHandle()); opHandles.add(new ContractCodeDeployOperationHandle()); opHandles.add(new JVMContractEventSendOperationHandle()); + opHandles.add(new ParticipantStateUpdateOperationHandle()); } /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java new file mode 100644 index 00000000..bb0d5b65 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java @@ -0,0 +1,84 @@ +package com.jd.blockchain.ledger.core.impl.handles; + +import com.jd.blockchain.crypto.AddressEncoding; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.ledger.core.impl.OperationHandleContext; + +public class ParticipantStateUpdateOperationHandle implements OperationHandle { + + @Override + public boolean support(Class operationType) { + return ParticipantStateUpdateOperation.class.isAssignableFrom(operationType); + } + + @Override + public BytesValue process(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + + ParticipantStateUpdateOperation stateUpdateOperation = (ParticipantStateUpdateOperation) op; + + LedgerAdminAccount adminAccount = newBlockDataset.getAdminAccount(); + + ParticipantNode[] participants = adminAccount.getParticipants(); + + ParticipantNode participantNode = null; + + for(int i = 0; i < participants.length; i++) { + if (stateUpdateOperation.getStateUpdateInfo().getPubKey().equals(participants[i].getPubKey())) { + participantNode = new PartNode(participants[i].getId(), participants[i].getName(), participants[i].getPubKey(), ParticipantNodeState.CONSENSUSED); + } + } + + adminAccount.updateParticipant(participantNode); + + return null; + } + + private static class PartNode implements ParticipantNode { + + private int id; + + private String address; + + private String name; + + private PubKey pubKey; + + private ParticipantNodeState participantNodeState; + + public PartNode(int id, String name, PubKey pubKey, ParticipantNodeState participantNodeState) { + this.id = id; + this.name = name; + this.pubKey = pubKey; + this.address = AddressEncoding.generateAddress(pubKey).toBase58(); + this.participantNodeState = participantNodeState; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getAddress() { + return address; + } + + @Override + public String getName() { + return name; + } + + @Override + public PubKey getPubKey() { + return pubKey; + } + + @Override + public ParticipantNodeState getParticipantNodeState() { + return participantNodeState; + } + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java new file mode 100644 index 00000000..8452ac92 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java @@ -0,0 +1,31 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.crypto.PubKey; + +/** + * 参与方状态更新信息; + * + * + */ +@DataContract(code = DataCodes.METADATA_PARTICIPANT_STATE_INFO) +public interface ParticipantStateUpdateInfo { + /** + * 公钥; + * + * @return + */ + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + PubKey getPubKey(); + + /** + * 参与方状态; + * + * @return + */ + @DataField(order = 2, refEnum = true) + ParticipantNodeState getState(); +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java new file mode 100644 index 00000000..1f4a4fc5 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java @@ -0,0 +1,31 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.crypto.PubKey; + +public class ParticipantStateUpdateInfoData implements ParticipantStateUpdateInfo { + private PubKey pubKey; + private ParticipantNodeState state; + + public ParticipantStateUpdateInfoData(PubKey pubKey, ParticipantNodeState state) { + this.pubKey = pubKey; + this.state = state; + } + + public void setPubKey(PubKey pubKey) { + this.pubKey = pubKey; + } + + @Override + public PubKey getPubKey() { + return pubKey; + } + + public void setState(ParticipantNodeState state) { + this.state = state; + } + + @Override + public ParticipantNodeState getState() { + return state; + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java new file mode 100644 index 00000000..1f656609 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java @@ -0,0 +1,11 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.consts.DataCodes; + +@DataContract(code= DataCodes.TX_OP_PARTICIPANT_STATE_UPDATE) +public interface ParticipantStateUpdateOperation extends Operation { + @DataField(order=1, refContract = true) + ParticipantStateUpdateInfo getStateUpdateInfo(); +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index e9d7fa77..df064ad8 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -25,6 +25,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private static final ParticipantRegisterOperationBuilderImpl PARTICIPANT_REG_OP_BUILDER = new ParticipantRegisterOperationBuilderImpl(); + private static final ParticipantStateUpdateOperationBuilderImpl PARTICIPANT_STATE_UPDATE_OP_BUILDER = new ParticipantStateUpdateOperationBuilderImpl(); + private LedgerInitOperationBuilder ledgerInitOpBuilder = new LedgerInitOperationBuilderFilter(); private UserRegisterOperationBuilder userRegOpBuilder = new UserRegisterOperationBuilderFilter(); @@ -39,6 +41,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private ParticipantRegisterOperationBuilder participantRegOpBuilder = new ParticipantRegisterOperationBuilderFilter(); + private ParticipantStateUpdateOperationBuilder participantStateModifyOpBuilder = new ParticipantStateUpdateOperationBuilderFilter(); + // TODO: 暂时只支持单线程情形,未考虑多线程; private List operationList = new ArrayList<>(); @@ -79,6 +83,9 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe @Override public ParticipantRegisterOperationBuilder participants() {return participantRegOpBuilder;} + @Override + public ParticipantStateUpdateOperationBuilder states() {return participantStateModifyOpBuilder;} + @Override public T contract(String address, Class contractIntf) { return contractInvoProxyBuilder.create(address, contractIntf, contractEventSendOpBuilder); @@ -262,6 +269,15 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe } } + private class ParticipantStateUpdateOperationBuilderFilter implements ParticipantStateUpdateOperationBuilder { + @Override + public ParticipantStateUpdateOperation update(ParticipantStateUpdateInfo stateUpdateInfo) { + ParticipantStateUpdateOperation op = PARTICIPANT_STATE_UPDATE_OP_BUILDER.update(stateUpdateInfo); + operationList.add(op); + return op; + } + } + private class ContractEventSendOperationBuilderFilter implements ContractEventSendOperationBuilder { @Override diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java index 8797b4ce..ac1dc9f7 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java @@ -6,6 +6,6 @@ package com.jd.blockchain.transaction; * @author huanghaiquan * */ -public interface ClientOperator extends UserOperator, DataAccountOperator, ContractOperator, EventOperator, ParticipantOperator { +public interface ClientOperator extends UserOperator, DataAccountOperator, ContractOperator, EventOperator, ParticipantOperator, ParticipantStateOperator{ } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateOperator.java new file mode 100644 index 00000000..22554031 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateOperator.java @@ -0,0 +1,10 @@ +package com.jd.blockchain.transaction; + +public interface ParticipantStateOperator { + /** + * 参与方状态更新操作; + * + * @return + */ + ParticipantStateUpdateOperationBuilder states(); +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java new file mode 100644 index 00000000..9a410a1e --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java @@ -0,0 +1,23 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; +import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; + +public class ParticipantStateUpdateOpTemplate implements ParticipantStateUpdateOperation { + + static { + DataContractRegistry.register(ParticipantStateUpdateOperation.class); + } + + private ParticipantStateUpdateInfo stateUpdateInfo; + + public ParticipantStateUpdateOpTemplate(ParticipantStateUpdateInfo stateUpdateInfo) { + this.stateUpdateInfo = stateUpdateInfo; + } + + @Override + public ParticipantStateUpdateInfo getStateUpdateInfo() { + return stateUpdateInfo; + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java new file mode 100644 index 00000000..afb66bff --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java @@ -0,0 +1,18 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; +import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; + +public interface ParticipantStateUpdateOperationBuilder { + + /** + * 更新参与方状态,已注册->参与共识; + * + * @param + * + * @param + * + * @return + */ + ParticipantStateUpdateOperation update(ParticipantStateUpdateInfo stateUpdateInfo); +} \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java new file mode 100644 index 00000000..7c579fd7 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java @@ -0,0 +1,12 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; +import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; + +public class ParticipantStateUpdateOperationBuilderImpl implements ParticipantStateUpdateOperationBuilder { + + @Override + public ParticipantStateUpdateOperation update(ParticipantStateUpdateInfo stateUpdateInfo) { + return new ParticipantStateUpdateOpTemplate(stateUpdateInfo); + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java index 9a810c56..b3b7ca7b 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java @@ -113,6 +113,9 @@ public class TxBuilder implements TransactionBuilder { @Override public ParticipantRegisterOperationBuilder participants() {return opFactory.participants(); } + @Override + public ParticipantStateUpdateOperationBuilder states() {return opFactory.states(); } + @Override public T contract(Bytes address, Class contractIntf) { return opFactory.contract(address, contractIntf); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java index e448db0e..7b407f61 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java @@ -71,6 +71,12 @@ public class TxTemplate implements TransactionTemplate { return txBuilder.participants(); } + @Override + public ParticipantStateUpdateOperationBuilder states() { + stateManager.operate(); + return txBuilder.states(); + } + @Override public T contract(Bytes address, Class contractIntf) { stateManager.operate(); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java similarity index 98% rename from source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java rename to source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java index c4e0e76a..382e1581 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertTrue; * @since 1.0.0 */ -public class SDK_GateWay_Participant_Test_ { +public class SDK_GateWay_Participant_Regist_Test_ { private PrivKey privKey; private PubKey pubKey; diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java new file mode 100644 index 00000000..c286dc66 --- /dev/null +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java @@ -0,0 +1,91 @@ +package test.com.jd.blockchain.sdk.test; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.*; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.sdk.samples.SDKDemo_Constant; +import com.jd.blockchain.tools.keygen.KeyGenCommand; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * 参与方状态更新测试 + * @author zhangshuang + * @create 2019/7/18 + * @since 1.0.0 + */ +public class SDK_GateWay_Participant_State_Update_Test_ { + private PrivKey privKey; + private PubKey pubKey; + + private BlockchainKeypair CLIENT_CERT = null; + + private String GATEWAY_IPADDR = null; + + private int GATEWAY_PORT; + + private boolean SECURE; + + private BlockchainService service; + + //根据密码工具产生的公私钥 + static String PUB = "3snPdw7i7PkdgqiGX7GbZuFSi1cwZn7vtjw4vifb1YoXgr9k6Kfmis"; + String PRIV = "177gjtZu8w1phqHFVNiFhA35cfimXmP6VuqrBFhfbXBWK8s4TRwro2tnpffwP1Emwr6SMN6"; + + @Before + public void init() { + + privKey = SDK_GateWay_KeyPair_Para.privkey1; + pubKey = SDK_GateWay_KeyPair_Para.pubKey1; + + CLIENT_CERT = new BlockchainKeypair(SDK_GateWay_KeyPair_Para.pubKey0, SDK_GateWay_KeyPair_Para.privkey0); + GATEWAY_IPADDR = "127.0.0.1"; + GATEWAY_PORT = 11000; + SECURE = false; + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, + CLIENT_CERT); + service = serviceFactory.getBlockchainService(); + + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); + } + + @Test + public void updateParticipantState_Test() { + HashDigest[] ledgerHashs = service.getLedgerHashs(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); + + //existed signer + AsymmetricKeypair keyPair = new BlockchainKeypair(pubKey, privKey); + + PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV, SDKDemo_Constant.PASSWORD); + + PubKey pubKey = KeyGenCommand.decodePubKey(PUB); + + System.out.println("Address = "+AddressEncoding.generateAddress(pubKey)); + + + ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(pubKey, ParticipantNodeState.CONSENSUSED); + txTemp.states().update(stateUpdateInfo); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + + // 使用私钥进行签名; + prepTx.sign(keyPair); + + // 提交交易; + TransactionResponse transactionResponse = prepTx.commit(); + assertTrue(transactionResponse.isSuccess()); + + } +} diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index c553dcd9..32c02b75 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -25,6 +25,7 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import org.apache.commons.io.FileUtils; import org.springframework.core.io.ClassPathResource; @@ -195,6 +196,32 @@ public class IntegrationBase { return keyPairResponse; } + public static KeyPairResponse testSDK_UpdateParticipantState(AsymmetricKeypair adminKey, BlockchainKeypair participantKeyPair, HashDigest ledgerHash, + BlockchainService blockchainService) { + // 定义交易; + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + + ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(participantKeyPair.getPubKey(), ParticipantNodeState.CONSENSUSED); + + txTpl.states().update(stateUpdateInfo); + + // 签名; + PreparedTransaction ptx = txTpl.prepare(); + + HashDigest transactionHash = ptx.getHash(); + + ptx.sign(adminKey); + + // 提交并等待共识返回; + TransactionResponse txResp = ptx.commit(); + + KeyPairResponse keyPairResponse = new KeyPairResponse(); + keyPairResponse.keyPair = participantKeyPair; + keyPairResponse.txResp = txResp; + keyPairResponse.txHash = transactionHash; + return keyPairResponse; + } + public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerRepository ledgerRepository, KeyPairType keyPairType) { TransactionResponse txResp = keyPairResponse.txResp; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index fd93a2c0..f9954ce1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -6,6 +6,9 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.ParticipantNodeState; +import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; +import com.jd.blockchain.ledger.ParticipantStateUpdateInfoData; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; @@ -32,6 +35,8 @@ public class IntegrationTest4Bftsmart { private static final boolean isRegisterParticipant = true; + private static final boolean isParticipantStateUpdate = true; + private static final boolean isWriteKv = true; private static final String DB_TYPE_MEM = "mem"; @@ -148,8 +153,9 @@ public class IntegrationTest4Bftsmart { System.out.printf("before add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + IntegrationBase.KeyPairResponse participantResponse; if (isRegisterParticipant) { - IntegrationBase.KeyPairResponse participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); + participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); } participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); @@ -158,6 +164,21 @@ public class IntegrationTest4Bftsmart { System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + System.out.println("update participant state before \r\n"); + + for (int i = 0; i < participantCount; i++) { + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + } + + if (isParticipantStateUpdate) { + IntegrationBase.testSDK_UpdateParticipantState(adminKey, new BlockchainKeypair(participantResponse.getKeyPair().getPubKey(), participantResponse.getKeyPair().getPrivKey()), ledgerHash, blockchainService); + } + + System.out.println("update participant state after\r\n"); + + for (int i = 0; i < participantCount; i++) { + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + } try { System.out.println("----------------- Init Completed -----------------"); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index f7315d17..216010cb 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -32,6 +32,8 @@ public class IntegrationTest4MQ { private static final boolean isRegisterParticipant = true; + private static final boolean isParticipantStateUpdate = true; + private static final boolean isWriteKv = true; private static final boolean isContract = false; @@ -146,8 +148,9 @@ public class IntegrationTest4MQ { System.out.printf("before add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + IntegrationBase.KeyPairResponse participantResponse; if (isRegisterParticipant) { - IntegrationBase.KeyPairResponse participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); + participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); } participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); @@ -156,6 +159,24 @@ public class IntegrationTest4MQ { System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); + + System.out.println("update participant state before \r\n"); + + for (int i = 0; i < participantCount; i++) { + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + } + + if (isParticipantStateUpdate) { + IntegrationBase.testSDK_UpdateParticipantState(adminKey, new BlockchainKeypair(participantResponse.getKeyPair().getPubKey(), participantResponse.getKeyPair().getPrivKey()), ledgerHash, blockchainService); + } + + System.out.println("update participant state after\r\n"); + + for (int i = 0; i < participantCount; i++) { + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + } + IntegrationBase.testConsistencyAmongNodes(ledgers); if(isOnline){ From 3c26584a9f2cc2b5823c805f8707b255b5306f5e Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Thu, 29 Aug 2019 18:32:37 +0800 Subject: [PATCH 066/124] print bug --- .../java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index 216010cb..a3735e1d 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -159,8 +159,6 @@ public class IntegrationTest4MQ { System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); - System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); - System.out.println("update participant state before \r\n"); for (int i = 0; i < participantCount; i++) { From 642e532641e00b307cc18c8af83f1f926bf66715 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 29 Aug 2019 23:12:03 +0800 Subject: [PATCH 067/124] Completed the roles configuration API of SDK; --- .../com/jd/blockchain/consts/DataCodes.java | 8 + .../jd/blockchain/ledger/PrivilegeBitset.java | 2 + .../ledger/RolesConfigureOperation.java | 39 +++++ .../jd/blockchain/ledger/SecurityUtils.java | 28 ++++ .../ledger/UserInfoSetOperation.java | 62 ++++---- .../ledger/UserRegisterOperation.java | 8 +- .../ledger/UserRoleAuthorizeOperation.java | 53 +++++++ .../BlockchainOperationFactory.java | 21 +++ .../transaction/ClientOperator.java | 3 +- .../transaction/RolePrivilegeConfigurer.java | 19 +++ .../transaction/RolesConfigureOpTemplate.java | 137 ++++++++++++++++++ .../transaction/RolesConfigurer.java | 11 ++ .../transaction/SecurityOperationBuilder.java | 16 ++ .../SecurityOperationBuilderImpl.java | 10 ++ .../transaction/SecurityOperator.java | 23 +++ .../jd/blockchain/transaction/TxBuilder.java | 5 + .../jd/blockchain/transaction/TxTemplate.java | 11 +- .../UserRoleAuthorizeOpTemplate.java | 93 ++++++++++++ .../samples/SDKDemo_ConfigureSecurity.java | 89 ++++++++++++ .../web/LedgerInitializeWebController.java | 9 +- .../com/jd/blockchain/utils/ArrayUtils.java | 7 + 21 files changed, 612 insertions(+), 42 deletions(-) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesConfigureOperation.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityUtils.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperator.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java create mode 100644 source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 7358ba4e..0baf7b40 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -54,6 +54,14 @@ public interface DataCodes { public static final int TX_RESPONSE = 0x350; public static final int TX_OP_RESULT = 0x360; + + public static final int TX_OP_ROLE_CONFIGURE = 0x370; + + public static final int TX_OP_ROLE_CONFIGURE_ENTRY = 0x371; + + public static final int TX_OP_USER_ROLE_AUTHORIZE = 0x372; + + public static final int TX_OP_USER_ROLE_AUTHORIZE_ENTRY = 0x373; // enum types of permissions; public static final int ENUM_TX_PERMISSION = 0x401; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java index aea812f9..1282530d 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java @@ -86,6 +86,7 @@ public class PrivilegeBitset> implements Privilege, BytesSe * @param privileges * @return */ + @SuppressWarnings("unchecked") public Privilege union(PrivilegeBitset... privileges) { return union(privileges, 0, privileges.length); } @@ -112,6 +113,7 @@ public class PrivilegeBitset> implements Privilege, BytesSe * @param privileges * @return */ + @SuppressWarnings("unchecked") public Privilege intersect(PrivilegeBitset... privileges) { return intersect(privileges, 0, privileges.length); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesConfigureOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesConfigureOperation.java new file mode 100644 index 00000000..1e448740 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolesConfigureOperation.java @@ -0,0 +1,39 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +/** + * 角色配置操作; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.TX_OP_ROLE_CONFIGURE) +public interface RolesConfigureOperation extends Operation { + + @DataField(order = 2, refContract = true, list = true) + RolePrivilegeEntry[] getRoles(); + + @DataContract(code = DataCodes.TX_OP_ROLE_CONFIGURE_ENTRY) + public static interface RolePrivilegeEntry { + + @DataField(order = 1, primitiveType = PrimitiveType.TEXT) + String getRoleName(); + + @DataField(order = 2, refEnum = true, list = true) + LedgerPermission[] getEnableLedgerPermissions(); + + @DataField(order = 3, refEnum = true, list = true) + LedgerPermission[] getDisableLedgerPermissions(); + + @DataField(order = 4, refEnum = true, list = true) + TransactionPermission[] getEnableTransactionPermissions(); + + @DataField(order = 5, refEnum = true, list = true) + TransactionPermission[] getDisableTransactionPermissions(); + + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityUtils.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityUtils.java new file mode 100644 index 00000000..b0175fc5 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityUtils.java @@ -0,0 +1,28 @@ +package com.jd.blockchain.ledger; + +public class SecurityUtils { + + public static final int MAX_ROLE_NAMES = 20; + + /** + * 校验角色名称的有效性,并格式化角色名称:去掉两端空白字符,统一为大写字符; + * + * @param roleName + * @return + */ + public static String formatRoleName(String roleName) { + if (roleName == null) { + throw new IllegalArgumentException("Role name is empty!"); + } + roleName = roleName.trim(); + if (roleName.length() > MAX_ROLE_NAMES) { + throw new IllegalArgumentException("Role name exceeds max length!"); + } + if (roleName.length() == 0) { + throw new IllegalArgumentException("Role name is empty!"); + } + + return roleName.toUpperCase(); + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfoSetOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfoSetOperation.java index b9a92317..c6372b78 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfoSetOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserInfoSetOperation.java @@ -1,33 +1,29 @@ -//package com.jd.blockchain.ledger; -// -//import com.jd.blockchain.binaryproto.DataContract; -// -///** -// * @author huanghaiquan -// * -// */ -//@DataContract(code=LedgerCodes.TX_OP_USER_INFO_SET) -//public interface UserInfoSetOperation extends Operation { -// -// @Override -// default OperationType getType() { -// return OperationType.SET_USER_INFO; -// } -// -// String getUserAddress(); -// -// KVEntry[] getPropertiesWriteSet(); -// -// -// @DataContract(code=LedgerCodes.TX_OP_USER_INFO_SET_KV) -// public static interface KVEntry{ -// -// String getKey(); -// -// String getValue(); -// -// long getExpectedVersion(); -// } -// -// -//} +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.consts.DataCodes; + +/** + * @author huanghaiquan + * + */ +@DataContract(code=DataCodes.TX_OP_USER_INFO_SET) +public interface UserInfoSetOperation extends Operation { + + String getUserAddress(); + + KVEntry[] getPropertiesWriteSet(); + + + @DataContract(code=DataCodes.TX_OP_USER_INFO_SET_KV) + public static interface KVEntry{ + + String getKey(); + + String getValue(); + + long getExpectedVersion(); + } + + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java index f325085e..caa642de 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRegisterOperation.java @@ -4,10 +4,10 @@ import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.consts.DataCodes; -@DataContract(code= DataCodes.TX_OP_USER_REG) +@DataContract(code = DataCodes.TX_OP_USER_REG) public interface UserRegisterOperation extends Operation { - - @DataField(order=2, refContract = true) - BlockchainIdentity getUserID(); + @DataField(order = 2, refContract = true) + BlockchainIdentity getUserID(); + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java new file mode 100644 index 00000000..12b230f0 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java @@ -0,0 +1,53 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.utils.Bytes; + +/** + * 角色配置操作; + * + * @author huanghaiquan + * + */ +@DataContract(code = DataCodes.TX_OP_USER_ROLE_AUTHORIZE) +public interface UserRoleAuthorizeOperation extends Operation { + + @DataField(order = 2, refContract = true, list = true) + UserRoleAuthEntry[] getUserRoleAuthorizations(); + + @DataContract(code = DataCodes.TX_OP_USER_ROLE_AUTHORIZE_ENTRY) + public static interface UserRoleAuthEntry { + + @DataField(order = 0, primitiveType = PrimitiveType.BYTES) + Bytes getUserAddress(); + + @DataField(order = 2, primitiveType = PrimitiveType.INT64) + long getExplectedVersion(); + + /** + * 要更新的多角色权限策略; + * @return + */ + RolesPolicy getRolesPolicy(); + + /** + * 授权的角色清单; + * + * @return + */ + @DataField(order = 1, primitiveType = PrimitiveType.TEXT) + String[] getAuthRoles(); + + /** + * 取消授权的角色清单; + * + * @return + */ + @DataField(order = 1, primitiveType = PrimitiveType.TEXT) + String[] getUnauthRoles(); + + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index ef9b138a..d95a3d1f 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -22,6 +22,8 @@ import com.jd.blockchain.utils.Bytes; * */ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOperator { + + private static final SecurityOperationBuilderImpl SECURITY_OP_BUILDER = new SecurityOperationBuilderImpl(); private static final LedgerInitOperationBuilderImpl LEDGER_INIT_OP_BUILDER = new LedgerInitOperationBuilderImpl(); @@ -32,6 +34,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private static final ContractCodeDeployOperationBuilderImpl CONTRACT_CODE_DEPLOY_OP_BUILDER = new ContractCodeDeployOperationBuilderImpl(); // private static final ContractEventSendOperationBuilderImpl CONTRACT_EVENT_SEND_OP_BUILDER = new ContractEventSendOperationBuilderImpl(); + + private SecurityOperationBuilderFilter securityOpBuilder = new SecurityOperationBuilderFilter(); private LedgerInitOperationBuilder ledgerInitOpBuilder = new LedgerInitOperationBuilderFilter(); @@ -52,6 +56,11 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe public LedgerInitOperationBuilder ledgers() { return ledgerInitOpBuilder; } + + @Override + public SecurityOperationBuilder security() { + return securityOpBuilder; + } @Override public UserRegisterOperationBuilder users() { @@ -155,6 +164,18 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe return op; } + } + + private class SecurityOperationBuilderFilter implements SecurityOperationBuilder { + + @Override + public RolesConfigurer roles() { + RolesConfigurer rolesConfigurer = SECURITY_OP_BUILDER.roles(); + operationList.add(rolesConfigurer.getOperation()); + return rolesConfigurer; + } + + } private class DataAccountRegisterOperationBuilderFilter implements DataAccountRegisterOperationBuilder { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java index 5e47ef89..2aec03bc 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java @@ -6,6 +6,7 @@ package com.jd.blockchain.transaction; * @author huanghaiquan * */ -public interface ClientOperator extends UserOperator, DataAccountOperator, ContractOperator, EventOperator { +public interface ClientOperator + extends SecurityOperator, UserOperator, DataAccountOperator, ContractOperator, EventOperator { } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java new file mode 100644 index 00000000..cfdd3a6f --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java @@ -0,0 +1,19 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.TransactionPermission; + +public interface RolePrivilegeConfigurer { + + String getRoleName(); + + RolePrivilegeConfigurer disable(TransactionPermission... permissions); + + RolePrivilegeConfigurer enable(TransactionPermission... permissions); + + RolePrivilegeConfigurer disable(LedgerPermission... permissions); + + RolePrivilegeConfigurer enable(LedgerPermission... permissions); + + RolePrivilegeConfigurer configure(String roleName); +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java new file mode 100644 index 00000000..5ff5fea0 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java @@ -0,0 +1,137 @@ +package com.jd.blockchain.transaction; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.RolesConfigureOperation; +import com.jd.blockchain.ledger.SecurityUtils; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.utils.ArrayUtils; + +public class RolesConfigureOpTemplate implements RolesConfigurer, RolesConfigureOperation { + + static { + DataContractRegistry.register(UserRegisterOperation.class); + } + + private Map rolesMap = Collections + .synchronizedMap(new LinkedHashMap()); + + public RolesConfigureOpTemplate() { + } + + boolean isEmpty() { + return rolesMap.isEmpty(); + } + + @Override + public RolePrivilegeEntry[] getRoles() { + return rolesMap.values().toArray(new RolePrivilegeEntry[rolesMap.size()]); + } + + @Override + public RolesConfigureOperation getOperation() { + return this; + } + + @Override + public RolePrivilegeConfigurer configure(String roleName) { + roleName = SecurityUtils.formatRoleName(roleName); + + RolePrivilegeConfig roleConfig = rolesMap.get(roleName); + if (roleConfig == null) { + roleConfig = new RolePrivilegeConfig(roleName); + rolesMap.put(roleName, roleConfig); + } + return roleConfig; + } + + private class RolePrivilegeConfig implements RolePrivilegeConfigurer, RolePrivilegeEntry { + + private String roleName; + + private Set enableLedgerPermissions = new LinkedHashSet(); + private Set disableLedgerPermissions = new LinkedHashSet(); + + private Set enableTxPermissions = new LinkedHashSet(); + private Set disableTxPermissions = new LinkedHashSet(); + + private RolePrivilegeConfig(String roleName) { + this.roleName = roleName; + } + + @Override + public String getRoleName() { + return roleName; + } + + @Override + public LedgerPermission[] getEnableLedgerPermissions() { + return ArrayUtils.toArray(enableLedgerPermissions, LedgerPermission.class); + } + + @Override + public LedgerPermission[] getDisableLedgerPermissions() { + return ArrayUtils.toArray(disableLedgerPermissions, LedgerPermission.class); + } + + @Override + public TransactionPermission[] getEnableTransactionPermissions() { + return ArrayUtils.toArray(enableTxPermissions, TransactionPermission.class); + } + + @Override + public TransactionPermission[] getDisableTransactionPermissions() { + return ArrayUtils.toArray(disableTxPermissions, TransactionPermission.class); + } + + @Override + public RolePrivilegeConfigurer enable(LedgerPermission... permissions) { + List permissionList = ArrayUtils.asList(permissions); + enableLedgerPermissions.addAll(permissionList); + disableLedgerPermissions.removeAll(permissionList); + + return this; + } + + @Override + public RolePrivilegeConfigurer disable(LedgerPermission... permissions) { + List permissionList = ArrayUtils.asList(permissions); + disableLedgerPermissions.addAll(permissionList); + enableLedgerPermissions.removeAll(permissionList); + + return this; + } + + @Override + public RolePrivilegeConfigurer enable(TransactionPermission... permissions) { + List permissionList = ArrayUtils.asList(permissions); + enableTxPermissions.addAll(permissionList); + disableTxPermissions.removeAll(permissionList); + + return this; + } + + @Override + public RolePrivilegeConfigurer disable(TransactionPermission... permissions) { + List permissionList = ArrayUtils.asList(permissions); + disableTxPermissions.addAll(permissionList); + enableTxPermissions.removeAll(permissionList); + + return this; + } + + @Override + public RolePrivilegeConfigurer configure(String roleName) { + return RolesConfigureOpTemplate.this.configure(roleName); + } + + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java new file mode 100644 index 00000000..16adf9a3 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java @@ -0,0 +1,11 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.RolesConfigureOperation; + +public interface RolesConfigurer { + + RolesConfigureOperation getOperation(); + + RolePrivilegeConfigurer configure(String roleName); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java new file mode 100644 index 00000000..692a08e7 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java @@ -0,0 +1,16 @@ +package com.jd.blockchain.transaction; + +public interface SecurityOperationBuilder { + + /** + * 注册; + * + * @param id + * 区块链身份; + * @param stateType + * 负载类型; + * @return + */ + RolesConfigurer roles(); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java new file mode 100644 index 00000000..dd5a87e5 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java @@ -0,0 +1,10 @@ +package com.jd.blockchain.transaction; + +public class SecurityOperationBuilderImpl implements SecurityOperationBuilder{ + + @Override + public RolesConfigurer roles() { + return new RolesConfigureOpTemplate(); + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperator.java new file mode 100644 index 00000000..a636c577 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperator.java @@ -0,0 +1,23 @@ +package com.jd.blockchain.transaction; + +/** + * 与安全配置相关的操作门面; + * + *
        + * + * 只能通过客户端接口直接操作;不支持通过合约操作; + * + * @author huanghaiquan + * + */ +public interface SecurityOperator { + + /** + * 注册账户操作; + * + * @return + */ + + SecurityOperationBuilder security(); + +} \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java index 1ff23a2f..8c582333 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxBuilder.java @@ -75,6 +75,11 @@ public class TxBuilder implements TransactionBuilder { public Collection getReturnValuehandlers() { return opFactory.getReturnValuetHandlers(); } + + @Override + public SecurityOperationBuilder security() { + return opFactory.security(); + } @Override public LedgerInitOperationBuilder ledgers() { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java index 9777d238..40d7d3cd 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxTemplate.java @@ -35,6 +35,12 @@ public class TxTemplate implements TransactionTemplate { return new PreparedTx(stateManager, txReqBuilder, txService, txBuilder.getReturnValuehandlers()); } + @Override + public SecurityOperationBuilder security() { + stateManager.operate(); + return txBuilder.security(); + } + @Override public UserRegisterOperationBuilder users() { stateManager.operate(); @@ -80,9 +86,10 @@ public class TxTemplate implements TransactionTemplate { @Override public void close() throws IOException { if (!stateManager.close()) { - Collection handlers = txBuilder.getReturnValuehandlers(); + Collection handlers = txBuilder.getReturnValuehandlers(); if (handlers.size() > 0) { - TransactionCancelledExeption error = new TransactionCancelledExeption("Transaction template has been cancelled!"); + TransactionCancelledExeption error = new TransactionCancelledExeption( + "Transaction template has been cancelled!"); for (OperationResultHandle handle : handlers) { handle.complete(error); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java new file mode 100644 index 00000000..fc425987 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java @@ -0,0 +1,93 @@ +package com.jd.blockchain.transaction; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.UserRoleAuthorizeOperation; +import com.jd.blockchain.utils.ArrayUtils; +import com.jd.blockchain.utils.Bytes; + +public class UserRoleAuthorizeOpTemplate implements UserRoleAuthorizeOperation { + + static { + DataContractRegistry.register(UserRegisterOperation.class); + } + + private Map rolesMap = new LinkedHashMap(); + + public UserRoleAuthorizeOpTemplate() { + } + + public UserRoleAuthorizeOpTemplate(BlockchainIdentity userID) { + } + + @Override + public UserRoleAuthConfig[] getUserRoleAuthorizations() { + return ArrayUtils.toArray(rolesMap.values(), UserRoleAuthConfig.class); + } + + public static class UserRoleAuthConfig implements UserRoleAuthEntry { + + private Bytes userAddress; + + private long expectedVersion; + + private RolesPolicy rolePolicy; + + private Set authRoles = new LinkedHashSet(); + private Set unauthRoles = new LinkedHashSet(); + + private UserRoleAuthConfig(Bytes userAddress, long expectedVersion) { + this.userAddress = userAddress; + + } + + @Override + public Bytes getUserAddress() { + return userAddress; + } + + @Override + public long getExplectedVersion() { + return expectedVersion; + } + + @Override + public RolesPolicy getRolesPolicy() { + return rolePolicy; + } + + @Override + public String[] getAuthRoles() { + return ArrayUtils.toArray(authRoles, String.class); + } + + @Override + public String[] getUnauthRoles() { + return ArrayUtils.toArray(unauthRoles, String.class); + } + + public UserRoleAuthConfig authorize(String... roles) { + Collection roleList = ArrayUtils.asList(roles); + authRoles.addAll(roleList); + unauthRoles.removeAll(roleList); + + return this; + } + + public UserRoleAuthConfig unauthorize(String... roles) { + Collection roleList = ArrayUtils.asList(roles); + unauthRoles.addAll(roleList); + authRoles.removeAll(roleList); + + return this; + } + } +} diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java new file mode 100644 index 00000000..5d78e92c --- /dev/null +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java @@ -0,0 +1,89 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.sdk.samples.SDKDemo_RegisterUser + * Author: shaozhuguang + * Department: 区块链研发部 + * Date: 2018/10/18 下午2:00 + * Description: 注册用户 + */ +package com.jd.blockchain.sdk.samples; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.AsymmetricKeypair; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.utils.ConsoleUtils; + +/** + * 注册用户 + * + * @author shaozhuguang + * @create 2018/10/18 + * @since 1.0.0 + */ + +public class SDKDemo_ConfigureSecurity { + public static void main(String[] args) { + + String GATEWAY_IPADDR = "127.0.0.1"; + int GATEWAY_PORT = 8081; + if (args != null && args.length == 2) { + GATEWAY_IPADDR = args[0]; + GATEWAY_PORT = Integer.parseInt(args[1]); + } + + // 注册相关class + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); + + PrivKey privKey = SDKDemo_Params.privkey1; + PubKey pubKey = SDKDemo_Params.pubKey1; + + BlockchainKeypair CLIENT_CERT = new BlockchainKeypair(SDKDemo_Params.pubKey0, SDKDemo_Params.privkey0); + + boolean SECURE = false; + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, + CLIENT_CERT); + BlockchainService service = serviceFactory.getBlockchainService(); + + HashDigest[] ledgerHashs = service.getLedgerHashs(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); + + // existed signer + AsymmetricKeypair signer = getSigner(); + + BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); + + // 注册 + txTemp.users().register(user.getIdentity()); + + txTemp.security().roles().configure("ADMIN") + .enable(LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT) + .enable(TransactionPermission.DIRECT_OPERATION).configure("GUEST") + .enable(TransactionPermission.CONTRACT_OPERATION); + + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); + + // 使用私钥进行签名; + prepTx.sign(signer); + + // 提交交易; + TransactionResponse transactionResponse = prepTx.commit(); + + ConsoleUtils.info("register user complete, result is [%s]", transactionResponse.isSuccess()); + } + + private static AsymmetricKeypair getSigner() { + return new BlockchainKeypair(SDKDemo_Params.pubKey1, SDKDemo_Params.privkey1); + } +} \ No newline at end of file diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index eab29145..2b930628 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -178,8 +178,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI Properties csProps = ledgerInitProps.getConsensusConfig(); ConsensusProvider csProvider = ConsensusProviders.getProvider(ledgerInitProps.getConsensusProvider()); - ConsensusSettings csSettings = csProvider.getSettingsFactory() - .getConsensusSettingsBuilder() + ConsensusSettings csSettings = csProvider.getSettingsFactory().getConsensusSettingsBuilder() .createSettings(csProps, ledgerInitProps.getConsensusParticipantNodes()); setConsensusProvider(csProvider); @@ -405,6 +404,12 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI return decision; } + /** + * 初始化账本数据,返回创始区块; + * + * @param ledgerEditor + * @return + */ private LedgerBlock initLedgerDataset(LedgerEditor ledgerEditor) { // 初始化时,自动将参与方注册为账本的用户; TxRequestBuilder txReqBuilder = new TxRequestBuilder(this.initTxContent); diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java index c787a014..3f2c10a6 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java @@ -30,6 +30,13 @@ public abstract class ArrayUtils { return array; } + public static T[] toArray(Collection collection, Class clazz){ + @SuppressWarnings("unchecked") + T[] array = (T[]) Array.newInstance(clazz, collection.size()); + collection.toArray(array); + return array; + } + public static List asList(T[] array){ return asList(array, 0, array.length); } From 9c45e482129d07e55d158a2492948b5645d04869 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 30 Aug 2019 00:15:43 +0800 Subject: [PATCH 068/124] Complete the user-roles authorization API of SDK; --- .../com/jd/blockchain/consts/DataCodes.java | 2 +- ...ation.java => UserAuthorizeOperation.java} | 30 ++-- .../BlockchainOperationFactory.java | 7 +- .../jd/blockchain/transaction/PreparedTx.java | 5 - .../transaction/RolePrivilegeConfigurer.java | 3 +- .../transaction/RolesConfigure.java | 7 + .../transaction/RolesConfigurer.java | 4 +- .../transaction/SecurityOperationBuilder.java | 13 +- .../SecurityOperationBuilderImpl.java | 5 + .../transaction/TxRequestBuilder.java | 3 - .../blockchain/transaction/UserAuthorize.java | 12 ++ .../transaction/UserAuthorizeOpTemplate.java | 131 ++++++++++++++++++ .../transaction/UserAuthorizer.java | 9 ++ .../UserRoleAuthorizeOpTemplate.java | 93 ------------- .../transaction/UserRolesAuthorizer.java | 13 ++ .../samples/SDKDemo_ConfigureSecurity.java | 12 +- 16 files changed, 221 insertions(+), 128 deletions(-) rename source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/{UserRoleAuthorizeOperation.java => UserAuthorizeOperation.java} (55%) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigure.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizer.java delete mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRolesAuthorizer.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 0baf7b40..3a2055f8 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -59,7 +59,7 @@ public interface DataCodes { public static final int TX_OP_ROLE_CONFIGURE_ENTRY = 0x371; - public static final int TX_OP_USER_ROLE_AUTHORIZE = 0x372; + public static final int TX_OP_USER_ROLES_AUTHORIZE = 0x372; public static final int TX_OP_USER_ROLE_AUTHORIZE_ENTRY = 0x373; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java similarity index 55% rename from source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java index 12b230f0..d46bd1a6 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleAuthorizeOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java @@ -12,42 +12,46 @@ import com.jd.blockchain.utils.Bytes; * @author huanghaiquan * */ -@DataContract(code = DataCodes.TX_OP_USER_ROLE_AUTHORIZE) -public interface UserRoleAuthorizeOperation extends Operation { +@DataContract(code = DataCodes.TX_OP_USER_ROLES_AUTHORIZE) +public interface UserAuthorizeOperation extends Operation { @DataField(order = 2, refContract = true, list = true) - UserRoleAuthEntry[] getUserRoleAuthorizations(); + UserRolesEntry[] getUserRolesAuthorizations(); @DataContract(code = DataCodes.TX_OP_USER_ROLE_AUTHORIZE_ENTRY) - public static interface UserRoleAuthEntry { + public static interface UserRolesEntry { + /** + * 用户地址; + * + * @return + */ @DataField(order = 0, primitiveType = PrimitiveType.BYTES) Bytes getUserAddress(); - @DataField(order = 2, primitiveType = PrimitiveType.INT64) - long getExplectedVersion(); - /** * 要更新的多角色权限策略; + * * @return */ - RolesPolicy getRolesPolicy(); + @DataField(order = 2, refEnum = true) + RolesPolicy getPolicy(); /** * 授权的角色清单; * * @return */ - @DataField(order = 1, primitiveType = PrimitiveType.TEXT) - String[] getAuthRoles(); - + @DataField(order = 3, primitiveType = PrimitiveType.TEXT, list = true) + String[] getAuthorizedRoles(); + /** * 取消授权的角色清单; * * @return */ - @DataField(order = 1, primitiveType = PrimitiveType.TEXT) - String[] getUnauthRoles(); + @DataField(order = 4, primitiveType = PrimitiveType.TEXT, list = true) + String[] getUnauthorizedRoles(); } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index d95a3d1f..efd2ead2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -175,7 +175,12 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe return rolesConfigurer; } - + @Override + public UserAuthorizer authorziations() { + UserAuthorizer userAuthorizer = SECURITY_OP_BUILDER.authorziations(); + operationList.add(userAuthorizer.getOperation()); + return userAuthorizer; + } } private class DataAccountRegisterOperationBuilderFilter implements DataAccountRegisterOperationBuilder { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/PreparedTx.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/PreparedTx.java index cb39f6b6..bba464e3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/PreparedTx.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/PreparedTx.java @@ -7,13 +7,8 @@ import java.util.Comparator; import org.springframework.cglib.proxy.UndeclaredThrowableException; -import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.AsymmetricKeypair; -import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.SignatureDigest; -import com.jd.blockchain.crypto.SignatureFunction; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.OperationResult; import com.jd.blockchain.ledger.PreparedTransaction; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java index cfdd3a6f..13539536 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolePrivilegeConfigurer.java @@ -3,7 +3,7 @@ package com.jd.blockchain.transaction; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.TransactionPermission; -public interface RolePrivilegeConfigurer { +public interface RolePrivilegeConfigurer extends RolesConfigure { String getRoleName(); @@ -15,5 +15,4 @@ public interface RolePrivilegeConfigurer { RolePrivilegeConfigurer enable(LedgerPermission... permissions); - RolePrivilegeConfigurer configure(String roleName); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigure.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigure.java new file mode 100644 index 00000000..4626fa5a --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigure.java @@ -0,0 +1,7 @@ +package com.jd.blockchain.transaction; + +public interface RolesConfigure { + + RolePrivilegeConfigurer configure(String roleName); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java index 16adf9a3..0621a626 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigurer.java @@ -2,10 +2,8 @@ package com.jd.blockchain.transaction; import com.jd.blockchain.ledger.RolesConfigureOperation; -public interface RolesConfigurer { +public interface RolesConfigurer extends RolesConfigure { RolesConfigureOperation getOperation(); - - RolePrivilegeConfigurer configure(String roleName); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java index 692a08e7..f3b6622c 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilder.java @@ -3,14 +3,17 @@ package com.jd.blockchain.transaction; public interface SecurityOperationBuilder { /** - * 注册; + * 配置角色; * - * @param id - * 区块链身份; - * @param stateType - * 负载类型; * @return */ RolesConfigurer roles(); + /** + * 授权用户; + * + * @return + */ + UserAuthorizer authorziations(); + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java index dd5a87e5..ce271fce 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/SecurityOperationBuilderImpl.java @@ -7,4 +7,9 @@ public class SecurityOperationBuilderImpl implements SecurityOperationBuilder{ return new RolesConfigureOpTemplate(); } + @Override + public UserAuthorizer authorziations() { + return new UserAuthorizeOpTemplate(); + } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java index ef4df764..d8627974 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java @@ -7,9 +7,6 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.NodeRequest; import com.jd.blockchain.ledger.TransactionContent; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java new file mode 100644 index 00000000..deb184d3 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java @@ -0,0 +1,12 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.utils.Bytes; + +public interface UserAuthorize { + + UserRolesAuthorizer forUser(BlockchainIdentity userId); + + UserRolesAuthorizer forUser(Bytes userAddress); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java new file mode 100644 index 00000000..40670e8c --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java @@ -0,0 +1,131 @@ +package com.jd.blockchain.transaction; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.SecurityUtils; +import com.jd.blockchain.ledger.UserAuthorizeOperation; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.utils.ArrayUtils; +import com.jd.blockchain.utils.Bytes; + +public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOperation { + + static { + DataContractRegistry.register(UserRegisterOperation.class); + } + + private Map userAuthMap = Collections + .synchronizedMap(new LinkedHashMap()); + + public UserAuthorizeOpTemplate() { + } + + public UserAuthorizeOpTemplate(BlockchainIdentity userID) { + } + + @Override + public UserRolesAuthorization[] getUserRolesAuthorizations() { + return ArrayUtils.toArray(userAuthMap.values(), UserRolesAuthorization.class); + } + + @Override + public UserAuthorizeOperation getOperation() { + return this; + } + + @Override + public UserRolesAuthorizer forUser(Bytes userAddress) { + UserRolesAuthorization userRolesAuth = userAuthMap.get(userAddress); + if (userRolesAuth == null) { + userRolesAuth = new UserRolesAuthorization(userAddress); + userAuthMap.put(userAddress, userRolesAuth); + } + return userRolesAuth; + } + + @Override + public UserRolesAuthorizer forUser(BlockchainIdentity userId) { + return forUser(userId.getAddress()); + } + + private class UserRolesAuthorization implements UserRolesAuthorizer, UserRolesEntry { + + private Bytes userAddress; + + private RolesPolicy policy = RolesPolicy.UNION; + + private Set authRoles = new LinkedHashSet(); + private Set unauthRoles = new LinkedHashSet(); + + private UserRolesAuthorization(Bytes userAddress) { + this.userAddress = userAddress; + } + + @Override + public Bytes getUserAddress() { + return userAddress; + } + + @Override + public RolesPolicy getPolicy() { + return policy; + } + + @Override + public String[] getAuthorizedRoles() { + return ArrayUtils.toArray(authRoles, String.class); + } + + @Override + public String[] getUnauthorizedRoles() { + return ArrayUtils.toArray(unauthRoles, String.class); + } + + @Override + public UserRolesAuthorizer setPolicy(RolesPolicy policy) { + this.policy = policy; + return this; + } + + @Override + public UserRolesAuthorizer authorize(String... roles) { + String roleName; + for (String r : roles) { + roleName = SecurityUtils.formatRoleName(r); + authRoles.add(roleName); + unauthRoles.remove(roleName); + } + + return this; + } + + @Override + public UserRolesAuthorizer unauthorize(String... roles) { + String roleName; + for (String r : roles) { + roleName = SecurityUtils.formatRoleName(r); + unauthRoles.add(roleName); + authRoles.remove(roleName); + } + + return this; + } + + @Override + public UserRolesAuthorizer forUser(BlockchainIdentity userId) { + return UserAuthorizeOpTemplate.this.forUser(userId); + } + + @Override + public UserRolesAuthorizer forUser(Bytes userAddress) { + return UserAuthorizeOpTemplate.this.forUser(userAddress); + } + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizer.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizer.java new file mode 100644 index 00000000..66f083c7 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizer.java @@ -0,0 +1,9 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.UserAuthorizeOperation; + +public interface UserAuthorizer extends UserAuthorize { + + UserAuthorizeOperation getOperation(); + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java deleted file mode 100644 index fc425987..00000000 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRoleAuthorizeOpTemplate.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.jd.blockchain.transaction; - -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.RolesPolicy; -import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.ledger.UserRoleAuthorizeOperation; -import com.jd.blockchain.utils.ArrayUtils; -import com.jd.blockchain.utils.Bytes; - -public class UserRoleAuthorizeOpTemplate implements UserRoleAuthorizeOperation { - - static { - DataContractRegistry.register(UserRegisterOperation.class); - } - - private Map rolesMap = new LinkedHashMap(); - - public UserRoleAuthorizeOpTemplate() { - } - - public UserRoleAuthorizeOpTemplate(BlockchainIdentity userID) { - } - - @Override - public UserRoleAuthConfig[] getUserRoleAuthorizations() { - return ArrayUtils.toArray(rolesMap.values(), UserRoleAuthConfig.class); - } - - public static class UserRoleAuthConfig implements UserRoleAuthEntry { - - private Bytes userAddress; - - private long expectedVersion; - - private RolesPolicy rolePolicy; - - private Set authRoles = new LinkedHashSet(); - private Set unauthRoles = new LinkedHashSet(); - - private UserRoleAuthConfig(Bytes userAddress, long expectedVersion) { - this.userAddress = userAddress; - - } - - @Override - public Bytes getUserAddress() { - return userAddress; - } - - @Override - public long getExplectedVersion() { - return expectedVersion; - } - - @Override - public RolesPolicy getRolesPolicy() { - return rolePolicy; - } - - @Override - public String[] getAuthRoles() { - return ArrayUtils.toArray(authRoles, String.class); - } - - @Override - public String[] getUnauthRoles() { - return ArrayUtils.toArray(unauthRoles, String.class); - } - - public UserRoleAuthConfig authorize(String... roles) { - Collection roleList = ArrayUtils.asList(roles); - authRoles.addAll(roleList); - unauthRoles.removeAll(roleList); - - return this; - } - - public UserRoleAuthConfig unauthorize(String... roles) { - Collection roleList = ArrayUtils.asList(roles); - unauthRoles.addAll(roleList); - authRoles.removeAll(roleList); - - return this; - } - } -} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRolesAuthorizer.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRolesAuthorizer.java new file mode 100644 index 00000000..2a58858a --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserRolesAuthorizer.java @@ -0,0 +1,13 @@ +package com.jd.blockchain.transaction; + +import com.jd.blockchain.ledger.RolesPolicy; + +public interface UserRolesAuthorizer extends UserAuthorize { + + UserRolesAuthorizer authorize(String... roles); + + UserRolesAuthorizer unauthorize(String... roles); + + UserRolesAuthorizer setPolicy(RolesPolicy rolePolicy); + +} diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java index 5d78e92c..d7d2170b 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_ConfigureSecurity.java @@ -66,11 +66,19 @@ public class SDKDemo_ConfigureSecurity { // 注册 txTemp.users().register(user.getIdentity()); - txTemp.security().roles().configure("ADMIN") + txTemp.security().roles() + .configure("ADMIN") .enable(LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT) - .enable(TransactionPermission.DIRECT_OPERATION).configure("GUEST") + .enable(TransactionPermission.DIRECT_OPERATION) + .configure("GUEST") .enable(TransactionPermission.CONTRACT_OPERATION); + txTemp.security().authorziations() + .forUser(user.getIdentity()) + .authorize("ADMIN", "MANAGER") + .forUser(CLIENT_CERT.getAddress()) + .authorize("GUEST"); + // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); From 133a7a162aa1cc1dbba24cd243f391dd5bf59501 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Sun, 1 Sep 2019 22:58:27 +0800 Subject: [PATCH 069/124] Fixed compilation errors; --- source/base/pom.xml | 8 -- source/gateway/pom.xml | 12 +- .../DefaultOperationHandleRegisteration.java | 79 +++++++++---- .../ledger/core/LedgerAdminDataset.java | 4 +- .../core/LedgerSecurityManagerImpl.java | 6 +- .../ledger/core/OperationHandle.java | 2 +- .../ledger/core/RolePrivilegeDataset.java | 6 + .../ledger/core/UserRoleDataset.java | 25 +++- .../AbstractLedgerOperationHandle.java | 9 +- ...ractContractEventSendOperationHandle.java} | 8 +- .../JVMContractEventSendOperationHandle.java | 6 +- .../RolesConfigureOperationHandle.java | 49 ++++++++ .../handles/UserAuthorizeOperationHandle.java | 72 ++++++++++++ .../core/serialize/LedgerBlockSerializer.java | 5 +- .../ledger/core/ContractInvokingHandle.java | 4 +- .../ledger/core/ContractInvokingTest.java | 2 +- .../ledger/core/LedgerAdminDatasetTest.java | 8 +- .../jd/blockchain/ledger/LedgerAdminInfo.java | 2 +- .../blockchain/ledger/LedgerPermission.java | 34 +++--- .../ledger/RolePrivilegeSettings.java | 2 + .../com/jd/blockchain/ledger/UserRoles.java | 19 +++ ...leSettings.java => UserRolesSettings.java} | 15 ++- source/peer/pom.xml | 10 +- source/pom.xml | 12 +- .../contract/samples/AssetContractImpl.java | 2 +- source/tools/tools-initializer/pom.xml | 8 +- .../initializer/LedgerInitPropertiesTest.java | 24 ++-- .../src/test/resources/ledger.init | 4 +- .../blockchain/mocker/MockerNodeContext.java | 7 +- .../handler/MockerContractExeHandle.java | 4 +- .../MockerOperationHandleRegister.java | 108 +++++++++--------- .../com/jd/blockchain/utils/StringUtils.java | 2 +- 32 files changed, 381 insertions(+), 177 deletions(-) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/{AbtractContractEventHandle.java => AbtractContractEventSendOperationHandle.java} (94%) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java rename source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/{UserRoleSettings.java => UserRolesSettings.java} (79%) diff --git a/source/base/pom.xml b/source/base/pom.xml index 367d7c8a..94592186 100644 --- a/source/base/pom.xml +++ b/source/base/pom.xml @@ -8,12 +8,4 @@ 1.1.0-SNAPSHOT base - - - - org.slf4j - slf4j-api - - - \ No newline at end of file diff --git a/source/gateway/pom.xml b/source/gateway/pom.xml index 6bd8122c..cb5478f6 100644 --- a/source/gateway/pom.xml +++ b/source/gateway/pom.xml @@ -75,7 +75,6 @@ commons-io commons-io - ${commons-io.version} @@ -98,18 +97,13 @@ org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - + - + org.springframework.boot diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java index 15d5a0f1..7ab3b129 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java @@ -1,7 +1,9 @@ package com.jd.blockchain.ledger.core; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.stereotype.Component; @@ -10,26 +12,31 @@ import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; import com.jd.blockchain.ledger.core.handles.JVMContractEventSendOperationHandle; +import com.jd.blockchain.ledger.core.handles.RolesConfigureOperationHandle; +import com.jd.blockchain.ledger.core.handles.UserAuthorizeOperationHandle; import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; @Component public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { - private List opHandles = new ArrayList<>(); + private static Map, OperationHandle> DEFAULT_HANDLES = new HashMap<>(); - public DefaultOperationHandleRegisteration() { - initDefaultHandles(); + private Map, OperationHandle> handles = new ConcurrentHashMap<>(); + + private Map, OperationHandle> cacheMapping = new ConcurrentHashMap<>(); + + static { + addDefaultHandle(new RolesConfigureOperationHandle()); + addDefaultHandle(new UserAuthorizeOperationHandle()); + addDefaultHandle(new DataAccountKVSetOperationHandle()); + addDefaultHandle(new DataAccountRegisterOperationHandle()); + addDefaultHandle(new UserRegisterOperationHandle()); + addDefaultHandle(new ContractCodeDeployOperationHandle()); + addDefaultHandle(new JVMContractEventSendOperationHandle()); } - /** - * 针对不采用bean依赖注入的方式来处理; - */ - private void initDefaultHandles() { - opHandles.add(new DataAccountKVSetOperationHandle()); - opHandles.add(new DataAccountRegisterOperationHandle()); - opHandles.add(new UserRegisterOperationHandle()); - opHandles.add(new ContractCodeDeployOperationHandle()); - opHandles.add(new JVMContractEventSendOperationHandle()); + private static void addDefaultHandle(OperationHandle handle) { + DEFAULT_HANDLES.put(handle.getOperationType(), handle); } /** @@ -37,9 +44,32 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis * * @param handle */ - public void insertAsTopPriority(OperationHandle handle) { - opHandles.remove(handle); - opHandles.add(0, handle); + public void registerHandle(OperationHandle handle) { + handles.put(handle.getOperationType(), handle); + } + + private OperationHandle getRegisteredHandle(Class operationType) { + OperationHandle hdl = handles.get(operationType); + if (hdl == null) { + for (Entry, OperationHandle> entry : handles.entrySet()) { + if (entry.getKey().isAssignableFrom(operationType)) { + hdl = entry.getValue(); + } + } + } + return hdl; + } + + private OperationHandle getDefaultHandle(Class operationType) { + OperationHandle hdl = DEFAULT_HANDLES.get(operationType); + if (hdl == null) { + for (Entry, OperationHandle> entry : DEFAULT_HANDLES.entrySet()) { + if (entry.getKey().isAssignableFrom(operationType)) { + hdl = entry.getValue(); + } + } + } + return hdl; } /* @@ -51,12 +81,19 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis */ @Override public OperationHandle getHandle(Class operationType) { - for (OperationHandle handle : opHandles) { - if (handle.support(operationType)) { - return handle; + OperationHandle hdl = cacheMapping.get(operationType); + if (hdl != null) { + return hdl; + } + hdl = getRegisteredHandle(operationType); + if (hdl == null) { + hdl = getDefaultHandle(operationType); + if (hdl == null) { + throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); } } - throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); + cacheMapping.put(operationType, hdl); + return hdl; } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index b85873a2..501e8d7d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -16,7 +16,7 @@ import com.jd.blockchain.ledger.LedgerMetadata_V2; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.RolePrivilegeSettings; -import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; import com.jd.blockchain.storage.service.VersioningKVStorage; @@ -105,7 +105,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { } @Override - public UserRoleSettings getUserRoles() { + public UserRolesSettings getUserRoles() { return userRoles; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java index 6b74c2ce..0a26de4c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java @@ -14,7 +14,7 @@ import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.utils.Bytes; @@ -28,7 +28,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private RolePrivilegeSettings rolePrivilegeSettings; - private UserRoleSettings userRolesSettings; + private UserRolesSettings userRolesSettings; // 用户的权限配置 private Map userPrivilegesCache = new ConcurrentHashMap<>(); @@ -36,7 +36,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private Map userRolesCache = new ConcurrentHashMap<>(); private Map rolesPrivilegeCache = new ConcurrentHashMap<>(); - public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserRoleSettings userRolesSettings) { + public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserRolesSettings userRolesSettings) { this.rolePrivilegeSettings = rolePrivilegeSettings; this.userRolesSettings = userRolesSettings; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java index 94affc27..d7bf15e8 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java @@ -11,7 +11,7 @@ public interface OperationHandle { * @param operationType * @return */ - boolean support(Class operationType); + Class getOperationType(); /** * 同步解析和执行操作; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java index c5685ecf..7e7bd238 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java @@ -283,4 +283,10 @@ public class RolePrivilegeDataset implements Transactional, MerkleProvable, Role public boolean isReadonly() { return dataset.isReadonly(); } + + @Override + public boolean contains(String roleName) { + Bytes key = encodeKey(roleName); + return dataset.getVersion(key) > -1; + } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java index 355f5973..9a88e39b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java @@ -1,5 +1,7 @@ package com.jd.blockchain.ledger.core; +import java.util.Collection; + import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.AuthorizationException; @@ -7,8 +9,8 @@ import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.RoleSet; import com.jd.blockchain.ledger.RolesPolicy; -import com.jd.blockchain.ledger.UserRoleSettings; import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVEntry; import com.jd.blockchain.storage.service.VersioningKVStorage; @@ -21,7 +23,7 @@ import com.jd.blockchain.utils.Transactional; * @author huanghaiquan * */ -public class UserRoleDataset implements Transactional, MerkleProvable, UserRoleSettings { +public class UserRoleDataset implements Transactional, MerkleProvable, UserRolesSettings { private MerkleDataSet dataset; @@ -84,6 +86,25 @@ public class UserRoleDataset implements Transactional, MerkleProvable, UserRoleS } } + /** + * 加入新的用户角色授权;
        + * + * 如果该用户的授权已经存在,则引发 {@link LedgerException} 异常; + * + * @param userAddress + * @param rolesPolicy + * @param roles + */ + @Override + public void addUserRoles(Bytes userAddress, RolesPolicy rolesPolicy, Collection roles) { + UserRoles roleAuth = new UserRoles(userAddress, -1, rolesPolicy); + roleAuth.addRoles(roles); + long nv = setUserRolesAuthorization(roleAuth); + if (nv < 0) { + throw new AuthorizationException("Roles authorization of User[" + userAddress + "] already exists!"); + } + } + /** * 设置用户角色授权;
        * 如果版本校验不匹配,则返回 -1; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java index 311dc9d5..89bc5cf6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java @@ -32,9 +32,14 @@ public abstract class AbstractLedgerOperationHandle impleme this.SUPPORTED_OPERATION_TYPE = supportedOperationType; } +// @Override +// public final boolean support(Class operationType) { +// return SUPPORTED_OPERATION_TYPE.isAssignableFrom(operationType); +// } + @Override - public final boolean support(Class operationType) { - return SUPPORTED_OPERATION_TYPE.isAssignableFrom(operationType); + public Class getOperationType() { + return SUPPORTED_OPERATION_TYPE; } @Override diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java similarity index 94% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java index 8a410f1a..edcb5db3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java @@ -22,11 +22,11 @@ import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionRequestExtension; @Service -public abstract class AbtractContractEventHandle implements OperationHandle { - +public abstract class AbtractContractEventSendOperationHandle implements OperationHandle { + @Override - public boolean support(Class operationType) { - return ContractEventSendOperation.class.isAssignableFrom(operationType); + public Class getOperationType() { + return ContractEventSendOperation.class; } @Override diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java index 018c4d8a..1cdd3fe6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java @@ -1,13 +1,13 @@ package com.jd.blockchain.ledger.core.handles; +import static com.jd.blockchain.utils.BaseConstant.CONTRACT_SERVICE_PROVIDER; + import com.jd.blockchain.contract.engine.ContractCode; import com.jd.blockchain.contract.engine.ContractEngine; import com.jd.blockchain.contract.engine.ContractServiceProviders; import com.jd.blockchain.ledger.core.ContractAccount; -import static com.jd.blockchain.utils.BaseConstant.CONTRACT_SERVICE_PROVIDER; - -public class JVMContractEventSendOperationHandle extends AbtractContractEventHandle { +public class JVMContractEventSendOperationHandle extends AbtractContractEventSendOperationHandle { private static final ContractEngine JVM_ENGINE; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java new file mode 100644 index 00000000..d0f95989 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java @@ -0,0 +1,49 @@ +package com.jd.blockchain.ledger.core.handles; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.RolesConfigureOperation; +import com.jd.blockchain.ledger.RolesConfigureOperation.RolePrivilegeEntry; +import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.OperationHandleContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; + +public class RolesConfigureOperationHandle extends AbstractLedgerOperationHandle { + public RolesConfigureOperationHandle() { + super(RolesConfigureOperation.class); + } + + @Override + protected void doProcess(RolesConfigureOperation operation, LedgerDataset newBlockDataset, + TransactionRequestExtension request, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(LedgerPermission.CONFIGURE_ROLES, MultiIdsPolicy.AT_LEAST_ONE); + + // 操作账本; + RolePrivilegeEntry[] rpcfgs = operation.getRoles(); + RolePrivilegeSettings rpSettings = newBlockDataset.getAdminDataset().getRolePrivileges(); + if (rpcfgs != null) { + for (RolePrivilegeEntry rpcfg : rpcfgs) { + RolePrivileges rp = rpSettings.getRolePrivilege(rpcfg.getRoleName()); + if (rp == null) { + rpSettings.addRolePrivilege(rpcfg.getRoleName(), rpcfg.getEnableLedgerPermissions(), + rpcfg.getEnableTransactionPermissions()); + } else { + rp.enable(rpcfg.getEnableLedgerPermissions()); + rp.enable(rpcfg.getEnableTransactionPermissions()); + + rp.disable(rpcfg.getDisableLedgerPermissions()); + rp.disable(rpcfg.getDisableTransactionPermissions()); + } + } + } + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java new file mode 100644 index 00000000..26ce7c4a --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java @@ -0,0 +1,72 @@ +package com.jd.blockchain.ledger.core.handles; + +import java.util.ArrayList; +import java.util.List; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.UserAuthorizeOperation; +import com.jd.blockchain.ledger.UserAuthorizeOperation.UserRolesEntry; +import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.OperationHandleContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; + +public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle { + public UserAuthorizeOperationHandle() { + super(UserAuthorizeOperation.class); + } + + @Override + protected void doProcess(UserAuthorizeOperation operation, LedgerDataset newBlockDataset, + TransactionRequestExtension request, LedgerDataset previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpoints(LedgerPermission.CONFIGURE_ROLES, MultiIdsPolicy.AT_LEAST_ONE); + + // 操作账本; + + UserRolesEntry[] urcfgs = operation.getUserRolesAuthorizations(); + UserRolesSettings urSettings = newBlockDataset.getAdminDataset().getUserRoles(); + RolePrivilegeSettings rolesSettings = newBlockDataset.getAdminDataset().getRolePrivileges(); + if (urcfgs != null) { + for (UserRolesEntry urcfg : urcfgs) { + // + String[] authRoles = urcfg.getAuthorizedRoles(); + List validRoles = new ArrayList(); + if (authRoles != null) { + for (String r : authRoles) { + if (rolesSettings.contains(r)) { + validRoles.add(r); + } + } + } + UserRoles ur = urSettings.getUserRoles(urcfg.getUserAddress()); + if (ur == null) { + RolesPolicy policy = urcfg.getPolicy(); + if (policy == null) { + policy = RolesPolicy.UNION; + } + urSettings.addUserRoles(urcfg.getUserAddress(), policy, validRoles); + } else { + ur.addRoles(validRoles); + ur.removeRoles(urcfg.getUnauthorizedRoles()); + + // 如果请求中设置了策略,才进行更新; + RolesPolicy policy = urcfg.getPolicy(); + if (policy != null) { + ur.setPolicy(policy); + } + } + } + } + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/serialize/LedgerBlockSerializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/serialize/LedgerBlockSerializer.java index 65b15129..09cedf9d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/serialize/LedgerBlockSerializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/serialize/LedgerBlockSerializer.java @@ -1,13 +1,12 @@ package com.jd.blockchain.ledger.core.serialize; +import java.lang.reflect.Type; + import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson.serializer.SerializeWriter; -import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.LedgerBlock; -import java.lang.reflect.Type; - public class LedgerBlockSerializer implements ObjectSerializer { @Override diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java index 83883d06..c804a64a 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingHandle.java @@ -8,10 +8,10 @@ import com.jd.blockchain.contract.engine.ContractCode; import com.jd.blockchain.contract.jvm.AbstractContractCode; import com.jd.blockchain.contract.jvm.ContractDefinition; import com.jd.blockchain.ledger.core.ContractAccount; -import com.jd.blockchain.ledger.core.handles.AbtractContractEventHandle; +import com.jd.blockchain.ledger.core.handles.AbtractContractEventSendOperationHandle; import com.jd.blockchain.utils.Bytes; -public class ContractInvokingHandle extends AbtractContractEventHandle { +public class ContractInvokingHandle extends AbtractContractEventSendOperationHandle { private Map contractInstances = new ConcurrentHashMap(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index 335a21bd..d7807db3 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -61,7 +61,7 @@ public class ContractInvokingTest { // 注册合约处理器; DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - opReg.insertAsTopPriority(contractInvokingHandle); + opReg.registerHandle(contractInvokingHandle); // 发布指定地址合约 deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index bce41009..e591aa7f 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.UserRoleSettings; +import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminDataset; @@ -92,7 +92,7 @@ public class LedgerAdminDatasetTest { testStorage); ledgerAdminDataset.getRolePrivileges().addRolePrivilege("DEFAULT", - new LedgerPermission[] { LedgerPermission.AUTHORIZE_ROLES, LedgerPermission.REGISTER_USER, + new LedgerPermission[] { LedgerPermission.CONFIGURE_ROLES, LedgerPermission.REGISTER_USER, LedgerPermission.APPROVE_TX }, new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION }); @@ -225,7 +225,7 @@ public class LedgerAdminDatasetTest { } private void verifyRealoadingRoleAuthorizations(LedgerAdminInfo actualAccount, - RolePrivilegeSettings expRolePrivilegeSettings, UserRoleSettings expUserRoleSettings) { + RolePrivilegeSettings expRolePrivilegeSettings, UserRolesSettings expUserRoleSettings) { // 验证基本信息; RolePrivilegeSettings actualRolePrivileges = actualAccount.getRolePrivileges(); RolePrivileges[] expRPs = expRolePrivilegeSettings.getRolePrivileges(); @@ -239,7 +239,7 @@ public class LedgerAdminDatasetTest { assertArrayEquals(expRP.getTransactionPrivilege().toBytes(), actualRP.getTransactionPrivilege().toBytes()); } - UserRoleSettings actualUserRoleSettings = actualAccount.getUserRoles(); + UserRolesSettings actualUserRoleSettings = actualAccount.getUserRoles(); UserRoles[] expUserRoles = expUserRoleSettings.getUserRoles(); assertEquals(expUserRoles.length, actualUserRoleSettings.getUserCount()); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java index 035db6f2..b666535e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java @@ -10,7 +10,7 @@ public interface LedgerAdminInfo { ParticipantNode[] getParticipants(); - UserRoleSettings getUserRoles(); + UserRolesSettings getUserRoles(); RolePrivilegeSettings getRolePrivileges(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java index 82793392..a735a2da 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPermission.java @@ -15,57 +15,61 @@ import com.jd.blockchain.consts.DataCodes; public enum LedgerPermission { /** - * 授权角色权限;
        - * 包括:创建角色、设置角色的权限代码、分配用户角色; + * 配置角色的权限;
        */ - AUTHORIZE_ROLES((byte) 0x01), + CONFIGURE_ROLES((byte) 0x01), + + /** + * 授权用户角色;
        + */ + AUTHORIZE_USER_ROLES((byte) 0x02), /** * 设置共识协议;
        */ - SET_CONSENSUS((byte) 0x02), + SET_CONSENSUS((byte) 0x03), /** * 设置密码体系;
        */ - SET_CRYPTO((byte) 0x03), + SET_CRYPTO((byte) 0x04), /** * 注册参与方;
        */ - REGISTER_PARTICIPANT((byte) 0x04), + REGISTER_PARTICIPANT((byte) 0x05), /** * 注册用户;
        * * 如果不具备此项权限,则无法注册用户; */ - REGISTER_USER((byte) 0x05), + REGISTER_USER((byte) 0x11), /** * 注册数据账户;
        */ - REGISTER_DATA_ACCOUNT((byte) 0x06), + REGISTER_DATA_ACCOUNT((byte) 0x12), /** * 注册合约;
        */ - REGISTER_CONTRACT((byte) 0x07), + REGISTER_CONTRACT((byte) 0x13), /** * 升级合约 */ - UPGRADE_CONTRACT((byte) 0x08), + UPGRADE_CONTRACT((byte) 0x14), /** * 设置用户属性;
        */ - SET_USER_ATTRIBUTES((byte) 0x09), + SET_USER_ATTRIBUTES((byte) 0x15), /** * 写入数据账户;
        */ - WRITE_DATA_ACCOUNT((byte) 0x0A), + WRITE_DATA_ACCOUNT((byte) 0x16), /** * 参与方核准交易;
        @@ -74,16 +78,14 @@ public enum LedgerPermission { *

        * 只对交易请求的节点签名列表{@link TransactionRequest#getNodeSignatures()}的用户产生影响; */ - APPROVE_TX((byte) 0x0B), + APPROVE_TX((byte) 0x0C), /** * 参与方共识交易;
        * * 如果不具备此项权限,则无法作为共识节点接入并对交易进行共识; */ - CONSENSUS_TX((byte) 0x0C); - - + CONSENSUS_TX((byte) 0x0D); @EnumField(type = PrimitiveType.INT8) public final byte CODE; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java index b678d644..cdaa99e8 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RolePrivilegeSettings.java @@ -155,4 +155,6 @@ public interface RolePrivilegeSettings { long disablePermissions(String roleName, LedgerPermission[] ledgerPermissions, TransactionPermission[] txPermissions); + boolean contains(String r); + } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java index 42160fe3..d73d67a2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java @@ -1,5 +1,6 @@ package com.jd.blockchain.ledger; +import java.util.Collection; import java.util.Set; import java.util.TreeSet; @@ -72,6 +73,24 @@ public class UserRoles implements RoleSet { } } + public void addRoles(Collection roles) { + for (String r : roles) { + this.roles.add(r); + } + } + + public void removeRoles(String... roles) { + for (String r : roles) { + this.roles.remove(r); + } + } + + public void removeRoles(Collection roles) { + for (String r : roles) { + this.roles.remove(r); + } + } + /** * 设置角色集合;
        * 注意,这不是追加;现有的不在参数指定范围的角色将被移除; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRolesSettings.java similarity index 79% rename from source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRolesSettings.java index f7426ccf..af822b82 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoleSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRolesSettings.java @@ -1,8 +1,10 @@ package com.jd.blockchain.ledger; +import java.util.Collection; + import com.jd.blockchain.utils.Bytes; -public interface UserRoleSettings { +public interface UserRolesSettings { /** * 单一用户可被授权的角色数量的最大值; @@ -51,6 +53,17 @@ public interface UserRoleSettings { * @param roles */ void addUserRoles(Bytes userAddress, RolesPolicy rolesPolicy, String... roles); + + /** + * 加入新的用户角色授权;
        + * + * 如果该用户的授权已经存在,则引发 {@link LedgerException} 异常; + * + * @param userAddress + * @param rolesPolicy + * @param roles + */ + void addUserRoles(Bytes userAddress, RolesPolicy rolesPolicy, Collection roles); /** * 更新用户角色授权;
        diff --git a/source/peer/pom.xml b/source/peer/pom.xml index f428d188..a7127e68 100644 --- a/source/peer/pom.xml +++ b/source/peer/pom.xml @@ -54,18 +54,12 @@ org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - + org.springframework.boot diff --git a/source/pom.xml b/source/pom.xml index 6d34bb90..9dd10642 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -8,11 +8,6 @@ spring-boot-starter-parent 2.0.6.RELEASE - - - - - com.jd.blockchain jdchain-root @@ -45,9 +40,6 @@ 0.7.0.RELEASE 1.0.0-SNAPSHOT 2.4 - - 3.3.0 1.2.2 1.8.8 @@ -85,6 +77,10 @@ + + org.springframework.boot + spring-boot-starter-logging + junit junit diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java index 803d9d5c..d662378d 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/contract/samples/AssetContractImpl.java @@ -54,7 +54,7 @@ public class AssetContractImpl implements EventProcessingAware, AssetContract { KVDataObject currTotal = (KVDataObject) kvEntries[0]; long newTotal = currTotal.longValue() + amount; eventContext.getLedger().dataAccount(ASSET_ADDRESS).setInt64(KEY_TOTAL, newTotal, currTotal.getVersion()); - + // 分配到持有者账户; KVDataObject holderAmount = (KVDataObject) kvEntries[1]; long newHodlerAmount = holderAmount.longValue() + amount; diff --git a/source/tools/tools-initializer/pom.xml b/source/tools/tools-initializer/pom.xml index f4cfe07d..b4f51d28 100644 --- a/source/tools/tools-initializer/pom.xml +++ b/source/tools/tools-initializer/pom.xml @@ -72,18 +72,18 @@ org.springframework.boot spring-boot-starter-web - + - + org.springframework.boot diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java index 6e890a52..8b34623c 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java @@ -96,15 +96,18 @@ public class LedgerInitPropertiesTest { TransactionPermission.CONTRACT_OPERATION }, roleDefault.getTransactionPermissions()); RoleInitData roleAdmin = rolesInitDatas.get("ADMIN"); - assertArrayEquals(new LedgerPermission[] { LedgerPermission.AUTHORIZE_ROLES, LedgerPermission.SET_CONSENSUS, - LedgerPermission.SET_CRYPTO, LedgerPermission.REGISTER_PARTICIPANT, - LedgerPermission.REGISTER_USER }, roleAdmin.getLedgerPermissions()); + assertArrayEquals( + new LedgerPermission[] { LedgerPermission.CONFIGURE_ROLES, LedgerPermission.AUTHORIZE_USER_ROLES, + LedgerPermission.SET_CONSENSUS, LedgerPermission.SET_CRYPTO, + LedgerPermission.REGISTER_PARTICIPANT, LedgerPermission.REGISTER_USER }, + roleAdmin.getLedgerPermissions()); assertArrayEquals(new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION }, roleAdmin.getTransactionPermissions()); RoleInitData roleManager = rolesInitDatas.get("MANAGER"); assertArrayEquals( - new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT, + new LedgerPermission[] { LedgerPermission.CONFIGURE_ROLES, LedgerPermission.AUTHORIZE_USER_ROLES, + LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT, LedgerPermission.REGISTER_CONTRACT, LedgerPermission.UPGRADE_CONTRACT, LedgerPermission.SET_USER_ATTRIBUTES, LedgerPermission.WRITE_DATA_ACCOUNT }, roleManager.getLedgerPermissions()); @@ -127,7 +130,7 @@ public class LedgerInitPropertiesTest { // 验证参与方信息; assertEquals(4, initProps.getConsensusParticipantCount()); - + ConsensusParticipantConfig part0 = initProps.getConsensusParticipant(0); assertEquals("jd.com", part0.getName()); PubKey pubKey0 = KeyGenCommand.decodePubKey("3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9"); @@ -135,26 +138,25 @@ public class LedgerInitPropertiesTest { assertEquals("127.0.0.1", part0.getInitializerAddress().getHost()); assertEquals(8800, part0.getInitializerAddress().getPort()); assertEquals(true, part0.getInitializerAddress().isSecure()); - assertArrayEquals(new String[] {"ADMIN", "MANAGER"}, part0.getRoles()); + assertArrayEquals(new String[] { "ADMIN", "MANAGER" }, part0.getRoles()); assertEquals(RolesPolicy.UNION, part0.getRolesPolicy()); - ConsensusParticipantConfig part1 = initProps.getConsensusParticipant(1); assertEquals(false, part1.getInitializerAddress().isSecure()); PubKey pubKey1 = KeyGenCommand.decodePubKey("3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX"); assertEquals(pubKey1, part1.getPubKey()); - assertArrayEquals(new String[] { "MANAGER"}, part1.getRoles()); + assertArrayEquals(new String[] { "MANAGER" }, part1.getRoles()); assertEquals(RolesPolicy.UNION, part1.getRolesPolicy()); ConsensusParticipantConfig part2 = initProps.getConsensusParticipant(2); assertEquals("7VeRAr3dSbi1xatq11ZcF7sEPkaMmtZhV9shonGJWk9T4pLe", part2.getPubKey().toBase58()); - assertArrayEquals(new String[] { "MANAGER"}, part2.getRoles()); + assertArrayEquals(new String[] { "MANAGER" }, part2.getRoles()); assertEquals(RolesPolicy.UNION, part2.getRolesPolicy()); - + ConsensusParticipantConfig part3 = initProps.getConsensusParticipant(3); PubKey pubKey3 = KeyGenCommand.decodePubKey("3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"); assertEquals(pubKey3, part3.getPubKey()); - assertArrayEquals(new String[] { "GUEST"}, part3.getRoles()); + assertArrayEquals(new String[] { "GUEST" }, part3.getRoles()); assertEquals(RolesPolicy.INTERSECT, part3.getRolesPolicy()); } finally { diff --git a/source/tools/tools-initializer/src/test/resources/ledger.init b/source/tools/tools-initializer/src/test/resources/ledger.init index 275778b7..ebbd8872 100644 --- a/source/tools/tools-initializer/src/test/resources/ledger.init +++ b/source/tools/tools-initializer/src/test/resources/ledger.init @@ -37,11 +37,11 @@ security.role.DEFAULT.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION # 其它角色的配置示例; # 系统管理员角色:只能操作全局性的参数配置和用户注册,只能执行直接操作指令; -security.role.ADMIN.ledger-privileges=AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER +security.role.ADMIN.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER security.role.ADMIN.tx-privileges=DIRECT_OPERATION # 业务主管角色:只能够执行账本数据相关的操作,包括注册用户、注册数据账户、注册合约、升级合约、写入数据等;能够执行直接操作指令和调用合约; -security.role.MANAGER.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +security.role.MANAGER.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, security.role.MANAGER.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION # 访客角色:不具备任何的账本权限,只有数据读取的操作;也只能够通过调用合约来读取数据; diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index e29cd904..189b7f63 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -57,18 +57,19 @@ import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerSecurityManager; +import com.jd.blockchain.ledger.core.OperationHandleRegisteration; import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.mocker.config.MockerConstant; import com.jd.blockchain.mocker.config.PresetAnswerPrompter; import com.jd.blockchain.mocker.handler.MockerContractExeHandle; -import com.jd.blockchain.mocker.handler.MockerOperationHandleRegister; import com.jd.blockchain.mocker.proxy.ContractProxy; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.DbConnectionFactory; @@ -91,7 +92,7 @@ public class MockerNodeContext implements BlockchainQueryService { private DbConnectionFactory dbConnFactory = new MemoryDBConnFactory(); - private MockerOperationHandleRegister opHandler = new MockerOperationHandleRegister(); + private DefaultOperationHandleRegisteration opHandler = new DefaultOperationHandleRegisteration(); private MockerContractExeHandle contractExeHandle = new MockerContractExeHandle(); @@ -194,7 +195,7 @@ public class MockerNodeContext implements BlockchainQueryService { contractExeHandle.initLedger(ledgerManager, ledgerHash); - opHandler.registerHandler(contractExeHandle); + opHandler.registerHandle(contractExeHandle); return this; } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index 7683c3db..174e1f86 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -73,8 +73,8 @@ public class MockerContractExeHandle implements OperationHandle { } @Override - public boolean support(Class operationType) { - return ContractEventSendOperation.class.isAssignableFrom(operationType); + public Class getOperationType() { + return ContractEventSendOperation.class; } public void initLedger(LedgerManager ledgerManager, HashDigest ledgerHash) { diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java index 52c8392a..d22c6174 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerOperationHandleRegister.java @@ -1,54 +1,54 @@ -package com.jd.blockchain.mocker.handler; - -import java.util.ArrayList; -import java.util.List; - -import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.ledger.core.OperationHandle; -import com.jd.blockchain.ledger.core.OperationHandleRegisteration; -import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; -import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; -import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; -import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; - -public class MockerOperationHandleRegister implements OperationHandleRegisteration { - - private List opHandles = new ArrayList<>(); - - public MockerOperationHandleRegister() { - initDefaultHandles(); - } - - /** - * 针对不采用bean依赖注入的方式来处理; - */ - private void initDefaultHandles(){ - opHandles.add(new DataAccountKVSetOperationHandle()); - opHandles.add(new DataAccountRegisterOperationHandle()); - opHandles.add(new UserRegisterOperationHandle()); - opHandles.add(new ContractCodeDeployOperationHandle()); -// opHandles.add(new ContractEventSendOperationHandle()); - } - - public List getOpHandles() { - return opHandles; - } - - public void registerHandler(OperationHandle operationHandle) { - opHandles.add(operationHandle); - } - - public void removeHandler(OperationHandle operationHandle) { - opHandles.remove(operationHandle); - } - - @Override - public OperationHandle getHandle(Class operationType) { - for (OperationHandle handle : opHandles) { - if (handle.support(operationType)) { - return handle; - } - } - throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); - } -} +//package com.jd.blockchain.mocker.handler; +// +//import java.util.ArrayList; +//import java.util.List; +// +//import com.jd.blockchain.ledger.LedgerException; +//import com.jd.blockchain.ledger.core.OperationHandle; +//import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +//import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; +//import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; +//import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; +//import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; +// +//public class MockerOperationHandleRegister implements OperationHandleRegisteration { +// +// private List opHandles = new ArrayList<>(); +// +// public MockerOperationHandleRegister() { +// initDefaultHandles(); +// } +// +// /** +// * 针对不采用bean依赖注入的方式来处理; +// */ +// private void initDefaultHandles(){ +// opHandles.add(new DataAccountKVSetOperationHandle()); +// opHandles.add(new DataAccountRegisterOperationHandle()); +// opHandles.add(new UserRegisterOperationHandle()); +// opHandles.add(new ContractCodeDeployOperationHandle()); +//// opHandles.add(new ContractEventSendOperationHandle()); +// } +// +// public List getOpHandles() { +// return opHandles; +// } +// +// public void registerHandler(OperationHandle operationHandle) { +// opHandles.add(operationHandle); +// } +// +// public void removeHandler(OperationHandle operationHandle) { +// opHandles.remove(operationHandle); +// } +// +// @Override +// public OperationHandle getHandle(Class operationType) { +// for (OperationHandle handle : opHandles) { +// if (handle.support(operationType)) { +// return handle; +// } +// } +// throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); +// } +//} diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java index fea16ace..5761f464 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java @@ -51,7 +51,7 @@ public class StringUtils { */ public static String[] splitToArray(String str, String delimiter, boolean trimElement, boolean ignoreEmptyElement) { if (str == null) { - return null; + return EMPTY_ARRAY; } if (trimElement) { str = str.trim(); From e37c1fe05e9b1944b5b26882b2fa46242045506a Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 2 Sep 2019 10:25:15 +0800 Subject: [PATCH 070/124] Configured logback as default logging framework; --- .../com/jd/blockchain/ledger/core/CryptoConfig.java | 2 +- .../ledger-core/src/test/resources/logback-test.xml | 6 ++++++ .../src/test/resources/logback-test.xml | 6 ++++++ source/test/test-ledger-core/pom.xml | 6 ++++++ .../ledger/core/MerkleDatasetPerformanceTester.java | 11 ++++++++++- 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 source/ledger/ledger-core/src/test/resources/logback-test.xml create mode 100644 source/test/test-integration/src/test/resources/logback-test.xml diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/CryptoConfig.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/CryptoConfig.java index 8f5d2ea1..2a23f8bf 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/CryptoConfig.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/CryptoConfig.java @@ -82,7 +82,7 @@ public class CryptoConfig implements CryptoSetting { public void setHashAlgorithm(short hashAlgorithm) { if (codeAlgorithms == null || !codeAlgorithms.containsKey(hashAlgorithm)) { - throw new LedgerException("The specified algorithm[" + hashAlgorithm + "] has no provider!"); + throw new LedgerException("Current CryptoConfig has no crypto provider!"); } this.hashAlgorithm = hashAlgorithm; } diff --git a/source/ledger/ledger-core/src/test/resources/logback-test.xml b/source/ledger/ledger-core/src/test/resources/logback-test.xml new file mode 100644 index 00000000..29013782 --- /dev/null +++ b/source/ledger/ledger-core/src/test/resources/logback-test.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/test/test-integration/src/test/resources/logback-test.xml b/source/test/test-integration/src/test/resources/logback-test.xml new file mode 100644 index 00000000..29013782 --- /dev/null +++ b/source/test/test-integration/src/test/resources/logback-test.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/test/test-ledger-core/pom.xml b/source/test/test-ledger-core/pom.xml index c7350156..4d831fdc 100644 --- a/source/test/test-ledger-core/pom.xml +++ b/source/test/test-ledger-core/pom.xml @@ -20,10 +20,16 @@ storage-redis ${project.version} + + com.jd.blockchain + storage-rocksdb + ${project.version} + com.jd.blockchain crypto-classic ${project.version} + \ No newline at end of file diff --git a/source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java b/source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java index ef2f6064..bd614126 100644 --- a/source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java +++ b/source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java @@ -3,9 +3,10 @@ package test.perf.com.jd.blockchain.ledger.core; import java.io.IOException; import java.util.Random; -import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.MerkleDataSet; import com.jd.blockchain.storage.service.DbConnection; @@ -16,6 +17,8 @@ import com.jd.blockchain.storage.service.utils.MemoryKVStorage; public class MerkleDatasetPerformanceTester { + private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName() }; + private static final String MKL_KEY_PREFIX = ""; public static void main(String[] args) { @@ -131,7 +134,13 @@ public class MerkleDatasetPerformanceTester { VersioningKVStorage verStorage) { Random rand = new Random(); + CryptoProvider[] supportedProviders = new CryptoProvider[SUPPORTED_PROVIDERS.length]; + for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { + supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); + } + CryptoConfig cryptoConfig = new CryptoConfig(); + cryptoConfig.setSupportedProviders(supportedProviders); cryptoConfig.setHashAlgorithm(Crypto.getAlgorithm("SHA256")); cryptoConfig.setAutoVerifyHash(true); From 13d2218f299401e09fa33e9b0e4ce117ec02e81f Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Mon, 2 Sep 2019 17:20:09 +0800 Subject: [PATCH 071/124] modify participant state update interface bug --- .../src/main/java/com/jd/blockchain/ContractDeployExeUtil.java | 1 + .../test/com/jd/blockchain/ledger/ContractInvokingTest.java | 1 + .../java/test/com/jd/blockchain/ledger/LedgerManagerTest.java | 1 + .../java/test/com/jd/blockchain/ledger/TransactionSetTest.java | 1 + .../jd/blockchain/web/converters/BinaryMessageConverter.java | 1 + .../java/com/jd/blockchain/peer/web/ManagementController.java | 1 + .../com/jd/blockchain/sdk/client/GatewayServiceFactory.java | 1 + .../sdk/test/SDK_GateWay_Participant_Regist_Test_.java | 2 ++ .../sdk/test/SDK_GateWay_Participant_State_Update_Test_.java | 3 +++ .../com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java | 1 + .../com/jd/blockchain/capability/service/SettingsInit.java | 1 + .../main/java/com/jd/blockchain/mocker/MockerNodeContext.java | 2 ++ 12 files changed, 16 insertions(+) diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java index 24a25a90..157abcbb 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java @@ -95,6 +95,7 @@ public enum ContractDeployExeUtil { DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); } public BlockchainService initBcsrv(String host, int port) { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index 3ee6f875..0523ec64 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -33,6 +33,7 @@ public class ContractInvokingTest { DataContractRegistry.register(TransactionResponse.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); } private static final String LEDGER_KEY_PREFIX = "LDG://"; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java index a38e5ca8..3cd1a26d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java @@ -45,6 +45,7 @@ public class LedgerManagerTest { DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(BlockBody.class); DataContractRegistry.register(CryptoProvider.class); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java index ebfdc55a..bb7cec55 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionSetTest.java @@ -37,6 +37,7 @@ public class TransactionSetTest { DataContractRegistry.register(ContractCodeDeployOperation.class); DataContractRegistry.register(ContractEventSendOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); CryptoSetting defCryptoSetting = LedgerTestUtils.createDefaultCryptoSetting(); MemoryKVStorage testStorage = new MemoryKVStorage(); diff --git a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java b/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java index b2683e2f..0814de76 100644 --- a/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java +++ b/source/ledger/ledger-rpc/src/main/java/com/jd/blockchain/web/converters/BinaryMessageConverter.java @@ -47,6 +47,7 @@ public class BinaryMessageConverter extends AbstractHttpMessageConverter DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(ActionRequest.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index ec2443a3..04b71c41 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -103,6 +103,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java index df8a4991..7e0729a7 100644 --- a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java +++ b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java @@ -48,6 +48,7 @@ public class GatewayServiceFactory implements BlockchainServiceFactory, Closeabl DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(ActionRequest.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java index 382e1581..ec523269 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java @@ -59,6 +59,8 @@ public class SDK_GateWay_Participant_Regist_Test_ { DataContractRegistry.register(NodeRequest.class); DataContractRegistry.register(EndpointRequest.class); DataContractRegistry.register(TransactionResponse.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); } @Test diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java index c286dc66..f77397c8 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java @@ -56,6 +56,9 @@ public class SDK_GateWay_Participant_State_Update_Test_ { DataContractRegistry.register(NodeRequest.class); DataContractRegistry.register(EndpointRequest.class); DataContractRegistry.register(TransactionResponse.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); } @Test diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index 7f6ee769..baae348a 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -77,6 +77,7 @@ public class LedgerPerformanceTest { DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(DataAccountKVSetOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); } public static void test(String[] args) { diff --git a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java index f6c9bced..868a8e09 100644 --- a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java +++ b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java @@ -47,6 +47,7 @@ public class SettingsInit { DataContractRegistry.register(DataAccountRegisterOperation.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(ActionResponse.class); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 56aced5d..312bcc45 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.consensus.ClientIdentification; import com.jd.blockchain.consensus.ClientIdentifications; @@ -89,6 +90,7 @@ public class MockerNodeContext implements BlockchainQueryService { DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(DataAccountKVSetOperation.class); DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(ActionRequest.class); DataContractRegistry.register(ActionResponse.class); From d88feabb77efe853efe2c972faf3263bebe44921 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 3 Sep 2019 16:39:40 +0800 Subject: [PATCH 072/124] modify participant state update bug --- .../ParticipantRegisterOperationHandle.java | 12 ++++++------ .../ParticipantStateUpdateOperationHandle.java | 18 ++++++++++++++++++ .../ledger/ParticipantStateUpdateInfo.java | 12 +++++++++++- .../ledger/ParticipantStateUpdateInfoData.java | 14 +++++++++++++- ...GateWay_Participant_State_Update_Test_.java | 4 +++- .../jd/blockchain/intgr/IntegrationBase.java | 4 +++- 6 files changed, 54 insertions(+), 10 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java index 255a8eb9..d0e47e52 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantRegisterOperationHandle.java @@ -20,11 +20,11 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { ParticipantInfo participantInfo = participantRegOp.getParticipantInfo(); - ConsensusProvider provider = ConsensusProviders.getProvider(adminAccount.getSetting().getConsensusProvider()); +// ConsensusProvider provider = ConsensusProviders.getProvider(adminAccount.getSetting().getConsensusProvider()); ParticipantNode participantNode = new PartNode((int)(adminAccount.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTED); - LedgerAdminAccount.LedgerMetadataImpl metadata = (LedgerAdminAccount.LedgerMetadataImpl) adminAccount.getMetadata(); +// LedgerAdminAccount.LedgerMetadataImpl metadata = (LedgerAdminAccount.LedgerMetadataImpl) adminAccount.getMetadata(); PubKey pubKey = participantNode.getPubKey(); @@ -32,12 +32,12 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { BlockchainIdentityData identityData = new BlockchainIdentityData(pubKey); //update consensus setting - Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(metadata.getSetting().getConsensusSetting(), participantInfo); +// Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(metadata.getSetting().getConsensusSetting(), participantInfo); - LedgerSetting ledgerSetting = new LedgerConfiguration(adminAccount.getSetting().getConsensusProvider(), - newConsensusSettings, metadata.getSetting().getCryptoSetting()); +// LedgerSetting ledgerSetting = new LedgerConfiguration(adminAccount.getSetting().getConsensusProvider(), +// newConsensusSettings, metadata.getSetting().getCryptoSetting()); - metadata.setSetting(ledgerSetting); +// metadata.setSetting(ledgerSetting); // metadata.setViewId(metadata.getViewId() + 1); //reg participant as user diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java index bb0d5b65..0976651f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ParticipantStateUpdateOperationHandle.java @@ -1,10 +1,13 @@ package com.jd.blockchain.ledger.core.impl.handles; +import com.jd.blockchain.consensus.ConsensusProvider; +import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; import com.jd.blockchain.ledger.core.impl.OperationHandleContext; +import com.jd.blockchain.utils.Bytes; public class ParticipantStateUpdateOperationHandle implements OperationHandle { @@ -20,6 +23,10 @@ public class ParticipantStateUpdateOperationHandle implements OperationHandle { LedgerAdminAccount adminAccount = newBlockDataset.getAdminAccount(); + ConsensusProvider provider = ConsensusProviders.getProvider(adminAccount.getSetting().getConsensusProvider()); + + LedgerAdminAccount.LedgerMetadataImpl metadata = (LedgerAdminAccount.LedgerMetadataImpl) adminAccount.getMetadata(); + ParticipantNode[] participants = adminAccount.getParticipants(); ParticipantNode participantNode = null; @@ -27,9 +34,20 @@ public class ParticipantStateUpdateOperationHandle implements OperationHandle { for(int i = 0; i < participants.length; i++) { if (stateUpdateOperation.getStateUpdateInfo().getPubKey().equals(participants[i].getPubKey())) { participantNode = new PartNode(participants[i].getId(), participants[i].getName(), participants[i].getPubKey(), ParticipantNodeState.CONSENSUSED); + break; } } + //update consensus setting + ParticipantInfo participantInfo = new ParticipantInfoData("", participantNode.getName(), participantNode.getPubKey(), stateUpdateOperation.getStateUpdateInfo().getNetworkAddress()); + + Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(metadata.getSetting().getConsensusSetting(), participantInfo); + + LedgerSetting ledgerSetting = new LedgerConfiguration(adminAccount.getSetting().getConsensusProvider(), + newConsensusSettings, metadata.getSetting().getCryptoSetting()); + + metadata.setSetting(ledgerSetting); + adminAccount.updateParticipant(participantNode); return null; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java index 8452ac92..70c89f3f 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfo.java @@ -5,6 +5,7 @@ import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.utils.net.NetworkAddress; /** * 参与方状态更新信息; @@ -13,6 +14,7 @@ import com.jd.blockchain.crypto.PubKey; */ @DataContract(code = DataCodes.METADATA_PARTICIPANT_STATE_INFO) public interface ParticipantStateUpdateInfo { + /** * 公钥; * @@ -21,11 +23,19 @@ public interface ParticipantStateUpdateInfo { @DataField(order = 1, primitiveType = PrimitiveType.BYTES) PubKey getPubKey(); + /** + * 共识协议的网络地址; + * + * @return + */ + @DataField(order = 2, primitiveType = PrimitiveType.BYTES) + NetworkAddress getNetworkAddress(); + /** * 参与方状态; * * @return */ - @DataField(order = 2, refEnum = true) + @DataField(order = 3, refEnum = true) ParticipantNodeState getState(); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java index 1f4a4fc5..5e11c178 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateInfoData.java @@ -1,14 +1,17 @@ package com.jd.blockchain.ledger; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.utils.net.NetworkAddress; public class ParticipantStateUpdateInfoData implements ParticipantStateUpdateInfo { private PubKey pubKey; private ParticipantNodeState state; + private NetworkAddress networkAddress; - public ParticipantStateUpdateInfoData(PubKey pubKey, ParticipantNodeState state) { + public ParticipantStateUpdateInfoData(PubKey pubKey, ParticipantNodeState state, NetworkAddress networkAddress) { this.pubKey = pubKey; this.state = state; + this.networkAddress = networkAddress; } public void setPubKey(PubKey pubKey) { @@ -20,6 +23,15 @@ public class ParticipantStateUpdateInfoData implements ParticipantStateUpdateInf return pubKey; } + public void setNetworkAddress(NetworkAddress networkAddress) { + this.networkAddress = networkAddress; + } + + @Override + public NetworkAddress getNetworkAddress() { + return networkAddress; + } + public void setState(ParticipantNodeState state) { this.state = state; } diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java index f77397c8..ea04ef69 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java @@ -7,6 +7,7 @@ import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.sdk.samples.SDKDemo_Constant; import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.net.NetworkAddress; import org.junit.Before; import org.junit.Test; @@ -76,8 +77,9 @@ public class SDK_GateWay_Participant_State_Update_Test_ { System.out.println("Address = "+AddressEncoding.generateAddress(pubKey)); + NetworkAddress networkAddress = new NetworkAddress("127.0.0.1", 20000); - ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(pubKey, ParticipantNodeState.CONSENSUSED); + ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(pubKey, ParticipantNodeState.CONSENSUSED, networkAddress); txTemp.states().update(stateUpdateInfo); // TX 准备就绪; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index 32c02b75..d5d8f3af 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -201,7 +201,9 @@ public class IntegrationBase { // 定义交易; TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(participantKeyPair.getPubKey(), ParticipantNodeState.CONSENSUSED); + ParticipantInfoData participantInfoData = new ParticipantInfoData("add", "peer4", participantKeyPair.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); + + ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(participantKeyPair.getPubKey(), ParticipantNodeState.CONSENSUSED, participantInfoData.getNetworkAddress()); txTpl.states().update(stateUpdateInfo); From 51c653033840f80f0900cf34e3538967dcb9fdf1 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 3 Sep 2019 18:52:50 +0800 Subject: [PATCH 073/124] modify participant state update bug --- .../blockchain/intgr/IntegrationTest4Bftsmart.java | 10 ++++++++-- .../com/jd/blockchain/intgr/IntegrationTest4MQ.java | 13 +++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index f9954ce1..a7157d39 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -1,5 +1,7 @@ package test.com.jd.blockchain.intgr; +import com.jd.blockchain.consensus.ConsensusProviders; +import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; @@ -45,6 +47,8 @@ public class IntegrationTest4Bftsmart { private static final String DB_TYPE_ROCKSDB = "rocksdb"; + public static final String BFTSMART_PROVIDER = "com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider"; + @Test public void test4Memory() { test(LedgerInitConsensusConfig.bftsmartProvider, DB_TYPE_MEM, LedgerInitConsensusConfig.memConnectionStrings); @@ -164,7 +168,8 @@ public class IntegrationTest4Bftsmart { System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); - System.out.println("update participant state before \r\n"); + BftsmartConsensusSettings consensusSettings = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount().getSetting().getConsensusSetting().toBytes()); + System.out.printf("update participant state before ,old consensus env node num = %d\r\n", consensusSettings.getNodes().length); for (int i = 0; i < participantCount; i++) { System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); @@ -174,8 +179,9 @@ public class IntegrationTest4Bftsmart { IntegrationBase.testSDK_UpdateParticipantState(adminKey, new BlockchainKeypair(participantResponse.getKeyPair().getPubKey(), participantResponse.getKeyPair().getPrivKey()), ledgerHash, blockchainService); } - System.out.println("update participant state after\r\n"); + BftsmartConsensusSettings consensusSettingsNew = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getSetting().getConsensusSetting().toBytes()); + System.out.printf("update participant state after ,new consensus env node num = %d\r\n", consensusSettingsNew.getNodes().length); for (int i = 0; i < participantCount; i++) { System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index a3735e1d..38619459 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -1,5 +1,8 @@ package test.com.jd.blockchain.intgr; +import com.jd.blockchain.consensus.ConsensusProviders; +import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; +import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; @@ -48,6 +51,8 @@ public class IntegrationTest4MQ { private static final String DATA_RETRIEVAL_URL= "http://192.168.151.39:10001"; + public static final String MQ_PROVIDER = "com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider"; + @Test public void test4Memory() { test(LedgerInitConsensusConfig.mqProvider, DB_TYPE_MEM, LedgerInitConsensusConfig.memConnectionStrings); @@ -159,7 +164,9 @@ public class IntegrationTest4MQ { System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); - System.out.println("update participant state before \r\n"); + MsgQueueConsensusSettings consensusSettings = (MsgQueueConsensusSettings) ConsensusProviders.getProvider(MQ_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount().getSetting().getConsensusSetting().toBytes()); + + System.out.printf("update participant state before ,old consensus env node num = %d\r\n", consensusSettings.getNodes().length); for (int i = 0; i < participantCount; i++) { System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); @@ -169,7 +176,9 @@ public class IntegrationTest4MQ { IntegrationBase.testSDK_UpdateParticipantState(adminKey, new BlockchainKeypair(participantResponse.getKeyPair().getPubKey(), participantResponse.getKeyPair().getPrivKey()), ledgerHash, blockchainService); } - System.out.println("update participant state after\r\n"); + BftsmartConsensusSettings consensusSettingsNew = (BftsmartConsensusSettings) ConsensusProviders.getProvider(MQ_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getSetting().getConsensusSetting().toBytes()); + + System.out.printf("update participant state after ,new consensus env node num = %d\r\n", consensusSettingsNew.getNodes().length); for (int i = 0; i < participantCount; i++) { System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); From 47ac7af85de74dece8965a0ed34b7fdb34cf7679 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 3 Sep 2019 23:25:40 +0800 Subject: [PATCH 074/124] Fixed bugs about contract invoking; --- .../jvm/InstantiatedContractCode.java | 25 ++++ .../ledger/core/impl/LedgerQueryService.java | 6 +- .../ledger/ContractInvokingHandle.java | 26 +--- .../ledger/ContractInvokingTest.java | 137 ++++++++++++++++-- .../ledger/TransactionBatchProcessorTest.java | 16 +- .../jd/blockchain/ledger/TxTestContract.java | 18 +++ .../blockchain/ledger/TxTestContractImpl.java | 79 ++++++++++ .../blockchain/ledger/BytesValueEncoding.java | 48 +++--- .../resolver/BooleanToBytesValueResolver.java | 58 ++++++++ .../ledger/resolver/BytesValueResolver.java | 124 ++++++++-------- source/test/test-contract/pom.xml | 34 +++++ .../ContractTransactionRollbackTest.java | 14 ++ 12 files changed, 461 insertions(+), 124 deletions(-) create mode 100644 source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java create mode 100644 source/test/test-contract/pom.xml create mode 100644 source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java diff --git a/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java new file mode 100644 index 00000000..94adc3a7 --- /dev/null +++ b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/InstantiatedContractCode.java @@ -0,0 +1,25 @@ +package com.jd.blockchain.contract.jvm; + +import com.jd.blockchain.contract.ContractType; +import com.jd.blockchain.utils.Bytes; + +public class InstantiatedContractCode extends AbstractContractCode { + + private T instance; + + public InstantiatedContractCode(Bytes address, long version, Class delaredInterface, T instance) { + super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); + this.instance = instance; + } + + private static ContractDefinition resolveContractDefinition(Class declaredIntf, Class implementedClass) { + ContractType contractType = ContractType.resolve(declaredIntf); + return new ContractDefinition(contractType, implementedClass); + } + + @Override + protected T getContractInstance() { + return instance; + } + + } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java index 5696ded7..226be047 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerQueryService.java @@ -20,6 +20,8 @@ import com.jd.blockchain.utils.QueryUtil; public class LedgerQueryService implements BlockchainQueryService { + private static final KVDataEntry[] EMPTY_ENTRIES = new KVDataEntry[0]; + private LedgerService ledgerService; public LedgerQueryService(LedgerService ledgerService) { @@ -254,7 +256,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public KVDataEntry[] getDataEntries(HashDigest ledgerHash, String address, String... keys) { if (keys == null || keys.length == 0) { - return null; + return EMPTY_ENTRIES; } LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); @@ -266,7 +268,7 @@ public class LedgerQueryService implements BlockchainQueryService { for (int i = 0; i < entries.length; i++) { final String currKey = keys[i]; - ver = dataAccount.getDataVersion(Bytes.fromString(currKey)); + ver = dataAccount == null ? -1 : dataAccount.getDataVersion(Bytes.fromString(currKey)); if (ver < 0) { entries[i] = new KVDataObject(currKey, -1, null); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java index c0a31321..ef354223 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java @@ -3,10 +3,8 @@ package test.com.jd.blockchain.ledger; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.jd.blockchain.contract.ContractType; import com.jd.blockchain.contract.engine.ContractCode; -import com.jd.blockchain.contract.jvm.AbstractContractCode; -import com.jd.blockchain.contract.jvm.ContractDefinition; +import com.jd.blockchain.contract.jvm.InstantiatedContractCode; import com.jd.blockchain.ledger.core.ContractAccount; import com.jd.blockchain.ledger.core.impl.handles.AbtractContractEventHandle; import com.jd.blockchain.utils.Bytes; @@ -21,30 +19,10 @@ public class ContractInvokingHandle extends AbtractContractEventHandle { } public ContractCode setup(Bytes address, Class contractIntf, T instance) { - ContractCodeInstance contract = new ContractCodeInstance(address, 0, contractIntf, instance); + InstantiatedContractCode contract = new InstantiatedContractCode(address, 0, contractIntf, instance); contractInstances.put(address, contract); return contract; } - private static class ContractCodeInstance extends AbstractContractCode { - - private T instance; - - public ContractCodeInstance(Bytes address, long version, Class delaredInterface, T instance) { - super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); - this.instance = instance; - } - - private static ContractDefinition resolveContractDefinition(Class declaredIntf, Class implementedClass) { - ContractType contractType = ContractType.resolve(declaredIntf); - return new ContractDefinition(contractType, implementedClass); - } - - @Override - protected T getContractInstance() { - return instance; - } - - } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index fd9df2d4..15bf2671 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -1,27 +1,57 @@ package test.com.jd.blockchain.ledger; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Random; + +import org.junit.Test; +import org.mockito.Mockito; + import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.DataAccountRegisterOperation; +import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.NodeRequest; +import com.jd.blockchain.ledger.OperationResult; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionRequestBuilder; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerTransactionContext; +import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; +import com.jd.blockchain.transaction.BooleanValueHolder; +import static com.jd.blockchain.transaction.ContractReturnValue.*; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; -import org.junit.Test; -import org.mockito.Mockito; - -import java.util.Random; - -import static org.junit.Assert.*; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.*; public class ContractInvokingTest { static { @@ -32,6 +62,7 @@ public class ContractInvokingTest { DataContractRegistry.register(EndpointRequest.class); DataContractRegistry.register(TransactionResponse.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(DataAccountRegisterOperation.class); } private static final String LEDGER_KEY_PREFIX = "LDG://"; @@ -45,7 +76,7 @@ public class ContractInvokingTest { private MemoryKVStorage storage = new MemoryKVStorage(); @Test - public void test() { + public void testNormal() { // 初始化账本到指定的存储库; HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); @@ -69,7 +100,6 @@ public class ContractInvokingTest { // 发布指定地址合约 deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); - // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); @@ -122,9 +152,86 @@ public class ContractInvokingTest { } + @Test + public void testReadNewWritting() { + // 初始化账本到指定的存储库; + HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); + + // 重新加载账本; + LedgerManager ledgerManager = new LedgerManager(); + LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); + + // 创建合约处理器; + ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); + + // 创建和加载合约实例; + BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); + Bytes contractAddress = contractKey.getAddress(); + TxTestContractImpl contractInstance = new TxTestContractImpl(); + contractInvokingHandle.setup(contractAddress, TxTestContract.class, contractInstance); + + // 注册合约处理器; + DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); + opReg.insertAsTopPriority(contractInvokingHandle); + + // 发布指定地址合约 + deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); + + // 创建新区块的交易处理器; + LedgerBlock preBlock = ledgerRepo.getLatestBlock(); + LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + + // 加载合约 + LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, + opReg, ledgerManager); + + String key = TxTestContractImpl.KEY; + String value = "VAL"; + + TxBuilder txBuilder = new TxBuilder(ledgerHash); + BlockchainKeypair kpDataAccount = BlockchainKeyGenerator.getInstance().generate(); + contractInstance.setDataAddress(kpDataAccount.getAddress()); + + txBuilder.dataAccounts().register(kpDataAccount.getIdentity()); + TransactionRequestBuilder txReqBuilder1 = txBuilder.prepareRequest(); + txReqBuilder1.signAsEndpoint(parti0); + txReqBuilder1.signAsNode(parti0); + TransactionRequest txReq1 = txReqBuilder1.buildRequest(); + + // 构建基于接口调用合约的交易请求,用于测试合约调用; + txBuilder = new TxBuilder(ledgerHash); + TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); + BooleanValueHolder readableHolder = decode(contractProxy.testReadable()); + + TransactionRequestBuilder txReqBuilder2 = txBuilder.prepareRequest(); + txReqBuilder2.signAsEndpoint(parti0); + txReqBuilder2.signAsNode(parti0); + TransactionRequest txReq2 = txReqBuilder2.buildRequest(); + + TransactionResponse resp1 = txbatchProcessor.schedule(txReq1); + TransactionResponse resp2 = txbatchProcessor.schedule(txReq2); + + // 提交区块; + TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); + txResultHandle.commit(); + + BytesValue latestValue = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getBytes(key, + -1); + System.out.printf("latest value=[%s] %s \r\n", latestValue.getType(), latestValue.getValue().toUTF8String()); + + + boolean readable = readableHolder.get(); + assertTrue(readable); + + LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); + assertEquals(preBlock.getHeight() + 1, latestBlock.getHeight()); + assertEquals(resp1.getBlockHeight(), latestBlock.getHeight()); + assertEquals(resp1.getBlockHash(), latestBlock.getHash()); + } + private void deploy(LedgerRepository ledgerRepo, LedgerManager ledgerManager, - DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, - BlockchainKeypair contractKey) { + DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair contractKey) { // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java index f857a6ad..39665bde 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java @@ -288,6 +288,8 @@ public class TransactionBatchProcessorTest { "K2", "V-2-1", -1, ledgerHash, parti0, parti0); TransactionRequest txreq3 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), "K3", "V-3-1", -1, ledgerHash, parti0, parti0); + + // 连续写 K1,K1的版本将变为1; TransactionRequest txreq4 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), "K1", "V-1-2", 0, ledgerHash, parti0, parti0); @@ -316,14 +318,14 @@ public class TransactionBatchProcessorTest { assertNotNull(v1_1); assertNotNull(v2); assertNotNull(v3); - + assertEquals("V-1-1", v1_0.getValue().toUTF8String()); assertEquals("V-1-2", v1_1.getValue().toUTF8String()); assertEquals("V-2-1", v2.getValue().toUTF8String()); assertEquals("V-3-1", v3.getValue().toUTF8String()); // 提交多笔数据写入的交易,包含存在数据版本冲突的交易,验证交易是否正确回滚; - + // 先写一笔正确的交易; k3 的版本将变为 1 ; TransactionRequest txreq5 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), "K3", "V-3-2", 0, ledgerHash, parti0, parti0); // 指定冲突的版本号,正确的应该是版本1; @@ -343,11 +345,15 @@ public class TransactionBatchProcessorTest { BytesValue v1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1"); v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3"); - long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K1"); + // k1 的版本仍然为1,没有更新; + long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) + .getDataVersion("K1"); assertEquals(1, k1_version); - long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K3"); + + long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) + .getDataVersion("K3"); assertEquals(1, k3_version); - + assertNotNull(v1); assertNotNull(v3); assertEquals("V-1-2", v1.getValue().toUTF8String()); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java new file mode 100644 index 00000000..80df9803 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java @@ -0,0 +1,18 @@ +package test.com.jd.blockchain.ledger; + +import com.jd.blockchain.contract.Contract; +import com.jd.blockchain.contract.ContractEvent; + +@Contract +public interface TxTestContract { + + @ContractEvent(name = "testReadable") + boolean testReadable(); + +// @ContractEvent(name = "prepareData") +// String[] prepareData(String address); +// +// @ContractEvent(name = "doVersionConflictedWritting") +// void doVersionConflictedWritting(String key, String value, long version); + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java new file mode 100644 index 00000000..bd9137d4 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java @@ -0,0 +1,79 @@ +package test.com.jd.blockchain.ledger; + +import com.jd.blockchain.contract.ContractEventContext; +import com.jd.blockchain.contract.ContractLifecycleAware; +import com.jd.blockchain.contract.EventProcessingAware; +import com.jd.blockchain.ledger.KVDataEntry; +import com.jd.blockchain.utils.Bytes; + +public class TxTestContractImpl implements TxTestContract, ContractLifecycleAware, EventProcessingAware { + + private ContractEventContext eventContext; + + private Bytes dataAddress; + + public static String KEY = "k1"; + + @Override + public boolean testReadable() { + KVDataEntry v1 = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), + dataAddress.toBase58(), KEY)[0]; + String text1 = (String) v1.getValue(); + System.out.printf("k1=%s, version=%s \r\n", text1, v1.getVersion()); + + text1 = null == text1 ? "v" : text1; + String newValue = text1 + "-" + (v1.getVersion() + 1); + System.out.printf("new value = %s\r\n", newValue); + eventContext.getLedger().dataAccount(dataAddress).setText(KEY, newValue, v1.getVersion()); + + KVDataEntry v2 = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), + dataAddress.toBase58(), KEY)[0]; + System.out.printf("---- read new value ----\r\nk1=%s, version=%s \r\n", v2.getValue(), v2.getVersion()); + + String text2 = (String) v2.getValue(); + return text1.equals(text2); + } + +// @Override +// public String[] prepareData(String address) { +// // TODO Auto-generated method stub +// return null; +// } +// +// @Override +// public void doVersionConflictedWritting(String key, String value, long version) { +// // TODO Auto-generated method stub +// +// } + + @Override + public void postConstruct() { + // TODO Auto-generated method stub + + } + + @Override + public void beforeDestroy() { + // TODO Auto-generated method stub + + } + + @Override + public void beforeEvent(ContractEventContext eventContext) { + this.eventContext = eventContext; + } + + @Override + public void postEvent(ContractEventContext eventContext, Exception error) { + this.eventContext = null; + } + + public Bytes getDataAddress() { + return dataAddress; + } + + public void setDataAddress(Bytes dataAddress) { + this.dataAddress = dataAddress; + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java index af67719e..66a9eee2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java @@ -1,32 +1,36 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.binaryproto.DataContract; -import com.jd.blockchain.ledger.resolver.*; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.ledger.resolver.BooleanToBytesValueResolver; +import com.jd.blockchain.ledger.resolver.BytesToBytesValueResolver; +import com.jd.blockchain.ledger.resolver.BytesValueResolver; +import com.jd.blockchain.ledger.resolver.IntegerToBytesValueResolver; +import com.jd.blockchain.ledger.resolver.LongToBytesValueResolver; +import com.jd.blockchain.ledger.resolver.ShortToBytesValueResolver; +import com.jd.blockchain.ledger.resolver.StringToBytesValueResolver; + public class BytesValueEncoding { private static final Map, BytesValueResolver> CLASS_RESOLVER_MAP = new ConcurrentHashMap<>(); private static final Map DATA_TYPE_RESOLVER_MAP = new ConcurrentHashMap<>(); + private static final Object[] EMPTY_OBJECTS = {}; + static { init(); } private static void init() { - BytesValueResolver[] resolvers = new BytesValueResolver[]{ - new BytesToBytesValueResolver(), - new IntegerToBytesValueResolver(), - new LongToBytesValueResolver(), - new ShortToBytesValueResolver(), - new StringToBytesValueResolver() - }; + BytesValueResolver[] resolvers = new BytesValueResolver[] { new BooleanToBytesValueResolver(), + new BytesToBytesValueResolver(), new IntegerToBytesValueResolver(), new LongToBytesValueResolver(), + new ShortToBytesValueResolver(), new StringToBytesValueResolver() }; for (BytesValueResolver currResolver : resolvers) { // 填充classMAP @@ -47,7 +51,6 @@ public class BytesValueEncoding { } } - public static BytesValue encodeSingle(Object value, Class type) { if (value == null) { return null; @@ -60,7 +63,8 @@ public class BytesValueEncoding { if (type.isInterface()) { // 判断是否含有DataContract注解 if (!type.isAnnotationPresent(DataContract.class)) { - throw new IllegalStateException(String.format("Interface[%s] can not be serialize !!!", type.getName())); + throw new IllegalStateException( + String.format("Interface[%s] can not be serialize !!!", type.getName())); } // 将对象序列化 byte[] serialBytes = BinaryProtocol.encode(value, type); @@ -72,7 +76,7 @@ public class BytesValueEncoding { } return bytesValueResolver.encode(value, type); } - + public static BytesValueList encodeArray(Object[] values, Class[] types) { if (values == null || values.length == 0) { return null; @@ -101,11 +105,14 @@ public class BytesValueEncoding { } return type == null ? valueResolver.decode(value) : valueResolver.decode(value, type); } - + public static Object[] decode(BytesValueList values, Class[] types) { + if (values == null) { + return EMPTY_OBJECTS; + } BytesValue[] bytesValues = values.getValues(); if (bytesValues == null || bytesValues.length == 0) { - return null; + return EMPTY_OBJECTS; } // 允许types为null,此时每个BytesValue按照当前的对象来处理 // 若types不为null,则types's长度必须和bytesValues一致 @@ -120,7 +127,8 @@ public class BytesValueEncoding { DataType dataType = bytesValue.getType(); BytesValueResolver valueResolver = DATA_TYPE_RESOLVER_MAP.get(dataType); if (valueResolver == null) { - throw new IllegalStateException(String.format("DataType[%s] can not find encoder !!!", dataType.name())); + throw new IllegalStateException( + String.format("DataType[%s] can not find encoder !!!", dataType.name())); } resolveObjs[i] = valueResolver.decode(bytesValue); } @@ -132,7 +140,7 @@ public class BytesValueEncoding { } return resolveObjs; } - + public static Object getDefaultValue(Class type) { if (type == void.class || type == Void.class) { return null; @@ -174,14 +182,14 @@ public class BytesValueEncoding { if (currParamType.isInterface()) { // 接口序列化必须实现DataContract注解 if (!currParamType.isAnnotationPresent(DataContract.class)) { - throw new IllegalStateException(String.format("Interface[%s] can not be serialize !!!", currParamType.getName())); + throw new IllegalStateException( + String.format("Interface[%s] can not be serialize !!!", currParamType.getName())); } return true; } return CLASS_RESOLVER_MAP.containsKey(currParamType); } - public static class BytesValueListData implements BytesValueList { private List bytesValues = new ArrayList<>(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java new file mode 100644 index 00000000..0664fdb9 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BooleanToBytesValueResolver.java @@ -0,0 +1,58 @@ +package com.jd.blockchain.ledger.resolver; + +import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.DataType; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.io.BytesUtils; + +import java.util.Set; + +public class BooleanToBytesValueResolver extends AbstractBytesValueResolver { + + private final Class[] supportClasses = { Boolean.class, boolean.class }; + + private final DataType[] supportDataTypes = { DataType.BOOLEAN }; + + private final Set> convertClasses = initBooleanConvertSet(); + + @Override + public BytesValue encode(Object value, Class type) { + if (!isSupport(type)) { + throw new IllegalStateException(String.format("Un-support encode Class[%s] Object !!!", type.getName())); + } + return BytesData.fromBoolean((boolean) value); + } + + @Override + public Class[] supportClasses() { + return supportClasses; + } + + @Override + public DataType[] supportDataTypes() { + return supportDataTypes; + } + + @Override + protected Object decode(Bytes value) { + return BytesUtils.toInt(value.toBytes()); + } + + @Override + public Object decode(BytesValue value, Class clazz) { + // 支持转换为short、int、long + int intVal = (int) decode(value); + if (convertClasses.contains(clazz)) { + // 对于short和Short需要强制类型转换 + if (clazz.equals(short.class) || clazz.equals(Short.class)) { + return (short) intVal; + } else if (clazz.equals(long.class) || clazz.equals(Long.class)) { + return (long) intVal; + } + return intVal; + } else { + throw new IllegalStateException(String.format("Un-Support decode value to class[%s] !!!", clazz.getName())); + } + } +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java index 08e48658..4c659567 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/resolver/BytesValueResolver.java @@ -10,71 +10,79 @@ import java.util.Set; public interface BytesValueResolver { - /** - * Int相关的可转换Class集合 - */ - Class[] supportIntConvertClasses = { - short.class, Short.class, int.class, Integer.class, long.class, Long.class}; + /** + * Boolean相关的可转换Class集合 + */ + Class[] supportBooleanConvertClasses = { boolean.class, Boolean.class }; - /** - * 字节数组(字符串)相关可转换的Class集合 - */ - Class[] supportByteConvertClasses = { - String.class, Bytes.class, byte[].class}; + /** + * Int相关的可转换Class集合 + */ + Class[] supportIntConvertClasses = { short.class, Short.class, int.class, Integer.class, long.class, + Long.class }; - default Set> initIntConvertSet() { - return new HashSet<>(Arrays.asList(supportIntConvertClasses)); - } + /** + * 字节数组(字符串)相关可转换的Class集合 + */ + Class[] supportByteConvertClasses = { String.class, Bytes.class, byte[].class }; + + default Set> initBooleanConvertSet() { + return new HashSet<>(Arrays.asList(supportBooleanConvertClasses)); + } - default Set> initByteConvertSet() { - return new HashSet<>(Arrays.asList(supportByteConvertClasses)); - } + default Set> initIntConvertSet() { + return new HashSet<>(Arrays.asList(supportIntConvertClasses)); + } - /** - * 将对象转换为BytesValue - * - * @param value - * @return - */ - BytesValue encode(Object value); + default Set> initByteConvertSet() { + return new HashSet<>(Arrays.asList(supportByteConvertClasses)); + } - /** - * 将对象转换为BytesValue - * - * @param value - * @param type - * @return - */ - BytesValue encode(Object value, Class type); + /** + * 将对象转换为BytesValue + * + * @param value + * @return + */ + BytesValue encode(Object value); - /** - * 当前解析器支持的Class列表 - * - * @return - */ - Class[] supportClasses(); + /** + * 将对象转换为BytesValue + * + * @param value + * @param type + * @return + */ + BytesValue encode(Object value, Class type); - /** - * 当前解析器支持的DataType列表 - * - * @return - */ - DataType[] supportDataTypes(); + /** + * 当前解析器支持的Class列表 + * + * @return + */ + Class[] supportClasses(); - /** - * 将BytesValue解析为对应的Object - * - * @param value - * @return - */ - Object decode(BytesValue value); + /** + * 当前解析器支持的DataType列表 + * + * @return + */ + DataType[] supportDataTypes(); - /** - * 将BytesValue转换为指定Class的Object - * - * @param value - * @param clazz - * @return - */ - Object decode(BytesValue value, Class clazz); + /** + * 将BytesValue解析为对应的Object + * + * @param value + * @return + */ + Object decode(BytesValue value); + + /** + * 将BytesValue转换为指定Class的Object + * + * @param value + * @param clazz + * @return + */ + Object decode(BytesValue value, Class clazz); } diff --git a/source/test/test-contract/pom.xml b/source/test/test-contract/pom.xml new file mode 100644 index 00000000..a9c59ddf --- /dev/null +++ b/source/test/test-contract/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.jd.blockchain + test + 1.0.1.RELEASE + + test-contract + + + + com.jd.blockchain + contract-jvm + ${project.version} + + + com.jd.blockchain + ledger-core + ${project.version} + + + com.jd.blockchain + storage-rocksdb + ${project.version} + + + com.jd.blockchain + crypto-classic + ${project.version} + + + \ No newline at end of file diff --git a/source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java b/source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java new file mode 100644 index 00000000..bda44aed --- /dev/null +++ b/source/test/test-contract/src/test/java/test/com/jd/blockchain/contract/ContractTransactionRollbackTest.java @@ -0,0 +1,14 @@ +package test.com.jd.blockchain.contract; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ContractTransactionRollbackTest { + + @Test + public void test() { + + } + +} From ef6c4049d2d11f5e85ed66be9aa59ca90f862611 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 5 Sep 2019 01:28:27 +0800 Subject: [PATCH 075/124] Fixed bugs: (1)unsupported no-parameter contract function; (2) doesn't rollback when kv-writting version conflict occurs; --- .../blockchain/ledger/core/DataAccount.java | 97 +++++++++- .../blockchain/ledger/core/MerkleDataSet.java | 2 +- .../DataAccountKVSetOperationHandle.java | 7 +- .../ledger/ContractInvokingTest.java | 172 +++++++++++++++++- .../jd/blockchain/ledger/KeyValueEntry.java | 19 ++ .../jd/blockchain/ledger/KeyValueObject.java | 47 +++++ .../blockchain/ledger/MerkleDataSetTest.java | 33 ++++ .../ledger/TransactionBatchProcessorTest.java | 12 +- .../jd/blockchain/ledger/TxTestContract.java | 9 +- .../blockchain/ledger/TxTestContractImpl.java | 17 +- .../blockchain/ledger/BytesValueEncoding.java | 15 +- .../ledger/DataVersionConflictException.java | 27 +++ .../blockchain/ledger/TransactionState.java | 5 + .../jd/blockchain/runtime/RuntimeContext.java | 2 +- 14 files changed, 435 insertions(+), 29 deletions(-) create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java create mode 100644 source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java index 792ca704..93286676 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java @@ -1,12 +1,11 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.BytesData; +import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.KVDataObject; import com.jd.blockchain.utils.Bytes; @@ -43,16 +42,85 @@ public class DataAccount implements AccountHeader, MerkleProvable { } + /** + * Create or update the value associated the specified key if the version + * checking is passed.
        + * + * The value of the key will be updated only if it's latest version equals the + * specified version argument.
        + * If the key doesn't exist, the version checking will be ignored, and key will + * be created with a new sequence number as id.
        + * It also could specify the version argument to -1 to ignore the version + * checking. + *

        + * If updating is performed, the version of the key increase by 1.
        + * If creating is performed, the version of the key initialize by 0.
        + * + * @param key The key of data; + * @param value The value of data; + * @param version The expected version of the key. + * @return The new version of the key.
        + * If the key is new created success, then return 0;
        + * If the key is updated success, then return the new version;
        + * If this operation fail by version checking or other reason, then + * return -1; + */ public long setBytes(Bytes key, BytesValue value, long version) { return baseAccount.setBytes(key, value, version); } + /** + * Create or update the value associated the specified key if the version + * checking is passed.
        + * + * The value of the key will be updated only if it's latest version equals the + * specified version argument.
        + * If the key doesn't exist, the version checking will be ignored, and key will + * be created with a new sequence number as id.
        + * It also could specify the version argument to -1 to ignore the version + * checking. + *

        + * If updating is performed, the version of the key increase by 1.
        + * If creating is performed, the version of the key initialize by 0.
        + * + * @param key The key of data; + * @param value The value of data; + * @param version The expected version of the key. + * @return The new version of the key.
        + * If the key is new created success, then return 0;
        + * If the key is updated success, then return the new version;
        + * If this operation fail by version checking or other reason, then + * return -1; + */ public long setBytes(Bytes key, String value, long version) { BytesValue bytesValue = BytesData.fromText(value); return baseAccount.setBytes(key, bytesValue, version); } + /** + * Create or update the value associated the specified key if the version + * checking is passed.
        + * + * The value of the key will be updated only if it's latest version equals the + * specified version argument.
        + * If the key doesn't exist, the version checking will be ignored, and key will + * be created with a new sequence number as id.
        + * It also could specify the version argument to -1 to ignore the version + * checking. + *

        + * If updating is performed, the version of the key increase by 1.
        + * If creating is performed, the version of the key initialize by 0.
        + * + * @param key The key of data; + * @param value The value of data; + * @param version The expected version of the key. + * @return The new version of the key.
        + * If the key is new created success, then return 0;
        + * If the key is updated success, then return the new version;
        + * If this operation fail by version checking or other reason, then + * return -1; + */ public long setBytes(Bytes key, byte[] value, long version) { BytesValue bytesValue = BytesData.fromBytes(value); return baseAccount.setBytes(key, bytesValue, version); @@ -121,6 +189,29 @@ public class DataAccount implements AccountHeader, MerkleProvable { public BytesValue getBytes(Bytes key, long version) { return baseAccount.getBytes(key, version); } + + /** + * @param key + * @param version + * @return + */ + public KVDataEntry getDataEntry(String key, long version) { + return getDataEntry(Bytes.fromString(key), version); + } + + /** + * @param key + * @param version + * @return + */ + public KVDataEntry getDataEntry(Bytes key, long version) { + BytesValue value = baseAccount.getBytes(key, version); + if (value == null) { + return new KVDataObject(key.toUTF8String(), -1, null); + }else { + return new KVDataObject(key.toUTF8String(), version, value); + } + } /** * return the specified index's KVDataEntry; @@ -131,7 +222,7 @@ public class DataAccount implements AccountHeader, MerkleProvable { */ public KVDataEntry[] getDataEntries(int fromIndex, int count) { - if (getDataEntriesTotalCount() == 0 || count == 0) { + if (count == 0 || getDataEntriesTotalCount() == 0) { return null; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java index b8dd170b..59ebd13f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java @@ -168,7 +168,7 @@ public class MerkleDataSet implements Transactional, MerkleProvable { */ public String getKeyAtIndex(int fromIndex) { MerkleDataNode dataNode = merkleTree.getData(fromIndex); - return new String(dataNode.getKey().toBytes()); + return dataNode.getKey().toUTF8String(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java index 2745a377..75607b51 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java @@ -7,6 +7,7 @@ import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataAccountDoesNotExistException; import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; +import com.jd.blockchain.ledger.DataVersionConflictException; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.LedgerDataSet; @@ -31,8 +32,12 @@ public class DataAccountKVSetOperationHandle implements OperationHandle { throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); } KVWriteEntry[] writeSet = kvWriteOp.getWriteSet(); + long v = -1; for (KVWriteEntry kvw : writeSet) { - account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); + v = account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); + if (v < 0) { + throw new DataVersionConflictException(); + } } return null; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index 15bf2671..73287b04 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -1,5 +1,6 @@ package test.com.jd.blockchain.ledger; +import static com.jd.blockchain.transaction.ContractReturnValue.decode; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -25,6 +26,7 @@ import com.jd.blockchain.ledger.BytesData; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.EndpointRequest; +import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerTransaction; @@ -40,16 +42,17 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerDataSet; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.LedgerTransactionContext; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.impl.LedgerManager; import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; +import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.BooleanValueHolder; -import static com.jd.blockchain.transaction.ContractReturnValue.*; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; @@ -152,7 +155,7 @@ public class ContractInvokingTest { } - @Test +// @Test public void testReadNewWritting() { // 初始化账本到指定的存储库; HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); @@ -219,7 +222,6 @@ public class ContractInvokingTest { BytesValue latestValue = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getBytes(key, -1); System.out.printf("latest value=[%s] %s \r\n", latestValue.getType(), latestValue.getValue().toUTF8String()); - boolean readable = readableHolder.get(); assertTrue(readable); @@ -230,6 +232,164 @@ public class ContractInvokingTest { assertEquals(resp1.getBlockHash(), latestBlock.getHash()); } + /** + * 验证在合约方法中写入数据账户时,如果版本校验失败是否会引发异常而导致回滚;
        + * 期待正确的表现是引发异常而回滚当前交易; + */ + @Test + public void testRollbackWhileVersionConfliction() { + // 初始化账本到指定的存储库; + HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); + + // 重新加载账本; + LedgerManager ledgerManager = new LedgerManager(); + LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); + + // 创建合约处理器; + ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); + + // 创建和加载合约实例; + BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); + Bytes contractAddress = contractKey.getAddress(); + TxTestContractImpl contractInstance = new TxTestContractImpl(); + contractInvokingHandle.setup(contractAddress, TxTestContract.class, contractInstance); + + // 注册合约处理器; + DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); + opReg.insertAsTopPriority(contractInvokingHandle); + + // 发布指定地址合约 + deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); + + // 注册数据账户; + BlockchainKeypair kpDataAccount = BlockchainKeyGenerator.getInstance().generate(); + contractInstance.setDataAddress(kpDataAccount.getAddress()); + registerDataAccount(ledgerRepo, ledgerManager, opReg, ledgerHash, kpDataAccount); + + // 调用合约 + // 构建基于接口调用合约的交易请求,用于测试合约调用; + buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { + @Override + public void buildTx(TxBuilder txBuilder) { + TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); + contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-0", + -1); + contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-0", + -1); + } + }); + // 预期数据都能够正常写入; + KVDataEntry kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", + 0); + KVDataEntry kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", + 0); + assertEquals(0, kv1.getVersion()); + assertEquals(0, kv2.getVersion()); + assertEquals("V1-0", kv1.getValue()); + assertEquals("V2-0", kv2.getValue()); + + // 构建基于接口调用合约的交易请求,用于测试合约调用; + buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { + @Override + public void buildTx(TxBuilder txBuilder) { + TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); + contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-1", + 0); + contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-1", + 0); + } + }); + // 预期数据都能够正常写入; + kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); + kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", 1); + assertEquals(1, kv1.getVersion()); + assertEquals(1, kv2.getVersion()); + assertEquals("V1-1", kv1.getValue()); + assertEquals("V2-1", kv2.getValue()); + + // 构建基于接口调用合约的交易请求,用于测试合约调用; + buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { + @Override + public void buildTx(TxBuilder txBuilder) { + TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); + contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-2", + 1); + contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-2", + 0); + } + }); + // 预期数据都能够正常写入; + kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); + assertEquals(1, kv1.getVersion()); + assertEquals("V1-1", kv1.getValue()); + kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 2); + assertEquals(-1, kv1.getVersion()); + assertEquals(null, kv1.getValue()); + + } + + private LedgerBlock buildBlock(LedgerRepository ledgerRepo, LedgerService ledgerService, + OperationHandleRegisteration opReg, TxDefinitor txDefinitor) { + LedgerBlock preBlock = ledgerRepo.getLatestBlock(); + LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, + opReg, ledgerService); + + TxBuilder txBuilder = new TxBuilder(ledgerRepo.getHash()); + txDefinitor.buildTx(txBuilder); + + TransactionRequest txReq = buildAndSignRequest(txBuilder, parti0, parti0); + TransactionResponse resp = txbatchProcessor.schedule(txReq); + + // 提交区块; + TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); + txResultHandle.commit(); + + LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); + assertNotNull(resp.getBlockHash()); + assertEquals(preBlock.getHeight() + 1, resp.getBlockHeight()); + return latestBlock; + } + + private TransactionRequest buildAndSignRequest(TxBuilder txBuilder, BlockchainKeypair endpointKey, + BlockchainKeypair nodeKey) { + TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); + txReqBuilder.signAsEndpoint(endpointKey); + txReqBuilder.signAsNode(nodeKey); + TransactionRequest txReq = txReqBuilder.buildRequest(); + return txReq; + } + + private void registerDataAccount(LedgerRepository ledgerRepo, LedgerManager ledgerManager, + DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair kpDataAccount) { + LedgerBlock preBlock = ledgerRepo.getLatestBlock(); + LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + + // 加载合约 + LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, + opReg, ledgerManager); + + // 注册数据账户; + TxBuilder txBuilder = new TxBuilder(ledgerHash); + + txBuilder.dataAccounts().register(kpDataAccount.getIdentity()); + TransactionRequestBuilder txReqBuilder1 = txBuilder.prepareRequest(); + txReqBuilder1.signAsEndpoint(parti0); + txReqBuilder1.signAsNode(parti0); + TransactionRequest txReq = txReqBuilder1.buildRequest(); + + TransactionResponse resp = txbatchProcessor.schedule(txReq); + + TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); + txResultHandle.commit(); + + assertNotNull(resp.getBlockHash()); + assertEquals(TransactionState.SUCCESS, resp.getExecutionState()); + assertEquals(preBlock.getHeight() + 1, resp.getBlockHeight()); + } + private void deploy(LedgerRepository ledgerRepo, LedgerManager ledgerManager, DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair contractKey) { // 创建新区块的交易处理器; @@ -300,4 +460,10 @@ public class ContractInvokingTest { new Random().nextBytes(chainCode); return chainCode; } + + public static interface TxDefinitor { + + void buildTx(TxBuilder txBuilder); + + } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java new file mode 100644 index 00000000..c4b40d59 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueEntry.java @@ -0,0 +1,19 @@ +package test.com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; + +@DataContract(code = 0x4010) +public interface KeyValueEntry { + + @DataField(order = 1, primitiveType = PrimitiveType.TEXT) + String getKey(); + + @DataField(order = 2, primitiveType = PrimitiveType.TEXT) + String getValue(); + + @DataField(order = 3, primitiveType = PrimitiveType.INT64) + long getVersion(); + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java new file mode 100644 index 00000000..24215ea7 --- /dev/null +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/KeyValueObject.java @@ -0,0 +1,47 @@ +package test.com.jd.blockchain.ledger; + +public class KeyValueObject implements KeyValueEntry { + + private String key; + + private String value; + + private long version; + + public KeyValueObject() { + } + + public KeyValueObject(String key, String value, long version) { + this.key = key; + this.value = value; + this.version = version; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return value; + } + + @Override + public long getVersion() { + return version; + } + + public void setKey(String key) { + this.key = key; + } + + public void setValue(String value) { + this.value = value; + } + + public void setVersion(long version) { + this.version = version; + } + +} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java index ce571d71..7bbe7682 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java @@ -32,6 +32,38 @@ public class MerkleDataSetTest { private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), SMCryptoService.class.getName() }; + + /** + * 测试存储的增长; + */ + @Test + public void testKeyIndex() { + + CryptoProvider[] supportedProviders = new CryptoProvider[SUPPORTED_PROVIDERS.length]; + for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { + supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); + } + + String keyPrefix = ""; + CryptoConfig cryptoConfig = new CryptoConfig(); + cryptoConfig.setSupportedProviders(supportedProviders); + cryptoConfig.setHashAlgorithm(ClassicAlgorithm.SHA256); + cryptoConfig.setAutoVerifyHash(true); + + MemoryKVStorage storage = new MemoryKVStorage(); + + MerkleDataSet mds = new MerkleDataSet(cryptoConfig, keyPrefix, storage, storage); + mds.setValue("A", "A".getBytes(), -1); + mds.setValue("B", "B".getBytes(), -1); + mds.setValue("C", "C".getBytes(), -1); + + mds.commit(); + + //校验 Key 的正确性; + assertEquals("A", mds.getKeyAtIndex(0)); + assertEquals("B", mds.getKeyAtIndex(1)); + assertEquals("C", mds.getKeyAtIndex(2)); + } /** * 测试存储的增长; @@ -59,6 +91,7 @@ public class MerkleDataSetTest { mds.commit(); HashDigest root1 = mds.getRootHash(); + // 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量; // 所以:3 项; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java index 39665bde..f51f211d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java @@ -14,6 +14,7 @@ import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.DataAccountRegisterOperation; +import com.jd.blockchain.ledger.DataVersionConflictException; import com.jd.blockchain.ledger.EndpointRequest; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitSetting; @@ -245,7 +246,7 @@ public class TransactionBatchProcessorTest { } @Test - public void testTxRollbackByVersionsConfliction() { + public void testTxRollbackByVersionsConflict() { final MemoryKVStorage STORAGE = new MemoryKVStorage(); // 初始化账本到指定的存储库; @@ -337,7 +338,14 @@ public class TransactionBatchProcessorTest { txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); txbatchProcessor.schedule(txreq5); - txbatchProcessor.schedule(txreq6); + // 预期会产生版本冲突异常; DataVersionConflictionException; + DataVersionConflictException versionConflictionException = null; + try { + txbatchProcessor.schedule(txreq6); + } catch (DataVersionConflictException e) { + versionConflictionException = e; + } + assertNotNull(versionConflictionException); newBlock = newBlockEditor.prepare(); newBlockEditor.commit(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java index 80df9803..80ee477f 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContract.java @@ -5,14 +5,11 @@ import com.jd.blockchain.contract.ContractEvent; @Contract public interface TxTestContract { - + @ContractEvent(name = "testReadable") boolean testReadable(); -// @ContractEvent(name = "prepareData") -// String[] prepareData(String address); -// -// @ContractEvent(name = "doVersionConflictedWritting") -// void doVersionConflictedWritting(String key, String value, long version); + @ContractEvent(name = "testRollbackWhileVersionConfliction") + void testRollbackWhileVersionConfliction(String address, String key, String value, long version); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java index bd9137d4..60ee6864 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TxTestContractImpl.java @@ -33,18 +33,12 @@ public class TxTestContractImpl implements TxTestContract, ContractLifecycleAwar String text2 = (String) v2.getValue(); return text1.equals(text2); } + + @Override + public void testRollbackWhileVersionConfliction(String address, String key, String value, long version) { + eventContext.getLedger().dataAccount(address).setText(key, value, version); + } -// @Override -// public String[] prepareData(String address) { -// // TODO Auto-generated method stub -// return null; -// } -// -// @Override -// public void doVersionConflictedWritting(String key, String value, long version) { -// // TODO Auto-generated method stub -// -// } @Override public void postConstruct() { @@ -76,4 +70,5 @@ public class TxTestContractImpl implements TxTestContract, ContractLifecycleAwar this.dataAddress = dataAddress; } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java index 66a9eee2..baee5868 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BytesValueEncoding.java @@ -183,10 +183,23 @@ public class BytesValueEncoding { // 接口序列化必须实现DataContract注解 if (!currParamType.isAnnotationPresent(DataContract.class)) { throw new IllegalStateException( - String.format("Interface[%s] can not be serialize !!!", currParamType.getName())); + String.format("Interface[%s] can not be annotated as a DataContract!!!", currParamType.getName())); } return true; } + + if (currParamType.isArray() ) { + Class componentType = currParamType.getComponentType(); + if (componentType.isInterface()) { + // 接口序列化必须实现DataContract注解 + if (!componentType.isAnnotationPresent(DataContract.class)) { + throw new IllegalStateException( + String.format("Interface[%s] can not be annotated as a DataContract!!!", currParamType.getName())); + } + return true; + } + } + return CLASS_RESOLVER_MAP.containsKey(currParamType); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java new file mode 100644 index 00000000..8af67d01 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/DataVersionConflictException.java @@ -0,0 +1,27 @@ +package com.jd.blockchain.ledger; + +public class DataVersionConflictException extends BlockRollbackException { + + private static final long serialVersionUID = 3583192000738807503L; + + private TransactionState state; + + public DataVersionConflictException() { + this(TransactionState.DATA_VERSION_CONFLICT, null); + } + + public DataVersionConflictException(String message) { + this(TransactionState.DATA_VERSION_CONFLICT, message); + } + + private DataVersionConflictException(TransactionState state, String message) { + super(message); + assert TransactionState.SUCCESS != state; + this.state = state; + } + + public TransactionState getState() { + return state; + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java index 6955eb94..55390655 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java @@ -38,6 +38,11 @@ public enum TransactionState { * 合约不存在; */ CONTRACT_DOES_NOT_EXIST((byte) 0x04), + + /** + * 数据写入时版本冲突; + */ + DATA_VERSION_CONFLICT((byte) 0x05), /** * 由于在错误的账本上执行交易而被丢弃; diff --git a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java index 569a95a5..04bc55cd 100644 --- a/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java +++ b/source/runtime/runtime-context/src/main/java/com/jd/blockchain/runtime/RuntimeContext.java @@ -81,7 +81,7 @@ public abstract class RuntimeContext { if (jarFile.isFile()) { FileUtils.deleteFile(jarFile); } else { - throw new IllegalStateException("Code storage confliction! --" + jarFile.getAbsolutePath()); + throw new IllegalStateException("Code storage conflict! --" + jarFile.getAbsolutePath()); } } FileUtils.writeBytes(jarBytes, jarFile); From 3378c0321b2fcffa5b91eb9739001784751742c5 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 5 Sep 2019 01:51:15 +0800 Subject: [PATCH 076/124] Upgrade version to 1.0.2.RELEASE; --- source/base/pom.xml | 2 +- source/binary-proto/pom.xml | 2 +- source/consensus/consensus-bftsmart/pom.xml | 2 +- source/consensus/consensus-framework/pom.xml | 2 +- source/consensus/consensus-mq/pom.xml | 2 +- source/consensus/pom.xml | 2 +- source/contract/contract-framework/pom.xml | 2 +- source/contract/contract-jvm/pom.xml | 2 +- source/contract/contract-maven-plugin/pom.xml | 2 +- source/contract/contract-samples/pom.xml | 2 +- source/contract/pom.xml | 2 +- source/crypto/crypto-adv/pom.xml | 2 +- source/crypto/crypto-classic/pom.xml | 2 +- source/crypto/crypto-framework/pom.xml | 2 +- source/crypto/crypto-pki/pom.xml | 2 +- source/crypto/crypto-sm/pom.xml | 2 +- source/crypto/pom.xml | 2 +- source/deployment/deployment-gateway/pom.xml | 2 +- source/deployment/deployment-peer/pom.xml | 2 +- source/deployment/pom.xml | 2 +- source/gateway/pom.xml | 2 +- source/ledger/ledger-core/pom.xml | 2 +- source/ledger/ledger-model/pom.xml | 2 +- source/ledger/ledger-rpc/pom.xml | 2 +- source/ledger/pom.xml | 2 +- source/peer/pom.xml | 2 +- source/pom.xml | 2 +- source/runtime/pom.xml | 2 +- source/runtime/runtime-context/pom.xml | 2 +- source/runtime/runtime-modular-booter/pom.xml | 2 +- source/runtime/runtime-modular/pom.xml | 2 +- source/sdk/pom.xml | 2 +- source/sdk/sdk-base/pom.xml | 2 +- source/sdk/sdk-client/pom.xml | 2 +- source/sdk/sdk-samples/pom.xml | 2 +- source/storage/pom.xml | 2 +- source/storage/storage-composite/pom.xml | 2 +- source/storage/storage-redis/pom.xml | 2 +- source/storage/storage-rocksdb/pom.xml | 2 +- source/storage/storage-service/pom.xml | 2 +- source/test/pom.xml | 2 +- source/test/test-consensus-client/pom.xml | 2 +- source/test/test-consensus-node/pom.xml | 4 ++-- source/test/test-integration/pom.xml | 2 +- source/test/test-ledger-core/pom.xml | 2 +- source/tools/pom.xml | 2 +- source/tools/tools-capability/pom.xml | 2 +- source/tools/tools-initializer-booter/pom.xml | 2 +- source/tools/tools-initializer/pom.xml | 2 +- source/tools/tools-keygen-booter/pom.xml | 2 +- source/tools/tools-keygen/pom.xml | 2 +- source/tools/tools-mocker/pom.xml | 2 +- source/utils/pom.xml | 2 +- source/utils/utils-common/pom.xml | 2 +- source/utils/utils-http/pom.xml | 2 +- source/utils/utils-serialize/pom.xml | 2 +- source/utils/utils-test/pom.xml | 2 +- source/utils/utils-web-server/pom.xml | 2 +- source/utils/utils-web/pom.xml | 2 +- 59 files changed, 60 insertions(+), 60 deletions(-) diff --git a/source/base/pom.xml b/source/base/pom.xml index 739e3013..8a683041 100644 --- a/source/base/pom.xml +++ b/source/base/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE base diff --git a/source/binary-proto/pom.xml b/source/binary-proto/pom.xml index eababa0e..d44b88f2 100644 --- a/source/binary-proto/pom.xml +++ b/source/binary-proto/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE binary-proto diff --git a/source/consensus/consensus-bftsmart/pom.xml b/source/consensus/consensus-bftsmart/pom.xml index d2b7b9fe..5dd4e1ca 100644 --- a/source/consensus/consensus-bftsmart/pom.xml +++ b/source/consensus/consensus-bftsmart/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain consensus - 1.0.1.RELEASE + 1.0.2.RELEASE consensus-bftsmart diff --git a/source/consensus/consensus-framework/pom.xml b/source/consensus/consensus-framework/pom.xml index 288a6c72..d2b1b8a5 100644 --- a/source/consensus/consensus-framework/pom.xml +++ b/source/consensus/consensus-framework/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain consensus - 1.0.1.RELEASE + 1.0.2.RELEASE consensus-framework diff --git a/source/consensus/consensus-mq/pom.xml b/source/consensus/consensus-mq/pom.xml index 1b1bcc8c..f3b67eef 100644 --- a/source/consensus/consensus-mq/pom.xml +++ b/source/consensus/consensus-mq/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain consensus - 1.0.1.RELEASE + 1.0.2.RELEASE consensus-mq diff --git a/source/consensus/pom.xml b/source/consensus/pom.xml index de3bf418..a6a819d8 100644 --- a/source/consensus/pom.xml +++ b/source/consensus/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE consensus pom diff --git a/source/contract/contract-framework/pom.xml b/source/contract/contract-framework/pom.xml index fc6522d3..59138327 100644 --- a/source/contract/contract-framework/pom.xml +++ b/source/contract/contract-framework/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain contract - 1.0.1.RELEASE + 1.0.2.RELEASE contract-framework diff --git a/source/contract/contract-jvm/pom.xml b/source/contract/contract-jvm/pom.xml index c90fb8f7..d7c088f3 100644 --- a/source/contract/contract-jvm/pom.xml +++ b/source/contract/contract-jvm/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain contract - 1.0.1.RELEASE + 1.0.2.RELEASE contract-jvm diff --git a/source/contract/contract-maven-plugin/pom.xml b/source/contract/contract-maven-plugin/pom.xml index f8c15327..3e31cd35 100644 --- a/source/contract/contract-maven-plugin/pom.xml +++ b/source/contract/contract-maven-plugin/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain contract - 1.0.1.RELEASE + 1.0.2.RELEASE contract-maven-plugin maven-plugin diff --git a/source/contract/contract-samples/pom.xml b/source/contract/contract-samples/pom.xml index 1421e60c..8bb1a913 100644 --- a/source/contract/contract-samples/pom.xml +++ b/source/contract/contract-samples/pom.xml @@ -5,7 +5,7 @@ contract com.jd.blockchain - 1.0.1.RELEASE + 1.0.2.RELEASE 4.0.0 diff --git a/source/contract/pom.xml b/source/contract/pom.xml index f8213546..135f759d 100644 --- a/source/contract/pom.xml +++ b/source/contract/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE contract pom diff --git a/source/crypto/crypto-adv/pom.xml b/source/crypto/crypto-adv/pom.xml index b375250c..86277d4f 100644 --- a/source/crypto/crypto-adv/pom.xml +++ b/source/crypto/crypto-adv/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain crypto - 1.0.1.RELEASE + 1.0.2.RELEASE crypto-adv diff --git a/source/crypto/crypto-classic/pom.xml b/source/crypto/crypto-classic/pom.xml index 6b48f130..4bd0ad62 100644 --- a/source/crypto/crypto-classic/pom.xml +++ b/source/crypto/crypto-classic/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain crypto - 1.0.1.RELEASE + 1.0.2.RELEASE crypto-classic diff --git a/source/crypto/crypto-framework/pom.xml b/source/crypto/crypto-framework/pom.xml index e1fed396..99d153e3 100644 --- a/source/crypto/crypto-framework/pom.xml +++ b/source/crypto/crypto-framework/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain crypto - 1.0.1.RELEASE + 1.0.2.RELEASE crypto-framework diff --git a/source/crypto/crypto-pki/pom.xml b/source/crypto/crypto-pki/pom.xml index ed9719cd..d86f9e8f 100644 --- a/source/crypto/crypto-pki/pom.xml +++ b/source/crypto/crypto-pki/pom.xml @@ -5,7 +5,7 @@ crypto com.jd.blockchain - 1.0.1.RELEASE + 1.0.2.RELEASE 4.0.0 diff --git a/source/crypto/crypto-sm/pom.xml b/source/crypto/crypto-sm/pom.xml index 6243bb9d..8c5623ba 100644 --- a/source/crypto/crypto-sm/pom.xml +++ b/source/crypto/crypto-sm/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain crypto - 1.0.1.RELEASE + 1.0.2.RELEASE crypto-sm diff --git a/source/crypto/pom.xml b/source/crypto/pom.xml index 7cb0bcb3..b8b3135f 100644 --- a/source/crypto/pom.xml +++ b/source/crypto/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE crypto pom diff --git a/source/deployment/deployment-gateway/pom.xml b/source/deployment/deployment-gateway/pom.xml index 90a14bae..7fdd6ea4 100644 --- a/source/deployment/deployment-gateway/pom.xml +++ b/source/deployment/deployment-gateway/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain deployment - 1.0.1.RELEASE + 1.0.2.RELEASE deployment-gateway diff --git a/source/deployment/deployment-peer/pom.xml b/source/deployment/deployment-peer/pom.xml index 09590768..6239b042 100644 --- a/source/deployment/deployment-peer/pom.xml +++ b/source/deployment/deployment-peer/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain deployment - 1.0.1.RELEASE + 1.0.2.RELEASE deployment-peer diff --git a/source/deployment/pom.xml b/source/deployment/pom.xml index 6894c623..3568713d 100644 --- a/source/deployment/pom.xml +++ b/source/deployment/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE deployment pom diff --git a/source/gateway/pom.xml b/source/gateway/pom.xml index 7edc2c85..cdcf26e0 100644 --- a/source/gateway/pom.xml +++ b/source/gateway/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE gateway diff --git a/source/ledger/ledger-core/pom.xml b/source/ledger/ledger-core/pom.xml index 4f23477e..b3d64c0d 100644 --- a/source/ledger/ledger-core/pom.xml +++ b/source/ledger/ledger-core/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain ledger - 1.0.1.RELEASE + 1.0.2.RELEASE ledger-core diff --git a/source/ledger/ledger-model/pom.xml b/source/ledger/ledger-model/pom.xml index 28dd7cae..88e1d7ee 100644 --- a/source/ledger/ledger-model/pom.xml +++ b/source/ledger/ledger-model/pom.xml @@ -6,7 +6,7 @@ com.jd.blockchain ledger - 1.0.1.RELEASE + 1.0.2.RELEASE ledger-model diff --git a/source/ledger/ledger-rpc/pom.xml b/source/ledger/ledger-rpc/pom.xml index b6793681..db16126c 100644 --- a/source/ledger/ledger-rpc/pom.xml +++ b/source/ledger/ledger-rpc/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain ledger - 1.0.1.RELEASE + 1.0.2.RELEASE ledger-rpc diff --git a/source/ledger/pom.xml b/source/ledger/pom.xml index 0876a46c..002c1383 100644 --- a/source/ledger/pom.xml +++ b/source/ledger/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE ledger pom diff --git a/source/peer/pom.xml b/source/peer/pom.xml index 9998f6db..4cb8110e 100644 --- a/source/peer/pom.xml +++ b/source/peer/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE peer diff --git a/source/pom.xml b/source/pom.xml index 1633c32e..bfd75f7e 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -16,7 +16,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE pom jdchain diff --git a/source/runtime/pom.xml b/source/runtime/pom.xml index b3f33611..df4e3eb0 100644 --- a/source/runtime/pom.xml +++ b/source/runtime/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE runtime pom diff --git a/source/runtime/runtime-context/pom.xml b/source/runtime/runtime-context/pom.xml index e2819040..4dec8d19 100644 --- a/source/runtime/runtime-context/pom.xml +++ b/source/runtime/runtime-context/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain runtime - 1.0.1.RELEASE + 1.0.2.RELEASE runtime-context diff --git a/source/runtime/runtime-modular-booter/pom.xml b/source/runtime/runtime-modular-booter/pom.xml index 95ad8b44..2c68f286 100644 --- a/source/runtime/runtime-modular-booter/pom.xml +++ b/source/runtime/runtime-modular-booter/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain runtime - 1.0.1.RELEASE + 1.0.2.RELEASE runtime-modular-booter \ No newline at end of file diff --git a/source/runtime/runtime-modular/pom.xml b/source/runtime/runtime-modular/pom.xml index 4bf67684..0b2d4ab0 100644 --- a/source/runtime/runtime-modular/pom.xml +++ b/source/runtime/runtime-modular/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain runtime - 1.0.1.RELEASE + 1.0.2.RELEASE runtime-modular diff --git a/source/sdk/pom.xml b/source/sdk/pom.xml index e5da5929..af743c62 100644 --- a/source/sdk/pom.xml +++ b/source/sdk/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE sdk pom diff --git a/source/sdk/sdk-base/pom.xml b/source/sdk/sdk-base/pom.xml index cd23f963..a0436d70 100644 --- a/source/sdk/sdk-base/pom.xml +++ b/source/sdk/sdk-base/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain sdk - 1.0.1.RELEASE + 1.0.2.RELEASE sdk-base diff --git a/source/sdk/sdk-client/pom.xml b/source/sdk/sdk-client/pom.xml index 857d8c67..99ef7fb9 100644 --- a/source/sdk/sdk-client/pom.xml +++ b/source/sdk/sdk-client/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain sdk - 1.0.1.RELEASE + 1.0.2.RELEASE sdk-client diff --git a/source/sdk/sdk-samples/pom.xml b/source/sdk/sdk-samples/pom.xml index 1a7e0777..81bfceac 100644 --- a/source/sdk/sdk-samples/pom.xml +++ b/source/sdk/sdk-samples/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain sdk - 1.0.1.RELEASE + 1.0.2.RELEASE sdk-samples diff --git a/source/storage/pom.xml b/source/storage/pom.xml index 9aa84865..b99ec87f 100644 --- a/source/storage/pom.xml +++ b/source/storage/pom.xml @@ -3,7 +3,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE storage pom diff --git a/source/storage/storage-composite/pom.xml b/source/storage/storage-composite/pom.xml index 338d4535..80235eab 100644 --- a/source/storage/storage-composite/pom.xml +++ b/source/storage/storage-composite/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain storage - 1.0.1.RELEASE + 1.0.2.RELEASE storage-composite diff --git a/source/storage/storage-redis/pom.xml b/source/storage/storage-redis/pom.xml index 7be3b3c9..ee82955c 100644 --- a/source/storage/storage-redis/pom.xml +++ b/source/storage/storage-redis/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain storage - 1.0.1.RELEASE + 1.0.2.RELEASE storage-redis diff --git a/source/storage/storage-rocksdb/pom.xml b/source/storage/storage-rocksdb/pom.xml index a6156f3b..ae15aa88 100644 --- a/source/storage/storage-rocksdb/pom.xml +++ b/source/storage/storage-rocksdb/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain storage - 1.0.1.RELEASE + 1.0.2.RELEASE storage-rocksdb diff --git a/source/storage/storage-service/pom.xml b/source/storage/storage-service/pom.xml index bfd5edd9..51ada89e 100644 --- a/source/storage/storage-service/pom.xml +++ b/source/storage/storage-service/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain storage - 1.0.1.RELEASE + 1.0.2.RELEASE storage-service diff --git a/source/test/pom.xml b/source/test/pom.xml index 2980a2ad..57d3bbe4 100644 --- a/source/test/pom.xml +++ b/source/test/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE test pom diff --git a/source/test/test-consensus-client/pom.xml b/source/test/test-consensus-client/pom.xml index ccf014c4..21d48f85 100644 --- a/source/test/test-consensus-client/pom.xml +++ b/source/test/test-consensus-client/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain test - 1.0.1.RELEASE + 1.0.2.RELEASE test-consensus-client diff --git a/source/test/test-consensus-node/pom.xml b/source/test/test-consensus-node/pom.xml index 4c587833..2357ade8 100644 --- a/source/test/test-consensus-node/pom.xml +++ b/source/test/test-consensus-node/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain test - 1.0.1.RELEASE + 1.0.2.RELEASE test-consensus-node @@ -26,7 +26,7 @@ com.jd.blockchain consensus-bftsmart - 1.0.1.RELEASE + 1.0.2.RELEASE org.springframework.boot diff --git a/source/test/test-integration/pom.xml b/source/test/test-integration/pom.xml index 9c231504..623d65b3 100644 --- a/source/test/test-integration/pom.xml +++ b/source/test/test-integration/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain test - 1.0.1.RELEASE + 1.0.2.RELEASE test-integration diff --git a/source/test/test-ledger-core/pom.xml b/source/test/test-ledger-core/pom.xml index e5ea99a3..fbb10661 100644 --- a/source/test/test-ledger-core/pom.xml +++ b/source/test/test-ledger-core/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain test - 1.0.1.RELEASE + 1.0.2.RELEASE test-ledger-core diff --git a/source/tools/pom.xml b/source/tools/pom.xml index 2a52e854..4db6a1e9 100644 --- a/source/tools/pom.xml +++ b/source/tools/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE tools pom diff --git a/source/tools/tools-capability/pom.xml b/source/tools/tools-capability/pom.xml index 294fa416..2c6cafbe 100644 --- a/source/tools/tools-capability/pom.xml +++ b/source/tools/tools-capability/pom.xml @@ -5,7 +5,7 @@ tools com.jd.blockchain - 1.0.1.RELEASE + 1.0.2.RELEASE 4.0.0 diff --git a/source/tools/tools-initializer-booter/pom.xml b/source/tools/tools-initializer-booter/pom.xml index 958388a7..a5ac62a5 100644 --- a/source/tools/tools-initializer-booter/pom.xml +++ b/source/tools/tools-initializer-booter/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain tools - 1.0.1.RELEASE + 1.0.2.RELEASE tools-initializer-booter diff --git a/source/tools/tools-initializer/pom.xml b/source/tools/tools-initializer/pom.xml index ddee1b3d..877e4a43 100644 --- a/source/tools/tools-initializer/pom.xml +++ b/source/tools/tools-initializer/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain tools - 1.0.1.RELEASE + 1.0.2.RELEASE tools-initializer diff --git a/source/tools/tools-keygen-booter/pom.xml b/source/tools/tools-keygen-booter/pom.xml index 0000d7d7..c5308384 100644 --- a/source/tools/tools-keygen-booter/pom.xml +++ b/source/tools/tools-keygen-booter/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain tools - 1.0.1.RELEASE + 1.0.2.RELEASE tools-keygen-booter diff --git a/source/tools/tools-keygen/pom.xml b/source/tools/tools-keygen/pom.xml index 79e20dd0..66b1183d 100644 --- a/source/tools/tools-keygen/pom.xml +++ b/source/tools/tools-keygen/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain tools - 1.0.1.RELEASE + 1.0.2.RELEASE tools-keygen diff --git a/source/tools/tools-mocker/pom.xml b/source/tools/tools-mocker/pom.xml index badb7dae..c185efe2 100644 --- a/source/tools/tools-mocker/pom.xml +++ b/source/tools/tools-mocker/pom.xml @@ -5,7 +5,7 @@ tools com.jd.blockchain - 1.0.1.RELEASE + 1.0.2.RELEASE 4.0.0 diff --git a/source/utils/pom.xml b/source/utils/pom.xml index 0804470c..416ed922 100644 --- a/source/utils/pom.xml +++ b/source/utils/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain jdchain-root - 1.0.1.RELEASE + 1.0.2.RELEASE utils pom diff --git a/source/utils/utils-common/pom.xml b/source/utils/utils-common/pom.xml index c01ef466..0a391efe 100644 --- a/source/utils/utils-common/pom.xml +++ b/source/utils/utils-common/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain utils - 1.0.1.RELEASE + 1.0.2.RELEASE utils-common diff --git a/source/utils/utils-http/pom.xml b/source/utils/utils-http/pom.xml index cf3a6ed7..23a18762 100644 --- a/source/utils/utils-http/pom.xml +++ b/source/utils/utils-http/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain utils - 1.0.1.RELEASE + 1.0.2.RELEASE diff --git a/source/utils/utils-serialize/pom.xml b/source/utils/utils-serialize/pom.xml index 67501bfa..eab12a7f 100644 --- a/source/utils/utils-serialize/pom.xml +++ b/source/utils/utils-serialize/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain utils - 1.0.1.RELEASE + 1.0.2.RELEASE utils-serialize diff --git a/source/utils/utils-test/pom.xml b/source/utils/utils-test/pom.xml index f01f6404..24920b00 100644 --- a/source/utils/utils-test/pom.xml +++ b/source/utils/utils-test/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain utils - 1.0.1.RELEASE + 1.0.2.RELEASE utils-test diff --git a/source/utils/utils-web-server/pom.xml b/source/utils/utils-web-server/pom.xml index 70e3dc55..f7fe8e7d 100644 --- a/source/utils/utils-web-server/pom.xml +++ b/source/utils/utils-web-server/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain utils - 1.0.1.RELEASE + 1.0.2.RELEASE utils-web-server diff --git a/source/utils/utils-web/pom.xml b/source/utils/utils-web/pom.xml index 2979485f..84cb71b1 100644 --- a/source/utils/utils-web/pom.xml +++ b/source/utils/utils-web/pom.xml @@ -4,7 +4,7 @@ com.jd.blockchain utils - 1.0.1.RELEASE + 1.0.2.RELEASE utils-web From 9cdb13c6284eb4ce670424b7c8ead5fb3949b32c Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 5 Sep 2019 09:27:12 +0800 Subject: [PATCH 077/124] Fixed errors of ledger binding test cases; --- .../tools-initializer/src/test/resources/ledger-binding.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/tools/tools-initializer/src/test/resources/ledger-binding.conf b/source/tools/tools-initializer/src/test/resources/ledger-binding.conf index e4cf6c2b..99df03d0 100644 --- a/source/tools/tools-initializer/src/test/resources/ledger-binding.conf +++ b/source/tools/tools-initializer/src/test/resources/ledger-binding.conf @@ -6,7 +6,9 @@ j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf #第1个账本[j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk]的配置; #账本的当前共识参与方的ID; +binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.name = Test-Ledger-01 binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.address=1 +binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.name=parti-1 #账本的当前共识参与方的私钥文件的保存路径; binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.pk-path=keys/jd-com.priv #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; @@ -21,7 +23,9 @@ binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.db.pwd=kksfweffj #第2个账本[j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf]的配置; #账本的当前共识参与方的ID; +binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.name=Test-Ledger-02 binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.address=2 +binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.name=parti-2 #账本的当前共识参与方的私钥文件的保存路径; binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.pk-path=keys/jd-com-1.priv #账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; From 03284140d3c93dd0599109f8cf9d410b5ded78cb Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 5 Sep 2019 09:31:54 +0800 Subject: [PATCH 078/124] Fixed errors of ledger initialization test cases; --- .../test-integration/src/test/resources/ledger_init_test.init | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/test/test-integration/src/test/resources/ledger_init_test.init b/source/test/test-integration/src/test/resources/ledger_init_test.init index a1dde11f..7b303e35 100644 --- a/source/test/test-integration/src/test/resources/ledger_init_test.init +++ b/source/test/test-integration/src/test/resources/ledger_init_test.init @@ -3,7 +3,7 @@ ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe #账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; -ledger.name= +ledger.name=test-ledger #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 From bb1437c9ef56f5f73b5265b056575e4b8aafd2d9 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 5 Sep 2019 17:23:00 +0800 Subject: [PATCH 079/124] Fixed errors introduced from code merging; --- .../DataAccountKVSetOperationHandle.java | 8 ++- .../DataAccountKVSetOperationHandle.java | 55 ------------------- .../ledger/core/ContractInvokingTest.java | 41 +++++++++----- 3 files changed, 32 insertions(+), 72 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index 52801883..76f55220 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.DataAccountDoesNotExistException; import com.jd.blockchain.ledger.DataAccountKVSetOperation; +import com.jd.blockchain.ledger.DataVersionConflictException; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.DataAccount; @@ -33,10 +34,13 @@ public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHand throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); } KVWriteEntry[] writeSet = kvWriteOp.getWriteSet(); + long v = -1; for (KVWriteEntry kvw : writeSet) { - account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); + v = account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); + if (v < 0) { + throw new DataVersionConflictException(); + } } } - } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java deleted file mode 100644 index 75607b51..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/DataAccountKVSetOperationHandle.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.jd.blockchain.ledger.core.impl.handles; - -import org.springframework.stereotype.Service; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.ledger.BytesValue; -import com.jd.blockchain.ledger.DataAccountDoesNotExistException; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; -import com.jd.blockchain.ledger.DataVersionConflictException; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.LedgerDataSet; -import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.OperationHandle; -import com.jd.blockchain.ledger.core.TransactionRequestContext; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; -import com.jd.blockchain.utils.Bytes; - -@Service -public class DataAccountKVSetOperationHandle implements OperationHandle { - static { - DataContractRegistry.register(BytesValue.class); - } - - @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { - DataAccountKVSetOperation kvWriteOp = (DataAccountKVSetOperation) op; - DataAccount account = dataset.getDataAccountSet().getDataAccount(kvWriteOp.getAccountAddress()); - if (account == null) { - throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); - } - KVWriteEntry[] writeSet = kvWriteOp.getWriteSet(); - long v = -1; - for (KVWriteEntry kvw : writeSet) { - v = account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); - if (v < 0) { - throw new DataVersionConflictException(); - } - } - return null; - } - -// @Override -// public AsyncFuture asyncProcess(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { -// return null; -// } - - @Override - public boolean support(Class operationType) { - return DataAccountKVSetOperation.class.isAssignableFrom(operationType); - } - -} diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index 67e8d660..c731b35b 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.times; @@ -29,29 +30,39 @@ import com.jd.blockchain.ledger.EndpointRequest; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerTransaction; import com.jd.blockchain.ledger.NodeRequest; import com.jd.blockchain.ledger.OperationResult; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionContentBody; +import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.ledger.core.LedgerDataSet; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; +import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerSecurityManager; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.LedgerTransactionContext; +import com.jd.blockchain.ledger.core.LedgerTransactionalEditor; +import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.ledger.core.UserAccount; -import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.BooleanValueHolder; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; -import static org.mockito.Matchers.any; + +import test.com.jd.blockchain.ledger.TxTestContract; +import test.com.jd.blockchain.ledger.TxTestContractImpl; public class ContractInvokingTest { static { @@ -172,19 +183,19 @@ public class ContractInvokingTest { // 注册合约处理器; DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - opReg.insertAsTopPriority(contractInvokingHandle); + opReg.registerHandle(contractInvokingHandle); // 发布指定地址合约 deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(getSecurityManager(), newBlockEditor, + previousBlockDataset, opReg, ledgerManager); String key = TxTestContractImpl.KEY; String value = "VAL"; @@ -253,7 +264,7 @@ public class ContractInvokingTest { // 注册合约处理器; DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - opReg.insertAsTopPriority(contractInvokingHandle); + opReg.registerHandle(contractInvokingHandle); // 发布指定地址合约 deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); @@ -303,7 +314,7 @@ public class ContractInvokingTest { assertEquals(1, kv2.getVersion()); assertEquals("V1-1", kv1.getValue()); assertEquals("V2-1", kv2.getValue()); - + // 构建基于接口调用合约的交易请求,用于测试合约调用; buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { @Override @@ -328,10 +339,10 @@ public class ContractInvokingTest { private LedgerBlock buildBlock(LedgerRepository ledgerRepo, LedgerService ledgerService, OperationHandleRegisteration opReg, TxDefinitor txDefinitor) { LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerService); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(getSecurityManager(), newBlockEditor, + previousBlockDataset, opReg, ledgerService); TxBuilder txBuilder = new TxBuilder(ledgerRepo.getHash()); txDefinitor.buildTx(txBuilder); @@ -361,12 +372,12 @@ public class ContractInvokingTest { private void registerDataAccount(LedgerRepository ledgerRepo, LedgerManager ledgerManager, DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair kpDataAccount) { LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, - opReg, ledgerManager); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(getSecurityManager(), newBlockEditor, + previousBlockDataset, opReg, ledgerManager); // 注册数据账户; TxBuilder txBuilder = new TxBuilder(ledgerHash); From cd56b0812b278750174884a5db94df0e65766780 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 6 Sep 2019 01:13:16 +0800 Subject: [PATCH 080/124] Refactored the codes of ledger initialization; --- .../mq/MsgQueueConsensusSettingsBuilder.java | 4 +- .../jd/blockchain/ContractDeployExeUtil.java | 8 +- .../com/jd/blockchain/ContractDeployMojo.java | 6 +- .../com/jd/blockchain/crypto/KeyGenUtils.java | 187 ++++++++++++++++++ .../gateway/GatewayServerBooter.java | 14 +- .../gateway/web/BlockBrowserController.java | 4 +- .../ledger/core/LedgerInitializer.java | 154 +++++++++++++++ .../blockchain/ledger/core/LedgerManage.java | 22 ++- .../blockchain/ledger/core/LedgerManager.java | 27 ++- .../ledger/core/LedgerManagerTest.java | 6 +- .../ledger}/LedgerInitException.java | 4 +- .../ledger}/LedgerInitProperties.java | 21 +- .../ledger/TransactionRequestBuilder.java | 4 +- .../transaction/TxRequestBuilder.java | 16 +- .../sdk/samples/SDKDemo_Params.java | 18 +- .../blockchain/sdk/samples/SDK_Base_Demo.java | 6 +- .../sdk/test/SDKDemo_Contract_Test_.java | 8 +- .../sdk/test/SDK_GateWay_KeyPair_Para.java | 18 +- .../jd/blockchain/intgr/IntegrationTest.java | 16 +- .../intgr/consensus/ConsensusTest.java | 16 +- .../intgr/perf/GlobalPerformanceTest.java | 16 +- .../intgr/perf/LedgerInitializeTest.java | 24 +-- .../intgr/perf/LedgerInitializeWebTest.java | 36 ++-- .../intgr/perf/LedgerPerformanceTest.java | 12 +- .../com/jd/blockchain/intgr/perf/Utils.java | 13 +- .../blockchain/intgr/IntegrationBaseTest.java | 16 +- .../jd/blockchain/intgr/IntegrationTest2.java | 16 +- .../intgr/IntegrationTest4Bftsmart.java | 8 +- .../intgr/IntegrationTest4Contract.java | 8 +- .../blockchain/intgr/IntegrationTest4MQ.java | 8 +- .../intgr/IntegrationTestAll4Redis.java | 21 +- .../intgr/IntegrationTestDataAccount.java | 20 +- .../batch/bftsmart/BftsmartLedgerInit.java | 8 +- .../batch/bftsmart/BftsmartTestBase.java | 4 +- .../initializer/LedgerInitializeTest.java | 22 +-- .../LedgerInitializeWeb4Nodes.java | 18 +- .../LedgerInitializeWeb4SingleStepsTest.java | 20 +- .../ledger/LedgerBlockGeneratingTest.java | 12 +- .../capability/service/SettingsInit.java | 6 +- .../initializer/InitializerConfiguration.java | 8 +- .../tools/initializer/LedgerInitCommand.java | 15 +- .../tools/initializer/LedgerInitProcess.java | 1 + .../web/DecisionResponseConverter.java | 2 +- .../web/LedgerInitializeWebController.java | 115 ++++------- .../web/PermissionResponseConverter.java | 2 +- .../initializer/LedgerInitPropertiesTest.java | 14 +- .../tools/keygen/KeyGenCommand.java | 166 +--------------- .../mocker/MockerLedgerInitializer.java | 143 ++++++++------ .../blockchain/mocker/MockerNodeContext.java | 12 +- .../mocker/handler/MockerNodeHandler.java | 6 +- .../mocker/handler/MockerServiceHandler.java | 6 +- .../mocker/node/NodeWebContext.java | 2 +- 52 files changed, 757 insertions(+), 582 deletions(-) create mode 100644 source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/KeyGenUtils.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java rename source/{tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer => ledger/ledger-model/src/main/java/com/jd/blockchain/ledger}/LedgerInitException.java (75%) rename source/{tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer => ledger/ledger-model/src/main/java/com/jd/blockchain/ledger}/LedgerInitProperties.java (96%) diff --git a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java index 85b1fbc3..3ae907d8 100644 --- a/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java +++ b/source/consensus/consensus-mq/src/main/java/com/jd/blockchain/consensus/mq/MsgQueueConsensusSettingsBuilder.java @@ -20,9 +20,9 @@ import com.jd.blockchain.consensus.mq.settings.MsgQueueConsensusSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueNetworkSettings; import com.jd.blockchain.consensus.mq.settings.MsgQueueNodeSettings; import com.jd.blockchain.crypto.AddressEncoding; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; import com.jd.blockchain.utils.codec.Base58Utils; @@ -129,7 +129,7 @@ public class MsgQueueConsensusSettingsBuilder implements ConsensusSettingsBuilde String keyOfPubkey = nodeKey(PUBKEY_PATTERN, id); String base58PubKey = PropertiesUtils.getRequiredProperty(resolvingProps, keyOfPubkey); - PubKey pubKey = KeyGenCommand.decodePubKey(base58PubKey); + PubKey pubKey = KeyGenUtils.decodePubKey(base58PubKey); // PubKey pubKey = new PubKey(Base58Utils.decode(base58PubKey)); resolvingProps.remove(keyOfPubkey); diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java index a206a854..0b27f422 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java @@ -7,6 +7,7 @@ import java.io.InputStream; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BlockchainIdentity; @@ -29,7 +30,6 @@ import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.Base58Utils; import com.jd.blockchain.utils.net.NetworkAddress; @@ -47,8 +47,8 @@ public enum ContractDeployExeUtil { PubKey pub = null; PrivKey prv = null; try { - prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); - pub = KeyGenCommand.readPubKey(pubPath); + prv = KeyGenUtils.readPrivKey(prvPath, KeyGenUtils.encodePassword(rawPassword)); + pub = KeyGenUtils.readPubKey(pubPath); } catch (Exception e) { e.printStackTrace(); @@ -64,7 +64,7 @@ public enum ContractDeployExeUtil { BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); pub = contractKeyPair.getPubKey(); }else { - pub = KeyGenCommand.readPubKey(pubPath); + pub = KeyGenUtils.readPubKey(pubPath); } } catch (Exception e) { diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java index 3eac13bb..427fe00f 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java @@ -1,10 +1,10 @@ package com.jd.blockchain; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.StringUtils; import com.jd.blockchain.utils.codec.Base58Utils; import com.jd.blockchain.utils.io.FileUtils; @@ -102,8 +102,8 @@ public class ContractDeployMojo extends AbstractMojo { byte[] contractBytes = FileUtils.readBytes(contractPath); - PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); - PubKey pub = KeyGenCommand.decodePubKey(pubKey); + PrivKey prv = KeyGenUtils.decodePrivKeyWithRawPassword(prvKey, password); + PubKey pub = KeyGenUtils.decodePubKey(pubKey); BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); diff --git a/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/KeyGenUtils.java b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/KeyGenUtils.java new file mode 100644 index 00000000..717c813c --- /dev/null +++ b/source/crypto/crypto-framework/src/main/java/com/jd/blockchain/crypto/KeyGenUtils.java @@ -0,0 +1,187 @@ +package com.jd.blockchain.crypto; + +import java.util.Arrays; + +import javax.crypto.SecretKey; + +import com.jd.blockchain.utils.ConsoleUtils; +import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.io.BytesUtils; +import com.jd.blockchain.utils.io.FileUtils; +import com.jd.blockchain.utils.security.AESUtils; +import com.jd.blockchain.utils.security.DecryptionException; +import com.jd.blockchain.utils.security.ShaUtils; + +public class KeyGenUtils { + + private static final byte[] PUB_KEY_FILE_MAGICNUM = { (byte) 0xFF, 112, 117, 98 }; + + private static final byte[] PRIV_KEY_FILE_MAGICNUM = { (byte) 0x00, 112, 114, 118 }; + + /** + * 公钥编码输出为 Base58 字符; + * + * @param pubKey + * @return + */ + public static String encodePubKey(PubKey pubKey) { + byte[] pubKeyBytes = BytesUtils.concat(PUB_KEY_FILE_MAGICNUM, pubKey.toBytes()); + String base58PubKey = Base58Utils.encode(pubKeyBytes); + return base58PubKey; + } + + public static PubKey decodePubKey(String base58PubKey) { + byte[] keyBytes = Base58Utils.decode(base58PubKey); + return decodePubKey(keyBytes); + } + + public static String encodePrivKey(PrivKey privKey, String base58Pwd) { + byte[] pwdBytes = Base58Utils.decode(base58Pwd); + return encodePrivKey(privKey, pwdBytes); + } + + public static String encodePrivKey(PrivKey privKey, byte[] pwdBytes) { + byte[] encodedPrivKeyBytes = encryptPrivKey(privKey, pwdBytes); + String base58PrivKey = Base58Utils.encode(encodedPrivKeyBytes); + return base58PrivKey; + } + + public static byte[] encryptPrivKey(PrivKey privKey, byte[] pwdBytes) { + SecretKey userKey = AESUtils.generateKey128(pwdBytes); + byte[] encryptedPrivKeyBytes = AESUtils.encrypt(privKey.toBytes(), userKey); + return BytesUtils.concat(PRIV_KEY_FILE_MAGICNUM, encryptedPrivKeyBytes); + } + + /** + * @param encodedPubKeyBytes + * @return + */ + private static PubKey decodePubKeyBytes(byte[] encodedPubKeyBytes) { + byte[] pubKeyBytes = Arrays.copyOfRange(encodedPubKeyBytes, PUB_KEY_FILE_MAGICNUM.length, + encodedPubKeyBytes.length); + return new PubKey(pubKeyBytes); + } + + public static PrivKey decryptedPrivKeyBytes(byte[] encodedPrivKeyBytes, byte[] pwdBytes) { + // Read privKye; + SecretKey userKey = AESUtils.generateKey128(pwdBytes); + byte[] encryptedKeyBytes = Arrays.copyOfRange(encodedPrivKeyBytes, PRIV_KEY_FILE_MAGICNUM.length, + encodedPrivKeyBytes.length); + try { + byte[] plainKeyBytes = AESUtils.decrypt(encryptedKeyBytes, userKey); + return new PrivKey(plainKeyBytes); + } catch (DecryptionException e) { + throw new DecryptionException("Invalid password!", e); + } + } + + public static PubKey readPubKey(String keyFile) { + String base58KeyString = FileUtils.readText(keyFile); + return decodePubKey(base58KeyString); + } + + /** + * 解码公钥; + * + * @param encodedPubKeyBytes 从公钥; + * @return + */ + public static PubKey decodePubKey(byte[] encodedPubKeyBytes) { + if (BytesUtils.startsWith(encodedPubKeyBytes, PUB_KEY_FILE_MAGICNUM)) { + // Read pubKey; + return decodePubKeyBytes(encodedPubKeyBytes); + } + + throw new IllegalArgumentException("The specified bytes is not valid PubKey generated by the KeyGen tool!"); + } + + /** + * 从控制台读取加密口令,以二进制数组形式返回原始口令的一次SHA256的结果; + * + * @return + */ + public static byte[] readPassword() { + byte[] pwdBytes = ConsoleUtils.readPassword(); + return ShaUtils.hash_256(pwdBytes); + } + + /** + * 对指定的原始密码进行编码生成用于加解密的密码; + * + * @param rawPassword + * @return + */ + public static byte[] encodePassword(String rawPassword) { + byte[] pwdBytes = BytesUtils.toBytes(rawPassword, "UTF-8"); + return ShaUtils.hash_256(pwdBytes); + } + + /** + * 对指定的原始密码进行编码生成用于加解密的密码; + * + * @param rawPassword + * @return + */ + public static String encodePasswordAsBase58(String rawPassword) { + return Base58Utils.encode(encodePassword(rawPassword)); + } + + /** + * 从控制台读取加密口令,以Base58字符串形式返回口令的一次SHA256的结果; + * + * @return + */ + public static String readPasswordString() { + return Base58Utils.encode(readPassword()); + } + + public static PrivKey readPrivKey(String keyFile, String base58Pwd) { + return readPrivKey(keyFile, Base58Utils.decode(base58Pwd)); + } + + /** + * 从文件读取私钥; + * + * @param keyFile + * @param pwdBytes + * @return + */ + public static PrivKey readPrivKey(String keyFile, byte[] pwdBytes) { + String base58KeyString = FileUtils.readText(keyFile); + byte[] keyBytes = Base58Utils.decode(base58KeyString); + if (!BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { + throw new IllegalArgumentException("The specified file is not a private key file!"); + } + return decryptedPrivKeyBytes(keyBytes, pwdBytes); + } + + public static PrivKey decodePrivKey(String base58Key, String base58Pwd) { + byte[] decryptedKey = Base58Utils.decode(base58Pwd); + return decodePrivKey(base58Key, decryptedKey); + } + + public static PrivKey decodePrivKey(String base58Key, byte[] pwdBytes) { + byte[] keyBytes = Base58Utils.decode(base58Key); + if (!BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { + throw new IllegalArgumentException("The specified file is not a private key file!"); + } + return decryptedPrivKeyBytes(keyBytes, pwdBytes); + } + + public static PrivKey decodePrivKeyWithRawPassword(String base58Key, String rawPassword) { + byte[] pwdBytes = encodePassword(rawPassword); + byte[] keyBytes = Base58Utils.decode(base58Key); + if (!BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { + throw new IllegalArgumentException("The specified file is not a private key file!"); + } + return decryptedPrivKeyBytes(keyBytes, pwdBytes); + } + + public static boolean isPubKeyBytes(byte[] keyBytes) { + return BytesUtils.startsWith(keyBytes, PUB_KEY_FILE_MAGICNUM); + } + + public static boolean isPrivKeyBytes(byte[] keyBytes) { + return BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM); + } +} diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java index 3b9604f3..76c39b61 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java @@ -5,20 +5,20 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; -import com.jd.blockchain.gateway.web.BlockBrowserController; import org.apache.commons.io.FileUtils; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.crypto.AsymmetricKeypair; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.gateway.web.BlockBrowserController; import com.jd.blockchain.utils.ArgumentSet; +import com.jd.blockchain.utils.ArgumentSet.ArgEntry; import com.jd.blockchain.utils.BaseConstant; import com.jd.blockchain.utils.ConsoleUtils; -import com.jd.blockchain.utils.ArgumentSet.ArgEntry; public class GatewayServerBooter { @@ -88,19 +88,19 @@ public class GatewayServerBooter { String base58Pwd = config.keys().getDefault().getPrivKeyPassword(); if (base58Pwd == null || base58Pwd.length() == 0) { - base58Pwd = KeyGenCommand.readPasswordString(); + base58Pwd = KeyGenUtils.readPasswordString(); } // 加载密钥; - PubKey pubKey = KeyGenCommand.decodePubKey(config.keys().getDefault().getPubKeyValue()); + PubKey pubKey = KeyGenUtils.decodePubKey(config.keys().getDefault().getPubKeyValue()); PrivKey privKey = null; String base58PrivKey = config.keys().getDefault().getPrivKeyValue(); if (base58PrivKey == null) { //注:GatewayConfigProperties 确保了 PrivKeyValue 和 PrivKeyPath 必有其一; - privKey = KeyGenCommand.readPrivKey(config.keys().getDefault().getPrivKeyPath(), base58Pwd); + privKey = KeyGenUtils.readPrivKey(config.keys().getDefault().getPrivKeyPath(), base58Pwd); } else { - privKey = KeyGenCommand.decodePrivKey(base58PrivKey, base58Pwd); + privKey = KeyGenUtils.decodePrivKey(base58PrivKey, base58Pwd); } defaultKeyPair = new AsymmetricKeypair(pubKey, privKey); } diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java index 50255bd3..895d2d23 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java @@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.PeerService; import com.jd.blockchain.gateway.decompiler.utils.DecompilerUtils; @@ -34,7 +35,6 @@ import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.sdk.BlockchainExtendQueryService; import com.jd.blockchain.sdk.ContractSettings; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.BaseConstant; import com.jd.blockchain.utils.ConsoleUtils; @@ -482,7 +482,7 @@ public class BlockBrowserController implements BlockchainExtendQueryService { @RequestMapping(method = RequestMethod.GET, path = "utils/pubkey/{pubkey}/addr") public String getAddrByPubKey(@PathVariable(name = "pubkey") String strPubKey) { - PubKey pubKey = KeyGenCommand.decodePubKey(strPubKey); + PubKey pubKey = KeyGenUtils.decodePubKey(strPubKey); return AddressEncoding.generateAddress(pubKey).toBase58(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java new file mode 100644 index 00000000..dc612dfa --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -0,0 +1,154 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.SignatureDigest; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.BlockchainIdentityData; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitException; +import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.TransactionBuilder; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionState; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.storage.service.KVStorageService; +import com.jd.blockchain.transaction.SignatureUtils; +import com.jd.blockchain.transaction.TxBuilder; +import com.jd.blockchain.transaction.TxRequestBuilder; + +public class LedgerInitializer { + + private LedgerInitSetting initSetting; + + private TransactionContent initTxContent; + + private volatile LedgerBlock genesisBlock; + + private volatile LedgerEditor ledgerEditor; + + private volatile boolean committed = false; + + private volatile boolean canceled = false; + + /** + * 初始化生成的账本hash;
        + * + * 在成功执行 {@link #prepareLedger(KVStorageService, DigitalSignature...)} 之前总是返回 + * null; + * + * @return + */ + public HashDigest getLedgerHash() { + return genesisBlock == null ? null : genesisBlock.getHash(); + } + + /** + * @param initSetting + * @param initTxContent + */ + private LedgerInitializer(LedgerInitSetting initSetting, TransactionContent initTxContent) { + this.initSetting = initSetting; + this.initTxContent = initTxContent; + } + + public TransactionContent getTransactionContent() { + return initTxContent; + } + + public static LedgerInitializer create(LedgerInitSetting initSetting) { + // 生成初始化交易,并签署许可; + TransactionBuilder initTxBuilder = new TxBuilder(null);// 账本初始化交易的账本 hash 为 null; + initTxBuilder.ledgers().create(initSetting); + for (ParticipantNode p : initSetting.getConsensusParticipants()) { + // TODO:暂时只支持注册用户的初始化操作; + BlockchainIdentity superUserId = new BlockchainIdentityData(p.getPubKey()); + initTxBuilder.users().register(superUserId); + } + // 账本初始化配置声明的创建时间来初始化交易时间戳;注:不能用本地时间,因为共识节点之间的本地时间系统不一致; + TransactionContent initTxContent = initTxBuilder.prepareContent(initSetting.getCreatedTime()); + return new LedgerInitializer(initSetting, initTxContent); + } + + public SignatureDigest signTransaction(PrivKey privKey) { + return SignatureUtils.sign(initTxContent, privKey); + } + + /** + * 准备创建账本; + * + * @param storageService 存储服务; + * @param nodeSignatures 节点签名列表; + * @return + */ + public LedgerBlock prepareLedger(KVStorageService storageService, DigitalSignature... nodeSignatures) { + if (genesisBlock != null) { + throw new LedgerInitException("The ledger has been prepared!"); + } + // 生成账本; + this.ledgerEditor = createLedgerEditor(this.initSetting, storageService); + this.genesisBlock = prepareLedger(ledgerEditor, nodeSignatures); + + return genesisBlock; + } + + public void commit() { + if (committed) { + throw new LedgerInitException("The ledger has been committed!"); + } + if (canceled) { + throw new LedgerInitException("The ledger has been canceled!"); + } + committed = true; + this.ledgerEditor.commit(); + } + + public void cancel() { + if (canceled) { + throw new LedgerInitException("The ledger has been canceled!"); + } + if (committed) { + throw new LedgerInitException("The ledger has been committed!"); + } + this.ledgerEditor.cancel(); + } + + public static LedgerEditor createLedgerEditor(LedgerInitSetting initSetting, KVStorageService storageService) { + LedgerEditor genesisBlockEditor = LedgerTransactionalEditor.createEditor(initSetting, + LedgerManage.LEDGER_PREFIX, storageService.getExPolicyKVStorage(), + storageService.getVersioningKVStorage()); + return genesisBlockEditor; + } + + /** + * 初始化账本数据,返回创始区块; + * + * @param ledgerEditor + * @return + */ + private LedgerBlock prepareLedger(LedgerEditor ledgerEditor, DigitalSignature... nodeSignatures) { + // 初始化时,自动将参与方注册为账本的用户; + TxRequestBuilder txReqBuilder = new TxRequestBuilder(this.initTxContent); + txReqBuilder.addNodeSignature(nodeSignatures); + + TransactionRequest txRequest = txReqBuilder.buildRequest(); + + LedgerTransactionContext txCtx = ledgerEditor.newTransaction(txRequest); + Operation[] ops = txRequest.getTransactionContent().getOperations(); + // 注册用户; 注:第一个操作是 LedgerInitOperation; + // TODO:暂时只支持注册用户的初始化操作; + for (int i = 1; i < ops.length; i++) { + UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; + txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), + userRegOP.getUserID().getPubKey()); + } + + txCtx.commit(TransactionState.SUCCESS, null); + + return ledgerEditor.prepare(); + } +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java index 0e2ae587..65f8738b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java @@ -12,19 +12,21 @@ import com.jd.blockchain.storage.service.KVStorageService; */ public interface LedgerManage extends LedgerService { + static final String LEDGER_PREFIX = "LDG://"; + LedgerRepository register(HashDigest ledgerHash, KVStorageService storageService); void unregister(HashDigest ledgerHash); - /** - * 创建新账本; - * - * @param initSetting - * 初始化配置; - * @param initPermissions - * 参与者的初始化授权列表;与参与者列表一致; - * @return - */ - LedgerEditor newLedger(LedgerInitSetting initSetting, KVStorageService storageService); +// /** +// * 创建新账本; +// * +// * @param initSetting +// * 初始化配置; +// * @param initPermissions +// * 参与者的初始化授权列表;与参与者列表一致; +// * @return +// */ +// LedgerEditor newLedger(LedgerInitSetting initSetting, KVStorageService storageService); } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java index aad657fc..2e66b7ca 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java @@ -9,7 +9,6 @@ import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.KVStorageService; import com.jd.blockchain.storage.service.VersioningKVStorage; @@ -23,8 +22,6 @@ import com.jd.blockchain.utils.codec.Base58Utils; */ public class LedgerManager implements LedgerManage { - private static final String LEDGER_PREFIX = "LDG://"; - // @Autowired // private ExistentialKVStorage exPolicyStorage; // @@ -138,18 +135,18 @@ public class LedgerManager implements LedgerManage { } } - /* - * (non-Javadoc) - * - * @see com.jd.blockchain.ledger.core.LedgerManager#newLedger(com.jd.blockchain. - * ledger.core.ConsensusConfig, com.jd.blockchain.ledger.core.CryptoConfig) - */ - @Override - public LedgerEditor newLedger(LedgerInitSetting initSetting, KVStorageService storageService) { - LedgerEditor genesisBlockEditor = LedgerTransactionalEditor.createEditor(initSetting, LEDGER_PREFIX, - storageService.getExPolicyKVStorage(), storageService.getVersioningKVStorage()); - return genesisBlockEditor; - } +// /* +// * (non-Javadoc) +// * +// * @see com.jd.blockchain.ledger.core.LedgerManager#newLedger(com.jd.blockchain. +// * ledger.core.ConsensusConfig, com.jd.blockchain.ledger.core.CryptoConfig) +// */ +// @Override +// public LedgerEditor newLedger(LedgerInitSetting initSetting, KVStorageService storageService) { +// LedgerEditor genesisBlockEditor = LedgerTransactionalEditor.createEditor(initSetting, LEDGER_PREFIX, +// storageService.getExPolicyKVStorage(), storageService.getVersioningKVStorage()); +// return genesisBlockEditor; +// } static String getLedgerStoragePrefix(HashDigest ledgerHash) { String base58LedgerHash = Base58Utils.encode(ledgerHash.toBytes()); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java index cf3a294d..1b3b2348 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java @@ -37,6 +37,7 @@ import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DataAccountSet; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerEditor; +import com.jd.blockchain.ledger.core.LedgerInitializer; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerTransactionContext; @@ -81,13 +82,12 @@ public class LedgerManagerTest { public void testLedgerInit() { // 创建账本初始化配置; LedgerInitSetting initSetting = createLedgerInitSetting(); - + // 采用基于内存的 Storage; MemoryKVStorage storage = new MemoryKVStorage(); // 新建账本; - LedgerManager ledgerManager = new LedgerManager(); - LedgerEditor ldgEdt = ledgerManager.newLedger(initSetting, storage); + LedgerEditor ldgEdt = LedgerInitializer.createLedgerEditor(initSetting, storage); // 创建一个模拟的创世交易; TransactionRequest genesisTxReq = LedgerTestUtils.createLedgerInitTxRequest(participants); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitException.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitException.java similarity index 75% rename from source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitException.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitException.java index 3695a19e..c3a31079 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitException.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitException.java @@ -1,6 +1,4 @@ -package com.jd.blockchain.tools.initializer; - -import com.jd.blockchain.ledger.LedgerException; +package com.jd.blockchain.ledger; public class LedgerInitException extends LedgerException{ diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java similarity index 96% rename from source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java index ccc97362..f515eec5 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProperties.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.tools.initializer; +package com.jd.blockchain.ledger; import java.io.File; import java.io.FileNotFoundException; @@ -13,14 +13,8 @@ import java.util.TreeMap; import com.jd.blockchain.consts.Global; import com.jd.blockchain.crypto.AddressEncoding; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.ledger.RoleInitData; -import com.jd.blockchain.ledger.RoleInitSettings; -import com.jd.blockchain.ledger.RolesPolicy; -import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; import com.jd.blockchain.utils.StringUtils; @@ -156,6 +150,11 @@ public class LedgerInitProperties { return null; } + /** + * 私有的构造器; + * + * @param ledgerSeed + */ private LedgerInitProperties(byte[] ledgerSeed) { this.ledgerSeed = ledgerSeed; } @@ -232,7 +231,7 @@ public class LedgerInitProperties { RoleInitData[] rolesInitDatas = rolesInitSettingMap.values() .toArray(new RoleInitData[rolesInitSettingMap.size()]); initProps.setRoles(rolesInitDatas); - + // 解析共识相关的属性; initProps.consensusProvider = PropertiesUtils.getRequiredProperty(props, CONSENSUS_SERVICE_PROVIDER); String consensusConfigFilePath = PropertiesUtils.getRequiredProperty(props, CONSENSUS_CONFIG); @@ -274,10 +273,10 @@ public class LedgerInitProperties { String pubkeyKey = getKeyOfParticipant(i, PART_PUBKEY); String base58PubKey = PropertiesUtils.getProperty(props, pubkeyKey, false); if (base58PubKey != null) { - PubKey pubKey = KeyGenCommand.decodePubKey(base58PubKey); + PubKey pubKey = KeyGenUtils.decodePubKey(base58PubKey); parti.setPubKey(pubKey); } else if (pubkeyPath != null) { - PubKey pubKey = KeyGenCommand.readPubKey(pubkeyPath); + PubKey pubKey = KeyGenUtils.readPubKey(pubkeyPath); parti.setPubKey(pubKey); } else { throw new IllegalArgumentException( diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequestBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequestBuilder.java index 0818d865..87c445ad 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequestBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRequestBuilder.java @@ -60,7 +60,7 @@ public interface TransactionRequestBuilder extends HashObject { * Base64格式的签名摘要; * @return */ - void addEndpointSignature(DigitalSignature signature); + void addEndpointSignature(DigitalSignature... signature); /** * 加入签名; @@ -71,7 +71,7 @@ public interface TransactionRequestBuilder extends HashObject { * Base64格式的签名摘要; * @return */ - void addNodeSignature(DigitalSignature signature); + void addNodeSignature(DigitalSignature... signatures); /** * 生成交易请求; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java index d8627974..68653c0e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxRequestBuilder.java @@ -52,13 +52,21 @@ public class TxRequestBuilder implements TransactionRequestBuilder { } @Override - public void addNodeSignature(DigitalSignature signature) { - nodeSignatures.add(signature); + public void addNodeSignature(DigitalSignature... signatures) { + if (signatures != null) { + for (DigitalSignature s : signatures) { + nodeSignatures.add(s); + } + } } @Override - public void addEndpointSignature(DigitalSignature signature) { - endpointSignatures.add(signature); + public void addEndpointSignature(DigitalSignature... signatures) { + if (signatures != null) { + for (DigitalSignature s : signatures) { + endpointSignatures.add(s); + } + } } // public static DigitalSignature sign(TransactionContent txContent, AsymmetricKeypair keyPair) { diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java index 491abef8..cc2c5bb2 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Params.java @@ -8,9 +8,9 @@ */ package com.jd.blockchain.sdk.samples; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.tools.keygen.KeyGenCommand; /** * @@ -33,13 +33,13 @@ public class SDKDemo_Params { "177gk2FpjufgEon92mf2oRRFXDBZkRy8SkFci7Jxc5pApZEJz3oeCoxieWatDD3Xg7i1QEN", "177gjvv7qvfCAXroFezSn23UFXLVLFofKS3y6DXkJ2DwVWS4LcRNtxRgiqWmQEeWNz4KQ3J" }; - public static PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - public static PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - public static PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - public static PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + public static PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + public static PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + public static PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + public static PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); - public static PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); - public static PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); - public static PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); - public static PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + public static PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); + public static PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); + public static PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); + public static PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); } \ No newline at end of file diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Base_Demo.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Base_Demo.java index bbd4ed71..c2a7ce73 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Base_Demo.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_Base_Demo.java @@ -1,6 +1,7 @@ package com.jd.blockchain.sdk.samples; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BlockchainKeypair; @@ -9,7 +10,6 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.tools.keygen.KeyGenCommand; public abstract class SDK_Base_Demo { @@ -25,9 +25,9 @@ public abstract class SDK_Base_Demo { public void init() { // 生成连接网关的账号 - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(SDKDemo_Constant.PRIV_KEYS[0], SDKDemo_Constant.PASSWORD); + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(SDKDemo_Constant.PRIV_KEYS[0], SDKDemo_Constant.PASSWORD); - PubKey pubKey = KeyGenCommand.decodePubKey(SDKDemo_Constant.PUB_KEYS[0]); + PubKey pubKey = KeyGenUtils.decodePubKey(SDKDemo_Constant.PUB_KEYS[0]); adminKey = new BlockchainKeypair(pubKey, privKey); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDKDemo_Contract_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDKDemo_Contract_Test_.java index 2f7755da..4390c48c 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDKDemo_Contract_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDKDemo_Contract_Test_.java @@ -8,6 +8,7 @@ import org.junit.Test; import com.jd.blockchain.contract.TransferContract; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BlockchainKeyGenerator; @@ -18,9 +19,8 @@ import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.sdk.samples.SDKDemo_Constant; -import com.jd.blockchain.tools.keygen.KeyGenCommand; -import com.jd.blockchain.transaction.LongValueHolder; import com.jd.blockchain.transaction.GenericValueHolder; +import com.jd.blockchain.transaction.LongValueHolder; import com.jd.blockchain.utils.Bytes; public class SDKDemo_Contract_Test_ { @@ -34,10 +34,10 @@ public class SDKDemo_Contract_Test_ { @Before public void init() { // 生成连接网关的账号 - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(SDKDemo_Constant.PRIV_KEYS[0], + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(SDKDemo_Constant.PRIV_KEYS[0], SDKDemo_Constant.PASSWORD); - PubKey pubKey = KeyGenCommand.decodePubKey(SDKDemo_Constant.PUB_KEYS[0]); + PubKey pubKey = KeyGenUtils.decodePubKey(SDKDemo_Constant.PUB_KEYS[0]); adminKey = new BlockchainKeypair(pubKey, privKey); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java index 450e5ff4..706cd370 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_KeyPair_Para.java @@ -1,8 +1,8 @@ package test.com.jd.blockchain.sdk.test; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.tools.keygen.KeyGenCommand; /** * Created by zhangshuang3 on 2018/10/17. @@ -21,14 +21,14 @@ public class SDK_GateWay_KeyPair_Para { "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns" }; - public static PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - public static PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - public static PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - public static PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + public static PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + public static PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + public static PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + public static PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); - public static PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); - public static PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); - public static PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); - public static PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + public static PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); + public static PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); + public static PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); + public static PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java index 014dcd62..e6c999e8 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java @@ -17,6 +17,7 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; @@ -28,6 +29,7 @@ import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; @@ -42,9 +44,7 @@ import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.KVStorageService; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -110,7 +110,7 @@ public class IntegrationTest { peerStarting2.waitReturn(); peerStarting3.waitReturn(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWebTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWebTest.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(LedgerInitializeWebTest.PUB_KEYS[0]); @@ -452,16 +452,16 @@ public class IntegrationTest { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); LedgerInitializeWebTest.NodeWebContext nodeCtx3 = new LedgerInitializeWebTest.NodeWebContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[0], + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[0], LedgerInitializeWebTest.PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[1], + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[1], LedgerInitializeWebTest.PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[2], + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[2], LedgerInitializeWebTest.PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[3], + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWebTest.PRIV_KEYS[3], LedgerInitializeWebTest.PASSWORD); - String encodedPassword = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWebTest.PASSWORD); + String encodedPassword = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWebTest.PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java index b954a08f..05493cdb 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java @@ -16,12 +16,14 @@ import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionTemplate; @@ -37,10 +39,8 @@ import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitCommand; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.ConsoleUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -90,7 +90,7 @@ public class ConsensusTest { peerStarting2.waitReturn(); peerStarting3.waitReturn(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(Utils.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(Utils.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(Utils.PUB_KEYS[0]); @@ -205,12 +205,12 @@ public class ConsensusTest { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeInitContext nodeCtx3 = new NodeInitContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[0], Utils.PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[1], Utils.PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[2], Utils.PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[3], Utils.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[0], Utils.PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[1], Utils.PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[2], Utils.PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[3], Utils.PASSWORD); - String encodedPassword = KeyGenCommand.encodePasswordAsBase58(Utils.PASSWORD); + String encodedPassword = KeyGenUtils.encodePasswordAsBase58(Utils.PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java index 178cb46a..516fb8d4 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java @@ -18,12 +18,14 @@ import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionTemplate; @@ -38,10 +40,8 @@ import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitCommand; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.ConsoleUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -88,7 +88,7 @@ public class GlobalPerformanceTest { peerStarting2.waitReturn(); peerStarting3.waitReturn(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(Utils.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(Utils.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(Utils.PUB_KEYS[0]); @@ -203,12 +203,12 @@ public class GlobalPerformanceTest { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeInitContext nodeCtx3 = new NodeInitContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[0], Utils.PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[1], Utils.PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[2], Utils.PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[3], Utils.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[0], Utils.PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[1], Utils.PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[2], Utils.PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[3], Utils.PASSWORD); - String encodedPassword = KeyGenCommand.encodePasswordAsBase58(Utils.PASSWORD); + String encodedPassword = KeyGenUtils.encodePasswordAsBase58(Utils.PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index d33f0cb9..39618d9d 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -15,10 +15,12 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; @@ -30,12 +32,10 @@ import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; //import com.jd.blockchain.storage.service.utils.MemoryBasedDb; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.InitConsensusServiceFactory; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -81,22 +81,22 @@ public class LedgerInitializeTest { String[] memoryConnString = new String[] { "memory://local/0", "memory://local/1", "memory://local/2", "memory://local/3" }; - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); DBConnectionConfig testDb0 = new DBConnectionConfig(); testDb0.setConnectionUri(memoryConnString[0]); AsyncCallback callback0 = node0.startInit(0, privkey0, initSetting, testDb0, consolePrompter); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); DBConnectionConfig testDb1 = new DBConnectionConfig(); testDb1.setConnectionUri(memoryConnString[1]); AsyncCallback callback1 = node1.startInit(1, privkey1, initSetting, testDb1, consolePrompter); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); DBConnectionConfig testDb2 = new DBConnectionConfig(); testDb2.setConnectionUri(memoryConnString[2]); AsyncCallback callback2 = node2.startInit(2, privkey2, initSetting, testDb2, consolePrompter); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); DBConnectionConfig testDb03 = new DBConnectionConfig(); testDb03.setConnectionUri(memoryConnString[3]); AsyncCallback callback3 = node3.startInit(3, privkey3, initSetting, testDb03, consolePrompter); @@ -115,19 +115,19 @@ public class LedgerInitializeTest { UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); UserAccount user0_0 = userset0.getUser(address0); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); UserAccount user1_0 = userset0.getUser(address1); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); UserAccount user2_0 = userset0.getUser(address2); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); UserAccount user3_0 = userset0.getUser(address3); } @@ -182,8 +182,8 @@ public class LedgerInitializeTest { public NodeContext(NetworkAddress address, Map serviceRegisterMap) { this.initCsServiceFactory = new MultiThreadInterInvokerFactory(serviceRegisterMap); - LedgerInitializeWebController initController = new LedgerInitializeWebController(ledgerManager, - memoryDBConnFactory, initCsServiceFactory); + LedgerInitializeWebController initController = new LedgerInitializeWebController(memoryDBConnFactory, + initCsServiceFactory); serviceRegisterMap.put(address, initController); this.initProcess = initController; } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index 122bcf00..7b49bfb7 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -16,12 +16,14 @@ import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.crypto.SignatureFunction; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.UserRegisterOperation; @@ -38,12 +40,10 @@ import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitCommand; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.HttpInitConsensServiceFactory; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -101,15 +101,15 @@ public class LedgerInitializeWebTest { node2.setPrompter(prompter); node3.setPrompter(prompter); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); // 测试生成“账本初始化许可”; LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); @@ -259,10 +259,10 @@ public class LedgerInitializeWebTest { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeWebContext node3 = new NodeWebContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); @@ -300,19 +300,19 @@ public class LedgerInitializeWebTest { UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); UserAccount user0_0 = userset0.getUser(address0); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); UserAccount user1_0 = userset0.getUser(address1); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); UserAccount user2_0 = userset0.getUser(address2); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); UserAccount user3_0 = userset0.getUser(address3); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index 07c05e08..03243aa4 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -21,6 +21,7 @@ import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BlockchainKeyGenerator; @@ -30,6 +31,7 @@ import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerSecurityException; import com.jd.blockchain.ledger.TransactionPermission; @@ -54,10 +56,8 @@ import com.jd.blockchain.storage.service.impl.redis.RedisStorageService; import com.jd.blockchain.storage.service.impl.rocksdb.RocksDBConnectionFactory; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.ArgumentSet; import com.jd.blockchain.utils.Bytes; @@ -559,19 +559,19 @@ public class LedgerPerformanceTest { NodeContext node3 = new NodeContext(initSetting.getConsensusParticipant(3).getInitializerAddress(), serviceRegisterMap, dbsetting3.connectionFactory); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[0], Utils.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[0], Utils.PASSWORD); AsyncCallback callback0 = node0.startInit(0, privkey0, initSetting, csProps, csProvider, dbsetting0.connectionConfig, consolePrompter, !optimized, hashAlg); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[1], Utils.PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[1], Utils.PASSWORD); AsyncCallback callback1 = node1.startInit(1, privkey1, initSetting, csProps, csProvider, dbsetting1.connectionConfig, consolePrompter, !optimized, hashAlg); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[2], Utils.PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[2], Utils.PASSWORD); AsyncCallback callback2 = node2.startInit(2, privkey2, initSetting, csProps, csProvider, dbsetting2.connectionConfig, consolePrompter, !optimized, hashAlg); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[3], Utils.PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(Utils.PRIV_KEYS[3], Utils.PASSWORD); AsyncCallback callback3 = node3.startInit(3, privkey3, initSetting, csProps, csProvider, dbsetting3.connectionConfig, consolePrompter, !optimized, hashAlg); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index d8424013..f6f92da4 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -16,12 +16,14 @@ import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.CryptoAlgorithm; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; @@ -31,12 +33,10 @@ import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.InitConsensusServiceFactory; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -47,8 +47,7 @@ public class Utils { public static final String PASSWORD = "abc"; - public static final String[] PUB_KEYS = { - "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", + public static final String[] PUB_KEYS = { "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk" }; @@ -86,7 +85,7 @@ public class Utils { public static ParticipantNode[] loadParticipantNodes() { ParticipantNode[] participantNodes = new ParticipantNode[PUB_KEYS.length]; for (int i = 0; i < PUB_KEYS.length; i++) { - participantNodes[i] = new PartNode(i, KeyGenCommand.decodePubKey(PUB_KEYS[i])); + participantNodes[i] = new PartNode(i, KeyGenUtils.decodePubKey(PUB_KEYS[i])); } return participantNodes; } @@ -119,8 +118,8 @@ public class Utils { DbConnectionFactory dbConnFactory) { this.dbConnFactory = dbConnFactory; this.initCsServiceFactory = new MultiThreadInterInvokerFactory(serviceRegisterMap); - LedgerInitializeWebController initController = new LedgerInitializeWebController(ledgerManager, - dbConnFactory, initCsServiceFactory); + LedgerInitializeWebController initController = new LedgerInitializeWebController(dbConnFactory, + initCsServiceFactory); serviceRegisterMap.put(address, initController); this.initProcess = initController; } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java index 0fb2d461..46df4b5a 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java @@ -14,15 +14,15 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; import com.jd.blockchain.utils.net.NetworkAddress; @@ -68,7 +68,7 @@ public class IntegrationBaseTest { peerStarting2.waitReturn(); peerStarting3.waitReturn(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(LedgerInitializeWeb4SingleStepsTest.PUB_KEYS[0]); @@ -140,16 +140,16 @@ public class IntegrationBaseTest { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeWebContext nodeCtx3 = new NodeWebContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[1], + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[1], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[2], + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[2], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[3], + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[3], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - String encodedPassword = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedPassword = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java index 3e5a1e85..cf2774b1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java @@ -18,12 +18,14 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; @@ -32,9 +34,7 @@ import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; import com.jd.blockchain.utils.net.NetworkAddress; @@ -85,7 +85,7 @@ public class IntegrationTest2 { peerStarting2.waitReturn(); peerStarting3.waitReturn(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(LedgerInitializeWeb4SingleStepsTest.PUB_KEYS[0]); @@ -178,16 +178,16 @@ public class IntegrationTest2 { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeWebContext nodeCtx3 = new NodeWebContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[1], + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[1], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[2], + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[2], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[3], + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[3], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - String encodedPassword = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedPassword = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index 9b7ae2ba..1838495d 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -2,6 +2,7 @@ package test.com.jd.blockchain.intgr; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; @@ -11,7 +12,6 @@ import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import org.junit.Test; @@ -65,7 +65,7 @@ public class IntegrationTest4Bftsmart { DbConnectionFactory dbConnectionFactory2 = peerNodes[2].getDBConnectionFactory(); DbConnectionFactory dbConnectionFactory3 = peerNodes[3].getDBConnectionFactory(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); GatewayConfigProperties.KeyPairConfig gwkey0 = new GatewayConfigProperties.KeyPairConfig(); gwkey0.setPubKeyValue(IntegrationBase.PUB_KEYS[0]); @@ -97,9 +97,9 @@ public class IntegrationTest4Bftsmart { GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(IntegrationBase.PRIV_KEYS[0], IntegrationBase.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(IntegrationBase.PRIV_KEYS[0], IntegrationBase.PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(IntegrationBase.PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(IntegrationBase.PUB_KEYS[0]); AsymmetricKeypair adminKey = new AsymmetricKeypair(pubKey0, privkey0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java index b2067f62..dc2c3ea1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java @@ -2,6 +2,7 @@ package test.com.jd.blockchain.intgr; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; @@ -11,7 +12,6 @@ import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import org.junit.Test; @@ -46,7 +46,7 @@ public class IntegrationTest4Contract { DbConnectionFactory dbConnectionFactory2 = peerNodes[2].getDBConnectionFactory(); DbConnectionFactory dbConnectionFactory3 = peerNodes[3].getDBConnectionFactory(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); GatewayConfigProperties.KeyPairConfig gwkey0 = new GatewayConfigProperties.KeyPairConfig(); gwkey0.setPubKeyValue(IntegrationBase.PUB_KEYS[0]); @@ -78,9 +78,9 @@ public class IntegrationTest4Contract { GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(IntegrationBase.PRIV_KEYS[0], IntegrationBase.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(IntegrationBase.PRIV_KEYS[0], IntegrationBase.PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(IntegrationBase.PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(IntegrationBase.PUB_KEYS[0]); AsymmetricKeypair adminKey = new AsymmetricKeypair(pubKey0, privkey0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index 6e8c7f1a..f401941c 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -2,6 +2,7 @@ package test.com.jd.blockchain.intgr; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; @@ -11,7 +12,6 @@ import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; import com.jd.blockchain.utils.io.FileUtils; @@ -72,7 +72,7 @@ public class IntegrationTest4MQ { DbConnectionFactory dbConnectionFactory2 = peerNodes[2].getDBConnectionFactory(); DbConnectionFactory dbConnectionFactory3 = peerNodes[3].getDBConnectionFactory(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(IntegrationBase.PUB_KEYS[0]); @@ -107,9 +107,9 @@ public class IntegrationTest4MQ { GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(IntegrationBase.PRIV_KEYS[0], IntegrationBase.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(IntegrationBase.PRIV_KEYS[0], IntegrationBase.PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(IntegrationBase.PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(IntegrationBase.PUB_KEYS[0]); AsymmetricKeypair adminKey = new AsymmetricKeypair(pubKey0, privkey0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java index 00470903..4ee5c5e8 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java @@ -2,7 +2,6 @@ package test.com.jd.blockchain.intgr; import com.jd.blockchain.crypto.*; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; -import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DataAccountSet; @@ -14,8 +13,6 @@ import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -99,7 +96,7 @@ public class IntegrationTestAll4Redis { DbConnectionFactory dbConnectionFactory2 = peer2.getDBConnectionFactory(); DbConnectionFactory dbConnectionFactory3 = peer3.getDBConnectionFactory(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(PUB_KEYS[0]); @@ -118,15 +115,15 @@ public class IntegrationTestAll4Redis { dbConnectionFactory3 }); testConsistencyAmongNodes(ledgers); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); AsymmetricKeypair adminKey = new AsymmetricKeypair(pubKey0, privkey0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java index e23fc48a..4efd7ae5 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java @@ -16,6 +16,7 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; @@ -24,6 +25,7 @@ import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.DataAccountKVSetOperation; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; @@ -35,9 +37,7 @@ import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; import com.jd.blockchain.utils.io.BytesUtils; @@ -99,7 +99,7 @@ public class IntegrationTestDataAccount { peerStarting2.waitReturn(); peerStarting3.waitReturn(); - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); KeyPairConfig gwkey0 = new KeyPairConfig(); gwkey0.setPubKeyValue(LedgerInitializeWeb4SingleStepsTest.PUB_KEYS[0]); @@ -123,9 +123,9 @@ public class IntegrationTestDataAccount { testConsistencyAmongNodes(context); // temp test add - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(LedgerInitializeWeb4SingleStepsTest.PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(LedgerInitializeWeb4SingleStepsTest.PUB_KEYS[0]); AsymmetricKeypair adminKey = new AsymmetricKeypair(pubKey0, privkey0); // regist data account @@ -275,16 +275,16 @@ public class IntegrationTestDataAccount { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeWebContext nodeCtx3 = new NodeWebContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[0], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[1], + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[1], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[2], + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[2], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[3], + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeWeb4SingleStepsTest.PRIV_KEYS[3], LedgerInitializeWeb4SingleStepsTest.PASSWORD); - String encodedPassword = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); + String encodedPassword = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeWeb4SingleStepsTest.PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java index 11f7d8e6..4d45e4a0 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java @@ -10,6 +10,7 @@ package test.com.jd.blockchain.intgr.batch.bftsmart; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; @@ -21,7 +22,6 @@ import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitCommand; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.io.FileUtils; import com.jd.blockchain.utils.net.NetworkAddress; @@ -177,9 +177,9 @@ public class BftsmartLedgerInit { GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(BftsmartConfig.PRIV_KEY[0], IntegrationBase.PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(BftsmartConfig.PRIV_KEY[0], IntegrationBase.PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(BftsmartConfig.PUB_KEY[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(BftsmartConfig.PUB_KEY[0]); AsymmetricKeypair adminKey = new AsymmetricKeypair(pubKey0, privkey0); @@ -233,7 +233,7 @@ public class BftsmartLedgerInit { } public GatewayTestRunner initGateWay(PeerTestRunner peerNode) { - String encodedBase58Pwd = KeyGenCommand.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); + String encodedBase58Pwd = KeyGenUtils.encodePasswordAsBase58(LedgerInitializeTest.PASSWORD); GatewayConfigProperties.KeyPairConfig gwkey0 = new GatewayConfigProperties.KeyPairConfig(); gwkey0.setPubKeyValue(BftsmartConfig.PUB_KEY[0]); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartTestBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartTestBase.java index f94f9364..9a485a22 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartTestBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartTestBase.java @@ -8,8 +8,8 @@ */ package test.com.jd.blockchain.intgr.batch.bftsmart; -import static com.jd.blockchain.tools.keygen.KeyGenCommand.encodePrivKey; -import static com.jd.blockchain.tools.keygen.KeyGenCommand.encodePubKey; +import static com.jd.blockchain.crypto.KeyGenUtils.encodePrivKey; +import static com.jd.blockchain.crypto.KeyGenUtils.encodePubKey; import org.junit.Test; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index 101c874b..813c964c 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -18,6 +18,7 @@ import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.SignatureDigest; @@ -25,6 +26,7 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; @@ -36,12 +38,10 @@ import com.jd.blockchain.ledger.core.UserAccountSet; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.InitConsensusServiceFactory; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -99,22 +99,22 @@ public class LedgerInitializeTest { NodeContext node3 = new NodeContext(initSetting.getConsensusParticipant(3).getInitializerAddress(), serviceRegisterMap); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); DBConnectionConfig testDb0 = new DBConnectionConfig(); testDb0.setConnectionUri(dbConnections[0]); AsyncCallback callback0 = node0.startInit(0, privkey0, initSetting, testDb0, consolePrompter); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); DBConnectionConfig testDb1 = new DBConnectionConfig(); testDb1.setConnectionUri(dbConnections[1]); AsyncCallback callback1 = node1.startInit(1, privkey1, initSetting, testDb1, consolePrompter); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); DBConnectionConfig testDb2 = new DBConnectionConfig(); testDb2.setConnectionUri(dbConnections[2]); AsyncCallback callback2 = node2.startInit(2, privkey2, initSetting, testDb2, consolePrompter); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); DBConnectionConfig testDb03 = new DBConnectionConfig(); testDb03.setConnectionUri(dbConnections[3]); AsyncCallback callback3 = node3.startInit(3, privkey3, initSetting, testDb03, consolePrompter); @@ -145,22 +145,22 @@ public class LedgerInitializeTest { UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); UserAccount user0_0 = userset0.getUser(address0); assertNotNull(user0_0); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); UserAccount user1_0 = userset0.getUser(address1); assertNotNull(user1_0); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); UserAccount user2_0 = userset0.getUser(address2); assertNotNull(user2_0); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); UserAccount user3_0 = userset0.getUser(address3); assertNotNull(user3_0); @@ -225,7 +225,7 @@ public class LedgerInitializeTest { public NodeContext(NetworkAddress address, Map serviceRegisterMap) { this.initCsServiceFactory = new MultiThreadInterInvokerFactory(serviceRegisterMap); - LedgerInitializeWebController initController = new LedgerInitializeWebController(ledgerManager, storageDb, + LedgerInitializeWebController initController = new LedgerInitializeWebController(storageDb, initCsServiceFactory); serviceRegisterMap.put(address, initController); this.initProcess = initController; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index 9831cf24..939811a1 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -4,6 +4,7 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; @@ -12,7 +13,6 @@ import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; import com.jd.blockchain.tools.initializer.*; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -87,10 +87,10 @@ public class LedgerInitializeWeb4Nodes { NetworkAddress initAddr3 = initSetting.getConsensusParticipant(3).getInitializerAddress(); NodeWebContext node3 = new NodeWebContext(3, initAddr3); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); CountDownLatch quitLatch = new CountDownLatch(4); @@ -140,25 +140,25 @@ public class LedgerInitializeWeb4Nodes { UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); System.out.printf("localNodeAddress0 = %s \r\n", address0.toBase58()); UserAccount user0_0 = userset0.getUser(address0); assertNotNull(user0_0); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); UserAccount user1_0 = userset0.getUser(address1); assertNotNull(user1_0); System.out.printf("localNodeAddress1 = %s \r\n", address1.toBase58()); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); UserAccount user2_0 = userset0.getUser(address2); assertNotNull(user2_0); System.out.printf("localNodeAddress2 = %s \r\n", address2.toBase58()); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); UserAccount user3_0 = userset0.getUser(address3); assertNotNull(user3_0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java index 081bf1d1..e79afbf4 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java @@ -19,10 +19,12 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.UserRegisterOperation; @@ -36,12 +38,10 @@ import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.tools.initializer.LedgerInitCommand; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.HttpInitConsensServiceFactory; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.transaction.TxRequestBuilder; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -105,15 +105,15 @@ public class LedgerInitializeWeb4SingleStepsTest { node2.setPrompter(prompter); node3.setPrompter(prompter); - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[0], PASSWORD); + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[1], PASSWORD); + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[2], PASSWORD); + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[3], PASSWORD); - PubKey pubKey0 = KeyGenCommand.decodePubKey(PUB_KEYS[0]); - PubKey pubKey1 = KeyGenCommand.decodePubKey(PUB_KEYS[1]); - PubKey pubKey2 = KeyGenCommand.decodePubKey(PUB_KEYS[2]); - PubKey pubKey3 = KeyGenCommand.decodePubKey(PUB_KEYS[3]); + PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); + PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); + PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); + PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); // 测试生成“账本初始化许可”; LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index c3d268fd..27f53934 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -18,10 +18,12 @@ import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; @@ -32,10 +34,8 @@ import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.tools.initializer.DBConnectionConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.ConsoleUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; @@ -146,28 +146,28 @@ public class LedgerBlockGeneratingTest { String[] memConns = new String[] { "memory://local/0", "memory://local/1", "memory://local/2", "memory://local/3" }; - PrivKey privkey0 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[0], + PrivKey privkey0 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[0], LedgerInitializeTest.PASSWORD); DBConnectionConfig testDb0 = new DBConnectionConfig(); testDb0.setConnectionUri(memConns[0]); AsyncCallback callback0 = node0.startInit(0, privkey0, initSetting, testDb0, consolePrompter, !optimized); - PrivKey privkey1 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[1], + PrivKey privkey1 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[1], LedgerInitializeTest.PASSWORD); DBConnectionConfig testDb1 = new DBConnectionConfig(); testDb1.setConnectionUri(memConns[1]); AsyncCallback callback1 = node1.startInit(1, privkey1, initSetting, testDb1, consolePrompter, !optimized); - PrivKey privkey2 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[2], + PrivKey privkey2 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[2], LedgerInitializeTest.PASSWORD); DBConnectionConfig testDb2 = new DBConnectionConfig(); testDb2.setConnectionUri(memConns[2]); AsyncCallback callback2 = node2.startInit(2, privkey2, initSetting, testDb2, consolePrompter, !optimized); - PrivKey privkey3 = KeyGenCommand.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[3], + PrivKey privkey3 = KeyGenUtils.decodePrivKeyWithRawPassword(LedgerInitializeTest.PRIV_KEYS[3], LedgerInitializeTest.PASSWORD); DBConnectionConfig testDb03 = new DBConnectionConfig(); testDb03.setConnectionUri(memConns[3]); diff --git a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java index 4723d615..db5e8d1e 100644 --- a/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java +++ b/source/tools/tools-capability/src/main/java/com/jd/blockchain/capability/service/SettingsInit.java @@ -15,6 +15,7 @@ import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; import com.jd.blockchain.consensus.bftsmart.BftsmartNodeSettings; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.ContractCodeDeployOperation; @@ -30,7 +31,6 @@ import com.jd.blockchain.ledger.TransactionContentBody; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.codec.Base58Utils; /** @@ -116,8 +116,8 @@ public class SettingsInit { CapabilitySettings.ledgerHash = hash; // 处理用户 - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(settings.getPrivKey(), settings.getPwd()); - PubKey pubKey = KeyGenCommand.decodePubKey(settings.getPubKey()); + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(settings.getPrivKey(), settings.getPwd()); + PubKey pubKey = KeyGenUtils.decodePubKey(settings.getPubKey()); CapabilitySettings.adminKey = new AsymmetricKeypair(pubKey, privKey); } diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/InitializerConfiguration.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/InitializerConfiguration.java index 3ad076d9..5129433f 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/InitializerConfiguration.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/InitializerConfiguration.java @@ -2,8 +2,12 @@ package com.jd.blockchain.tools.initializer; import org.springframework.context.annotation.Configuration; +/** + * Spring Boot 项目的配置类; + * + * @author huanghaiquan + * + */ @Configuration public interface InitializerConfiguration { - - } diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index 0c59c1cc..b288c3d7 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -12,12 +12,13 @@ import org.springframework.context.ConfigurableApplicationContext; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.tools.initializer.LedgerBindingConfig.BindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.ArgumentSet; import com.jd.blockchain.utils.ArgumentSet.ArgEntry; import com.jd.blockchain.utils.ArgumentSet.Setting; @@ -86,7 +87,7 @@ public class LedgerInitCommand { // load ledger init setting; LedgerInitProperties ledgerInitProperties = LedgerInitProperties.resolve(iniArg.getValue()); String localNodePubKeyString = localConf.getLocal().getPubKeyString(); - PubKey localNodePubKey = KeyGenCommand.decodePubKey(localNodePubKeyString); + PubKey localNodePubKey = KeyGenUtils.decodePubKey(localNodePubKeyString); // 地址根据公钥生成 String localNodeAddress = AddressEncoding.generateAddress(localNodePubKey).toBase58(); @@ -97,7 +98,7 @@ public class LedgerInitCommand { // String partiAddress = partiConf.getAddress(); // if (partiAddress == null) { // if (partiConf.getPubKeyPath() != null) { -// PubKey pubKey = KeyGenCommand.readPubKey(partiConf.getPubKeyPath()); +// PubKey pubKey = KeyGenUtils.readPubKey(partiConf.getPubKeyPath()); // partiConf.setPubKey(pubKey); // partiAddress = partiConf.getAddress(); // } @@ -114,9 +115,9 @@ public class LedgerInitCommand { // 加载当前节点的私钥; String base58Pwd = localConf.getLocal().getPassword(); if (base58Pwd == null) { - base58Pwd = KeyGenCommand.readPasswordString(); + base58Pwd = KeyGenUtils.readPasswordString(); } - PrivKey privKey = KeyGenCommand.decodePrivKey(localConf.getLocal().getPrivKeyString(), base58Pwd); + PrivKey privKey = KeyGenUtils.decodePrivKey(localConf.getLocal().getPrivKeyString(), base58Pwd); // Output ledger binding config of peer; if (!FileUtils.existDirectory(localConf.getBindingOutDir())) { @@ -189,7 +190,7 @@ public class LedgerInitCommand { // 设置参与方名称 bindingConf.getParticipant().setName(ledgerInitProperties.getConsensusParticipant(currId).getName()); - String encodedPrivKey = KeyGenCommand.encodePrivKey(privKey, base58Pwd); + String encodedPrivKey = KeyGenUtils.encodePrivKey(privKey, base58Pwd); bindingConf.getParticipant().setPk(encodedPrivKey); bindingConf.getParticipant().setPassword(base58Pwd); diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java index 58b01af5..ffa52d4f 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java @@ -3,6 +3,7 @@ package com.jd.blockchain.tools.initializer; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerInitProperties; /** * diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/DecisionResponseConverter.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/DecisionResponseConverter.java index ba60ec8c..e9d3017d 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/DecisionResponseConverter.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/DecisionResponseConverter.java @@ -3,7 +3,7 @@ package com.jd.blockchain.tools.initializer.web; import java.io.InputStream; import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.tools.initializer.LedgerInitException; +import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.utils.http.HttpServiceContext; import com.jd.blockchain.utils.http.ResponseConverter; import com.jd.blockchain.utils.http.agent.ServiceRequest; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index 2b930628..cd30494a 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -10,7 +10,6 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import com.jd.blockchain.transaction.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -31,34 +30,29 @@ import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.crypto.SignatureFunction; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainIdentityData; import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.LedgerInitException; +import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.ledger.TransactionBuilder; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; -import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerInitProposalData; -import com.jd.blockchain.ledger.core.LedgerManage; -import com.jd.blockchain.ledger.core.LedgerTransactionContext; +import com.jd.blockchain.ledger.core.LedgerInitializer; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.InitializingStep; -import com.jd.blockchain.tools.initializer.LedgerInitException; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; -import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.tools.initializer.Prompter; +import com.jd.blockchain.transaction.DigitalSignatureBlob; +import com.jd.blockchain.transaction.LedgerInitData; +import com.jd.blockchain.transaction.SignatureUtils; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.InvocationResult; import com.jd.blockchain.utils.io.BytesUtils; @@ -86,7 +80,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private volatile LedgerInitProposal localPermission; - private TransactionContent initTxContent; + private volatile LedgerInitializer initializer; private volatile int currentId = -1; @@ -100,19 +94,12 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private volatile ConsensusProvider consensusProvider; - private volatile LedgerBlock genesisBlock; - private volatile LedgerInitDecision localDecision; private volatile DecisionResultHandle[] decisions; private volatile DbConnection dbConn; - private volatile LedgerEditor ledgerEditor; - - @Autowired - private LedgerManage ledgerManager; - @Autowired private DbConnectionFactory dbConnFactory; @@ -123,11 +110,10 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI this.SIGN_FUNC = Crypto.getSignatureFunction(DEFAULT_SIGN_ALGORITHM); } - public LedgerInitializeWebController(LedgerManage ledgerManager, DbConnectionFactory dbConnFactory, + public LedgerInitializeWebController(DbConnectionFactory dbConnFactory, InitConsensusServiceFactory initCsServiceFactory) { this.SIGN_FUNC = Crypto.getSignatureFunction(DEFAULT_SIGN_ALGORITHM); - this.ledgerManager = ledgerManager; this.dbConnFactory = dbConnFactory; this.initCsServiceFactory = initCsServiceFactory; } @@ -137,7 +123,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI } public TransactionContent getInitTxContent() { - return initTxContent; + return initializer.getTransactionContent(); } public LedgerInitProposal getLocalPermission() { @@ -152,10 +138,6 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI this.prompter = prompter; } -// private ConsensusProvider getConsensusProvider() { -// return consensusProvider; -// } - private void setConsensusProvider(ConsensusProvider consensusProvider) { this.consensusProvider = consensusProvider; } @@ -226,11 +208,10 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI public LedgerInitDecision makeLocalDecision(PrivKey privKey) { // 生成账本; - this.ledgerEditor = ledgerManager.newLedger(this.ledgerInitSetting, dbConn.getStorageService()); - this.genesisBlock = initLedgerDataset(ledgerEditor); + initializer.prepareLedger(dbConn.getStorageService(), getNodesSignatures()); // 生成签名决定; - this.localDecision = makeDecision(currentId, genesisBlock.getHash(), privKey); + this.localDecision = makeDecision(currentId, initializer.getLedgerHash(), privKey); this.decisions = new DecisionResultHandle[this.ledgerInitSetting.getConsensusParticipants().length]; for (int i = 0; i < decisions.length; i++) { // 参与者的 id 是依次递增的; @@ -241,6 +222,18 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI return localDecision; } + private DigitalSignature[] getNodesSignatures() { + ParticipantNode[] parties = this.ledgerInitSetting.getConsensusParticipants(); + DigitalSignature[] signatures = new DigitalSignature[parties.length]; + for (int i = 0; i < parties.length; i++) { + PubKey pubKey = parties[i].getPubKey(); + SignatureDigest signDigest = this.permissions[i].getTransactionSignature(); + signatures[i] = new DigitalSignatureBlob(pubKey, signDigest); + } + + return signatures; + } + public HashDigest consensusDecisions(PrivKey privKey) { // 获取其它参与方的账本生成结果; boolean allDecided = startRequestDecisions(privKey, prompter); @@ -248,13 +241,13 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI prompter.error( "Rollback ledger initialization because of not all nodes make same decision! --[Current Participant=%s]", currentId); - ledgerEditor.cancel(); + initializer.cancel(); return null; } // 执行提交提交; - ledgerEditor.commit(); - return genesisBlock.getHash(); + initializer.commit(); + return initializer.getLedgerHash(); } public void closeDb() { @@ -334,7 +327,6 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI List partiList = ledgerProps.getConsensusParticipants(); ConsensusParticipantConfig[] parties = new ConsensusParticipantConfig[partiList.size()]; parties = partiList.toArray(parties); -// ConsensusParticipantConfig[] parties = partiList.toArray(new ConsensusParticipantConfig[partiList.size()]); ConsensusParticipantConfig[] orderedParties = sortAndVerify(parties); initSetting.setConsensusParticipants(orderedParties); initSetting.setCreatedTime(ledgerProps.getCreatedTime()); @@ -369,19 +361,11 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI initializerAddresses[i] = orderedParties[i].getInitializerAddress(); } - // 生成初始化交易,并签署许可; - TransactionBuilder initTxBuilder = new TxBuilder(null);// 账本初始化交易的账本 hash 为 null; - initTxBuilder.ledgers().create(initSetting); - for (ParticipantNode p : initSetting.getConsensusParticipants()) { - // TODO:暂时只支持注册用户的初始化操作; - BlockchainIdentity superUserId = new BlockchainIdentityData(p.getPubKey()); - initTxBuilder.users().register(superUserId); - } - // 账本初始化配置声明的创建时间来初始化交易时间戳;注:不能用本地时间,因为共识节点之间的本地时间系统不一致; - this.initTxContent = initTxBuilder.prepareContent(initSetting.getCreatedTime()); + // 初始化账本; + this.initializer = LedgerInitializer.create(ledgerInitSetting); // 对初始交易签名,生成当前参与者的账本初始化许可; - SignatureDigest permissionSign = SignatureUtils.sign(initTxContent, privKey); + SignatureDigest permissionSign = initializer.signTransaction(privKey); LedgerInitProposalData permission = new LedgerInitProposalData(currentId, permissionSign); this.currentId = currentId; @@ -404,39 +388,6 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI return decision; } - /** - * 初始化账本数据,返回创始区块; - * - * @param ledgerEditor - * @return - */ - private LedgerBlock initLedgerDataset(LedgerEditor ledgerEditor) { - // 初始化时,自动将参与方注册为账本的用户; - TxRequestBuilder txReqBuilder = new TxRequestBuilder(this.initTxContent); - ParticipantNode[] parties = this.ledgerInitSetting.getConsensusParticipants(); - for (int i = 0; i < parties.length; i++) { - PubKey pubKey = parties[i].getPubKey(); - SignatureDigest signDigest = this.permissions[i].getTransactionSignature(); - DigitalSignatureBlob digitalSignature = new DigitalSignatureBlob(pubKey, signDigest); - txReqBuilder.addNodeSignature(digitalSignature); - } - TransactionRequest txRequest = txReqBuilder.buildRequest(); - - LedgerTransactionContext txCtx = ledgerEditor.newTransaction(txRequest); - Operation[] ops = txRequest.getTransactionContent().getOperations(); - // 注册用户; 注:第一个操作是 LedgerInitOperation; - // TODO:暂时只支持注册用户的初始化操作; - for (int i = 1; i < ops.length; i++) { - UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; - txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), - userRegOP.getUserID().getPubKey()); - } - - txCtx.commit(TransactionState.SUCCESS, null); - - return ledgerEditor.prepare(); - } - /** * 请求所有其它参与方的账本创建许可; * @@ -506,7 +457,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI continue; } - if (!SignatureUtils.verifySignature(this.initTxContent, permission.getTransactionSignature(), pubKey)) { + if (!SignatureUtils.verifySignature(initializer.getTransactionContent(), permission.getTransactionSignature(), pubKey)) { prompter.error("Invalid permission from participant! --[Id=%s][name=%s]", participants[i].getAddress(), participants[i].getName()); allPermitted = false; @@ -753,7 +704,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI String.format("Reject decision because of self-synchronization! --[Id=%s]", remoteId)); } - if (this.genesisBlock == null) { + if (this.initializer == null) { // 当前参与者尚未准备就绪,返回 null; prompter.info("Not ready for genesis block! --[RemoteId=%s][CurrentId=%s]", remoteId, currentId); return null; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java index e07fa4d7..f291d6c9 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/PermissionResponseConverter.java @@ -3,8 +3,8 @@ package com.jd.blockchain.tools.initializer.web; import java.io.InputStream; import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.core.LedgerInitProposalData; -import com.jd.blockchain.tools.initializer.LedgerInitException; import com.jd.blockchain.utils.http.HttpServiceContext; import com.jd.blockchain.utils.http.ResponseConverter; import com.jd.blockchain.utils.http.agent.ServiceRequest; diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java index 8b34623c..d0bee332 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java @@ -19,14 +19,14 @@ import org.junit.Test; import org.springframework.core.io.ClassPathResource; import com.jd.blockchain.crypto.AddressEncoding; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.RoleInitData; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; -import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; -import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.utils.codec.HexUtils; public class LedgerInitPropertiesTest { @@ -133,7 +133,7 @@ public class LedgerInitPropertiesTest { ConsensusParticipantConfig part0 = initProps.getConsensusParticipant(0); assertEquals("jd.com", part0.getName()); - PubKey pubKey0 = KeyGenCommand.decodePubKey("3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9"); + PubKey pubKey0 = KeyGenUtils.decodePubKey("3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9"); assertEquals(pubKey0, part0.getPubKey()); assertEquals("127.0.0.1", part0.getInitializerAddress().getHost()); assertEquals(8800, part0.getInitializerAddress().getPort()); @@ -143,7 +143,7 @@ public class LedgerInitPropertiesTest { ConsensusParticipantConfig part1 = initProps.getConsensusParticipant(1); assertEquals(false, part1.getInitializerAddress().isSecure()); - PubKey pubKey1 = KeyGenCommand.decodePubKey("3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX"); + PubKey pubKey1 = KeyGenUtils.decodePubKey("3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX"); assertEquals(pubKey1, part1.getPubKey()); assertArrayEquals(new String[] { "MANAGER" }, part1.getRoles()); assertEquals(RolesPolicy.UNION, part1.getRolesPolicy()); @@ -154,7 +154,7 @@ public class LedgerInitPropertiesTest { assertEquals(RolesPolicy.UNION, part2.getRolesPolicy()); ConsensusParticipantConfig part3 = initProps.getConsensusParticipant(3); - PubKey pubKey3 = KeyGenCommand.decodePubKey("3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"); + PubKey pubKey3 = KeyGenUtils.decodePubKey("3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"); assertEquals(pubKey3, part3.getPubKey()); assertArrayEquals(new String[] { "GUEST" }, part3.getRoles()); assertEquals(RolesPolicy.INTERSECT, part3.getRolesPolicy()); @@ -170,7 +170,7 @@ public class LedgerInitPropertiesTest { int index = 0; for (String pubKeyStr : pubKeys) { System.out.println("[" + index + "][配置] = " + pubKeyStr); - PubKey pubKey = KeyGenCommand.decodePubKey(pubKeyStr); + PubKey pubKey = KeyGenUtils.decodePubKey(pubKeyStr); System.out.println("[" + index + "][公钥Base58] = " + pubKey.toBase58()); System.out.println("[" + index + "][地址] = " + AddressEncoding.generateAddress(pubKey).toBase58()); System.out.println("--------------------------------------------------------------------"); diff --git a/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java b/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java index e0c63a0b..cd2df93c 100644 --- a/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java +++ b/source/tools/tools-keygen/src/main/java/com/jd/blockchain/tools/keygen/KeyGenCommand.java @@ -1,15 +1,19 @@ package com.jd.blockchain.tools.keygen; +import static com.jd.blockchain.crypto.KeyGenUtils.decodePubKey; +import static com.jd.blockchain.crypto.KeyGenUtils.decryptedPrivKeyBytes; +import static com.jd.blockchain.crypto.KeyGenUtils.encodePrivKey; +import static com.jd.blockchain.crypto.KeyGenUtils.encodePubKey; +import static com.jd.blockchain.crypto.KeyGenUtils.readPassword; + import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import javax.crypto.SecretKey; - import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.ArgumentSet; @@ -17,18 +21,11 @@ import com.jd.blockchain.utils.ArgumentSet.ArgEntry; import com.jd.blockchain.utils.ArgumentSet.Setting; import com.jd.blockchain.utils.ConsoleUtils; import com.jd.blockchain.utils.codec.Base58Utils; -import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.io.FileUtils; -import com.jd.blockchain.utils.security.AESUtils; import com.jd.blockchain.utils.security.DecryptionException; -import com.jd.blockchain.utils.security.ShaUtils; public class KeyGenCommand { - public static final byte[] PUB_KEY_FILE_MAGICNUM = { (byte) 0xFF, 112, 117, 98 }; - - public static final byte[] PRIV_KEY_FILE_MAGICNUM = { (byte) 0x00, 112, 114, 118 }; - // 指定 -r 参数时为“读取模式”,显示密钥文件; -r 参数之后紧跟着指定要读取的公钥或者私钥文件的路径; private static final String READ_ARG = "-r"; @@ -172,34 +169,6 @@ public class KeyGenCommand { } } - public static String encodePubKey(PubKey pubKey) { - byte[] pubKeyBytes = BytesUtils.concat(PUB_KEY_FILE_MAGICNUM, pubKey.toBytes()); - String base58PubKey = Base58Utils.encode(pubKeyBytes); - return base58PubKey; - } - - public static PubKey decodePubKey(String base58PubKey) { - byte[] keyBytes = Base58Utils.decode(base58PubKey); - return decodePubKey(keyBytes); - } - - public static String encodePrivKey(PrivKey privKey, String base58Pwd) { - byte[] pwdBytes = Base58Utils.decode(base58Pwd); - return encodePrivKey(privKey, pwdBytes); - } - - public static String encodePrivKey(PrivKey privKey, byte[] pwdBytes) { - byte[] encodedPrivKeyBytes = encryptPrivKey(privKey, pwdBytes); - String base58PrivKey = Base58Utils.encode(encodedPrivKeyBytes); - return base58PrivKey; - } - - public static byte[] encryptPrivKey(PrivKey privKey, byte[] pwdBytes) { - SecretKey userKey = AESUtils.generateKey128(pwdBytes); - byte[] encryptedPrivKeyBytes = AESUtils.encrypt(privKey.toBytes(), userKey); - return BytesUtils.concat(PRIV_KEY_FILE_MAGICNUM, encryptedPrivKeyBytes); - } - /** * 读取密钥;
        * 如果是私钥,则需要输入密码; @@ -209,10 +178,10 @@ public class KeyGenCommand { public static void readKey(String keyFile, boolean decrypting) { String base58KeyString = FileUtils.readText(keyFile); byte[] keyBytes = Base58Utils.decode(base58KeyString); - if (BytesUtils.startsWith(keyBytes, PUB_KEY_FILE_MAGICNUM)) { + if (KeyGenUtils.isPubKeyBytes(keyBytes)) { if (decrypting) { // Try reading pubKey; - PubKey pubKey = doDecodePubKeyBytes(keyBytes); + PubKey pubKey = decodePubKey(keyBytes); ConsoleUtils.info( "======================== pub key ========================\r\n" + "[%s]\r\n" + "Raw:[%s][%s]\r\n", @@ -222,7 +191,7 @@ public class KeyGenCommand { base58KeyString); } return; - } else if (BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { + } else if (KeyGenUtils.isPrivKeyBytes(keyBytes)) { // Try reading privKye; try { if (decrypting) { @@ -246,119 +215,4 @@ public class KeyGenCommand { } } - private static PubKey doDecodePubKeyBytes(byte[] encodedPubKeyBytes) { - byte[] pubKeyBytes = Arrays.copyOfRange(encodedPubKeyBytes, PUB_KEY_FILE_MAGICNUM.length, - encodedPubKeyBytes.length); - return new PubKey(pubKeyBytes); - } - - public static PrivKey decryptedPrivKeyBytes(byte[] encodedPrivKeyBytes, byte[] pwdBytes) { - // Read privKye; - SecretKey userKey = AESUtils.generateKey128(pwdBytes); - byte[] encryptedKeyBytes = Arrays.copyOfRange(encodedPrivKeyBytes, PRIV_KEY_FILE_MAGICNUM.length, - encodedPrivKeyBytes.length); - try { - byte[] plainKeyBytes = AESUtils.decrypt(encryptedKeyBytes, userKey); - return new PrivKey(plainKeyBytes); - } catch (DecryptionException e) { - throw new DecryptionException("Invalid password!", e); - } - } - - public static PubKey readPubKey(String keyFile) { - String base58KeyString = FileUtils.readText(keyFile); - return decodePubKey(base58KeyString); - } - - public static PubKey decodePubKey(byte[] encodedPubKeyBytes) { - if (BytesUtils.startsWith(encodedPubKeyBytes, PUB_KEY_FILE_MAGICNUM)) { - // Read pubKey; - return doDecodePubKeyBytes(encodedPubKeyBytes); - } - - throw new IllegalArgumentException("The specified bytes is not valid PubKey generated by the KeyGen tool!"); - } - - /** - * 从控制台读取加密口令,以二进制数组形式返回原始口令的一次SHA256的结果; - * - * @return - */ - public static byte[] readPassword() { - byte[] pwdBytes = ConsoleUtils.readPassword(); - return ShaUtils.hash_256(pwdBytes); - } - - /** - * 对指定的原始密码进行编码生成用于加解密的密码; - * - * @param rawPassword - * @return - */ - public static byte[] encodePassword(String rawPassword) { - byte[] pwdBytes = BytesUtils.toBytes(rawPassword, "UTF-8"); - return ShaUtils.hash_256(pwdBytes); - } - - /** - * 对指定的原始密码进行编码生成用于加解密的密码; - * - * @param rawPassword - * @return - */ - public static String encodePasswordAsBase58(String rawPassword) { - return Base58Utils.encode(encodePassword(rawPassword)); - } - - /** - * 从控制台读取加密口令,以Base58字符串形式返回口令的一次SHA256的结果; - * - * @return - */ - public static String readPasswordString() { - return Base58Utils.encode(readPassword()); - } - - public static PrivKey readPrivKey(String keyFile, String base58Pwd) { - return readPrivKey(keyFile, Base58Utils.decode(base58Pwd)); - } - - /** - * 从文件读取私钥; - * - * @param keyFile - * @param pwdBytes - * @return - */ - public static PrivKey readPrivKey(String keyFile, byte[] pwdBytes) { - String base58KeyString = FileUtils.readText(keyFile); - byte[] keyBytes = Base58Utils.decode(base58KeyString); - if (!BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { - throw new IllegalArgumentException("The specified file is not a private key file!"); - } - return decryptedPrivKeyBytes(keyBytes, pwdBytes); - } - - public static PrivKey decodePrivKey(String base58Key, String base58Pwd) { - byte[] decryptedKey = Base58Utils.decode(base58Pwd); - return decodePrivKey(base58Key, decryptedKey); - } - - public static PrivKey decodePrivKey(String base58Key, byte[] pwdBytes) { - byte[] keyBytes = Base58Utils.decode(base58Key); - if (!BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { - throw new IllegalArgumentException("The specified file is not a private key file!"); - } - return decryptedPrivKeyBytes(keyBytes, pwdBytes); - } - - public static PrivKey decodePrivKeyWithRawPassword(String base58Key, String rawPassword) { - byte[] pwdBytes = encodePassword(rawPassword); - byte[] keyBytes = Base58Utils.decode(base58Key); - if (!BytesUtils.startsWith(keyBytes, PRIV_KEY_FILE_MAGICNUM)) { - throw new IllegalArgumentException("The specified file is not a private key file!"); - } - return decryptedPrivKeyBytes(keyBytes, pwdBytes); - } - } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java index fe9f332b..2a922b4a 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java @@ -1,28 +1,53 @@ package com.jd.blockchain.mocker; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import org.springframework.web.bind.annotation.RequestBody; + import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.crypto.*; +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoProvider; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.crypto.SignatureDigest; +import com.jd.blockchain.crypto.SignatureFunction; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.LedgerInitException; +import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; +import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.TransactionContent; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.LedgerInitDecision; +import com.jd.blockchain.ledger.core.LedgerInitProposal; +import com.jd.blockchain.ledger.core.LedgerInitProposalData; +import com.jd.blockchain.ledger.core.LedgerInitializer; +import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.DbConnectionFactory; -import com.jd.blockchain.tools.initializer.*; -import com.jd.blockchain.tools.initializer.LedgerInitProperties.ConsensusParticipantConfig; +import com.jd.blockchain.tools.initializer.DBConnectionConfig; +import com.jd.blockchain.tools.initializer.LedgerInitProcess; +import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitDecisionData; -import com.jd.blockchain.transaction.*; +import com.jd.blockchain.transaction.DigitalSignatureBlob; +import com.jd.blockchain.transaction.LedgerInitData; +import com.jd.blockchain.transaction.SignatureUtils; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.InvocationResult; import com.jd.blockchain.utils.io.BytesUtils; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.util.*; /** * 账本初始化控制器; @@ -36,8 +61,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon DataContractRegistry.register(TransactionRequest.class); } - private static final String[] SUPPORTED_PROVIDERS = { - ClassicCryptoService.class.getName(), + private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), SMCryptoService.class.getName() }; private static final String DEFAULT_SIGN_ALGORITHM = "ED25519"; @@ -46,7 +70,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon private volatile LedgerInitProposal localPermission; - private TransactionContent initTxContent; + private volatile LedgerInitializer initializer; private volatile int currentId = -1; @@ -59,16 +83,12 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon private volatile ConsensusProvider consensusProvider; - private volatile LedgerBlock genesisBlock; - private volatile LedgerInitDecision localDecision; private volatile DecisionResultHandle[] decisions; private volatile DbConnection dbConn; - private volatile LedgerEditor ledgerEditor; - private LedgerManager ledgerManager; private DbConnectionFactory dbConnFactory; @@ -88,7 +108,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon } public TransactionContent getInitTxContent() { - return initTxContent; + return initializer.getTransactionContent(); } public LedgerInitProposal getLocalPermission() { @@ -154,11 +174,12 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon public LedgerInitDecision makeLocalDecision(PrivKey privKey) { // 生成账本; - this.ledgerEditor = ledgerManager.newLedger(this.ledgerInitSetting, dbConn.getStorageService()); - this.genesisBlock = initLedgerDataset(ledgerEditor); +// this.ledgerEditor = ledgerManager.newLedger(this.ledgerInitSetting, dbConn.getStorageService()); +// this.genesisBlock = initLedgerDataset(ledgerEditor); + initializer.prepareLedger(dbConn.getStorageService(), getNodeSignatures()); // 生成签名决定; - this.localDecision = makeDecision(currentId, genesisBlock.getHash(), privKey); + this.localDecision = makeDecision(currentId, initializer.getLedgerHash(), privKey); this.decisions = new DecisionResultHandle[this.ledgerInitSetting.getConsensusParticipants().length]; for (int i = 0; i < decisions.length; i++) { // 参与者的 id 是依次递增的; @@ -169,10 +190,19 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon return localDecision; } + private DigitalSignature getNodeSignatures() { + ParticipantNode parti = this.ledgerInitSetting.getConsensusParticipants()[currentId]; + PubKey pubKey = parti.getPubKey(); + SignatureDigest signDigest = this.localPermission.getTransactionSignature(); + DigitalSignatureBlob digitalSignature = new DigitalSignatureBlob(pubKey, signDigest); + + return digitalSignature; + } + public HashDigest consensusDecisions() { // 执行提交提交; - ledgerEditor.commit(); - return genesisBlock.getHash(); + initializer.commit(); + return initializer.getLedgerHash(); } public void closeDb() { @@ -236,18 +266,11 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon if (!SIGN_FUNC.verify(testSign, myPubKey, testBytes)) { throw new LedgerInitException("Your pub-key specified in the init-settings isn't match your priv-key!"); } - // 生成初始化交易,并签署许可; - TransactionBuilder initTxBuilder = new TxBuilder(null);// 账本初始化交易的账本 hash 为 null; - initTxBuilder.ledgers().create(initSetting); - for (ParticipantNode p : initSetting.getConsensusParticipants()) { - // TODO:暂时只支持注册用户的初始化操作; - BlockchainIdentity superUserId = new BlockchainIdentityData(p.getPubKey()); - initTxBuilder.users().register(superUserId); - } - this.initTxContent = initTxBuilder.prepareContent(); + // 初始化; + this.initializer = LedgerInitializer.create(ledgerInitSetting); // 对初始交易签名,生成当前参与者的账本初始化许可; - SignatureDigest permissionSign = SignatureUtils.sign(initTxContent, privKey); + SignatureDigest permissionSign = SignatureUtils.sign(initializer.getTransactionContent(), privKey); localPermission = new LedgerInitProposalData(currentId, permissionSign); this.currentId = currentId; @@ -266,33 +289,33 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon return decision; } - private LedgerBlock initLedgerDataset(LedgerEditor ledgerEditor) { - // 初始化时,自动将参与方注册为账本的用户; - TxRequestBuilder txReqBuilder = new TxRequestBuilder(this.initTxContent); -// ParticipantNode[] parties = this.ledgerInitSetting.getConsensusParticipants(); - ParticipantNode parti = this.ledgerInitSetting.getConsensusParticipants()[currentId]; - - PubKey pubKey = parti.getPubKey(); - SignatureDigest signDigest = this.localPermission.getTransactionSignature(); - DigitalSignatureBlob digitalSignature = new DigitalSignatureBlob(pubKey, signDigest); - txReqBuilder.addNodeSignature(digitalSignature); - - TransactionRequest txRequest = txReqBuilder.buildRequest(); - - LedgerTransactionContext txCtx = ledgerEditor.newTransaction(txRequest); - Operation[] ops = txRequest.getTransactionContent().getOperations(); - // 注册用户; 注:第一个操作是 LedgerInitOperation; - // TODO:暂时只支持注册用户的初始化操作; - for (int i = 1; i < ops.length; i++) { - UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; - txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), - userRegOP.getUserID().getPubKey()); - } - - txCtx.commit(TransactionState.SUCCESS, null); - - return ledgerEditor.prepare(); - } +// private LedgerBlock initLedgerDataset(LedgerEditor ledgerEditor) { +// // 初始化时,自动将参与方注册为账本的用户; +// TxRequestBuilder txReqBuilder = new TxRequestBuilder(this.initTxContent); +//// ParticipantNode[] parties = this.ledgerInitSetting.getConsensusParticipants(); +// ParticipantNode parti = this.ledgerInitSetting.getConsensusParticipants()[currentId]; +// +// PubKey pubKey = parti.getPubKey(); +// SignatureDigest signDigest = this.localPermission.getTransactionSignature(); +// DigitalSignatureBlob digitalSignature = new DigitalSignatureBlob(pubKey, signDigest); +// txReqBuilder.addNodeSignature(digitalSignature); +// +// TransactionRequest txRequest = txReqBuilder.buildRequest(); +// +// LedgerTransactionContext txCtx = ledgerEditor.newTransaction(txRequest); +// Operation[] ops = txRequest.getTransactionContent().getOperations(); +// // 注册用户; 注:第一个操作是 LedgerInitOperation; +// // TODO:暂时只支持注册用户的初始化操作; +// for (int i = 1; i < ops.length; i++) { +// UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; +// txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), +// userRegOP.getUserID().getPubKey()); +// } +// +// txCtx.commit(TransactionState.SUCCESS, null); +// +// return ledgerEditor.prepare(); +// } private byte[] getDecisionBytes(int participantId, HashDigest ledgerHash) { return BytesUtils.concat(BytesUtils.toBytes(participantId), ledgerHash.toBytes()); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 189b7f63..ab2167a2 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -20,6 +20,7 @@ import com.jd.blockchain.consensus.action.ActionResponse; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; @@ -40,6 +41,7 @@ import com.jd.blockchain.ledger.KVInfoVO; import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInfo; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.LedgerMetadata; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerTransaction; @@ -75,8 +77,6 @@ import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; @@ -164,16 +164,16 @@ public class MockerNodeContext implements BlockchainQueryService { boolean isExist = false; // 通过公钥进行判断 for (Map.Entry entry : participants.entrySet()) { - String existPubKey = KeyGenCommand.encodePubKey(entry.getValue().getPubKey()); + String existPubKey = KeyGenUtils.encodePubKey(entry.getValue().getPubKey()); if (pubKeyString.equals(existPubKey)) { isExist = true; } } if (!isExist) { // 加入系统中 - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(MockerConstant.PRIVATE_KEYS[i], + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(MockerConstant.PRIVATE_KEYS[i], MockerConstant.PASSWORD); - PubKey pubKey = KeyGenCommand.decodePubKey(MockerConstant.PUBLIC_KEYS[i]); + PubKey pubKey = KeyGenUtils.decodePubKey(MockerConstant.PUBLIC_KEYS[i]); participants(new BlockchainKeypair(pubKey, privKey)); } if (participants.size() >= 4) { @@ -524,7 +524,7 @@ public class MockerNodeContext implements BlockchainQueryService { ledgerProp.put(partiPrefix + LedgerInitProperties.PART_NAME, name); ledgerProp.put(partiPrefix + LedgerInitProperties.PART_PUBKEY_PATH, ""); ledgerProp.put(partiPrefix + LedgerInitProperties.PART_PUBKEY, - KeyGenCommand.encodePubKey(keypair.getPubKey())); + KeyGenUtils.encodePubKey(keypair.getPubKey())); ledgerProp.put(partiPrefix + LedgerInitProperties.PART_INITIALIZER_HOST, MockerConstant.LOCAL_ADDRESS); ledgerProp.put(partiPrefix + LedgerInitProperties.PART_INITIALIZER_PORT, String.valueOf(MockerConstant.LEDGER_INIT_PORT_START + partiIndex * 10)); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerNodeHandler.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerNodeHandler.java index bd1bea24..b4d355fc 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerNodeHandler.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerNodeHandler.java @@ -1,8 +1,10 @@ package com.jd.blockchain.mocker.handler; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.gateway.GatewayConfigProperties; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.mocker.config.MockerConstant; import com.jd.blockchain.mocker.config.PresetAnswerPrompter; import com.jd.blockchain.mocker.node.GatewayNodeRunner; @@ -10,9 +12,7 @@ import com.jd.blockchain.mocker.node.NodeWebContext; import com.jd.blockchain.mocker.node.PeerNodeRunner; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; -import com.jd.blockchain.tools.keygen.KeyGenCommand; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.net.NetworkAddress; import org.springframework.util.ResourceUtils; @@ -93,7 +93,7 @@ public class MockerNodeHandler { // 启动服务器; NetworkAddress initAddr = initSetting.getConsensusParticipant(nodeIndex).getInitializerAddress(); NodeWebContext node = new NodeWebContext(nodeIndex, initAddr); - PrivKey privkey = KeyGenCommand.decodePrivKeyWithRawPassword(PRIVATE_KEYS[nodeIndex], PASSWORD); + PrivKey privkey = KeyGenUtils.decodePrivKeyWithRawPassword(PRIVATE_KEYS[nodeIndex], PASSWORD); DBConnectionConfig dbConn = new DBConnectionConfig(); dbConn.setConnectionUri(MockerConstant.DB_MEMS[nodeIndex]); ThreadInvoker.AsyncCallback nodeCallback = node.startInit(privkey, initSetting, dbConn, consolePrompter, diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerServiceHandler.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerServiceHandler.java index 36308c0c..d7f1f43b 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerServiceHandler.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerServiceHandler.java @@ -1,6 +1,7 @@ package com.jd.blockchain.mocker.handler; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; @@ -9,7 +10,6 @@ import com.jd.blockchain.mocker.data.KvData; import com.jd.blockchain.mocker.data.ResponseData; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.tools.keygen.KeyGenCommand; public class MockerServiceHandler { @@ -115,8 +115,8 @@ public class MockerServiceHandler { } private BlockchainKeypair defaultParticipant() { - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(MockerConstant.PRIVATE_KEYS[0], MockerConstant.PASSWORD); - PubKey pubKey = KeyGenCommand.decodePubKey(MockerConstant.PUBLIC_KEYS[0]); + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(MockerConstant.PRIVATE_KEYS[0], MockerConstant.PASSWORD); + PubKey pubKey = KeyGenUtils.decodePubKey(MockerConstant.PUBLIC_KEYS[0]); return new BlockchainKeypair(pubKey, privKey); } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java index 5735bf36..30cb4866 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java @@ -2,6 +2,7 @@ package com.jd.blockchain.mocker.node; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; @@ -12,7 +13,6 @@ import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; -import com.jd.blockchain.tools.initializer.LedgerInitProperties; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; import com.jd.blockchain.utils.concurrent.ThreadInvoker; From f713fbb397fd69a81e6e15067935aa513849375e Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 6 Sep 2019 10:57:44 +0800 Subject: [PATCH 081/124] Refactored; --- .../com/jd/blockchain/consts/DataCodes.java | 10 +- .../ledger/core/LedgerInitializer.java | 12 +- .../blockchain/ledger/RoleInitSettings.java | 2 +- .../blockchain/ledger/SecurityInitData.java | 6 + .../ledger/SecurityInitSettings.java | 5 +- .../ledger/UserAuthInitSettings.java | 21 +++ .../ledger}/LedgerInitPropertiesTest.java | 30 +++- .../src/test/resources/bftsmart.config | 144 ++++++++++++++++ .../src/test/resources/keys/parti2.pub | 1 + .../src/test/resources/ledger.init | 158 ++++++++++++++++++ 10 files changed, 378 insertions(+), 11 deletions(-) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitSettings.java rename source/{tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer => ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger}/LedgerInitPropertiesTest.java (88%) create mode 100644 source/ledger/ledger-model/src/test/resources/bftsmart.config create mode 100644 source/ledger/ledger-model/src/test/resources/keys/parti2.pub create mode 100644 source/ledger/ledger-model/src/test/resources/ledger.init diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 3a2055f8..20f0680f 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -71,6 +71,12 @@ public interface DataCodes { public static final int PRIVILEGE_SET = 0x410; public static final int ROLE_SET = 0x411; + + public static final int SECURITY_INIT_SETTING = 0x420; + + public static final int SECURITY_ROLE_INIT_SETTING = 0x421; + + public static final int SECURITY_USER_AUTH_INIT_SETTING = 0x422; // contract types of metadata; public static final int METADATA = 0x600; @@ -86,10 +92,6 @@ public interface DataCodes { public static final int METADATA_CONSENSUS_PARTICIPANT = 0x621; - public static final int METADATA_SECURITY_INIT_SETTING = 0x622; - - public static final int METADATA_ROLE_INIT_SETTING = 0x623; - // public static final int METADATA_CONSENSUS_NODE = 0x630; // // public static final int METADATA_CONSENSUS_SETTING = 0x631; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index dc612dfa..5c7116e2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -11,6 +11,7 @@ import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.SecurityInitSettings; import com.jd.blockchain.ledger.TransactionBuilder; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; @@ -60,8 +61,16 @@ public class LedgerInitializer { return initTxContent; } + private static SecurityInitSettings createDefaultSecurityInitSettings() { + throw new IllegalStateException("Not implemented!"); + } + public static LedgerInitializer create(LedgerInitSetting initSetting) { - // 生成初始化交易,并签署许可; + return create(initSetting, createDefaultSecurityInitSettings()); + } + + public static LedgerInitializer create(LedgerInitSetting initSetting, SecurityInitSettings securityInitSettings) { + // 生成初始化交易; TransactionBuilder initTxBuilder = new TxBuilder(null);// 账本初始化交易的账本 hash 为 null; initTxBuilder.ledgers().create(initSetting); for (ParticipantNode p : initSetting.getConsensusParticipants()) { @@ -71,6 +80,7 @@ public class LedgerInitializer { } // 账本初始化配置声明的创建时间来初始化交易时间戳;注:不能用本地时间,因为共识节点之间的本地时间系统不一致; TransactionContent initTxContent = initTxBuilder.prepareContent(initSetting.getCreatedTime()); + return new LedgerInitializer(initSetting, initTxContent); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java index bdfddb52..71c6965b 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitSettings.java @@ -11,7 +11,7 @@ import com.jd.blockchain.consts.DataCodes; * @author huanghaiquan * */ -@DataContract(code = DataCodes.METADATA_ROLE_INIT_SETTING) +@DataContract(code = DataCodes.SECURITY_ROLE_INIT_SETTING) public interface RoleInitSettings { /** diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java index 9ba2e250..a3d2e62a 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java @@ -25,4 +25,10 @@ public class SecurityInitData implements SecurityInitSettings { RoleInitData roleInitData = new RoleInitData(roleName, ledgerPermissions, transactionPermissions); roles.add(roleInitData); } + + @Override + public UserAuthInitSettings[] getUserAuthorizations() { + // TODO Auto-generated method stub + return null; + } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java index 32e5838e..8ca64d79 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitSettings.java @@ -10,7 +10,7 @@ import com.jd.blockchain.consts.DataCodes; * @author huanghaiquan * */ -@DataContract(code = DataCodes.METADATA_SECURITY_INIT_SETTING) +@DataContract(code = DataCodes.SECURITY_INIT_SETTING) public interface SecurityInitSettings { /** @@ -20,5 +20,8 @@ public interface SecurityInitSettings { */ @DataField(order = 0, refContract = true, list = true) RoleInitSettings[] getRoles(); + + @DataField(order = 1, refContract = true, list = true) + UserAuthInitSettings[] getUserAuthorizations(); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitSettings.java new file mode 100644 index 00000000..31a6033a --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitSettings.java @@ -0,0 +1,21 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.utils.Bytes; + +@DataContract(code = DataCodes.SECURITY_USER_AUTH_INIT_SETTING) +public interface UserAuthInitSettings { + + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + Bytes getUserAddress(); + + @DataField(order = 2, primitiveType = PrimitiveType.TEXT, list = true) + String[] getRoles(); + + @DataField(order = 3, refEnum = true) + RolesPolicy getPolicy(); + +} diff --git a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java similarity index 88% rename from source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java rename to source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java index d0bee332..c3f320b8 100644 --- a/source/tools/tools-initializer/src/test/java/test/com/jd/blockchain/tools/initializer/LedgerInitPropertiesTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java @@ -1,8 +1,7 @@ -package test.com.jd.blockchain.tools.initializer; +package test.com.jd.blockchain.ledger; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -18,18 +17,41 @@ import java.util.TimeZone; import org.junit.Test; import org.springframework.core.io.ClassPathResource; +import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.RoleInitData; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; +import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.utils.codec.HexUtils; public class LedgerInitPropertiesTest { + + + static { + DataContractRegistry.register(LedgerInitOperation.class); + DataContractRegistry.register(UserRegisterOperation.class); + } + + public static final String PASSWORD = "abc"; + + public static final String[] PUB_KEYS = { "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", + "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", + "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", + "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk" }; + + public static final String[] PRIV_KEYS = { + "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", + "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", + "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", + "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns" }; + private static String expectedCreatedTimeStr = "2019-08-01 14:26:58.069+0800"; @@ -166,7 +188,7 @@ public class LedgerInitPropertiesTest { @Test public void testPubKeyAddress() { - String[] pubKeys = TestConsts.PUB_KEYS; + String[] pubKeys = PUB_KEYS; int index = 0; for (String pubKeyStr : pubKeys) { System.out.println("[" + index + "][配置] = " + pubKeyStr); diff --git a/source/ledger/ledger-model/src/test/resources/bftsmart.config b/source/ledger/ledger-model/src/test/resources/bftsmart.config new file mode 100644 index 00000000..df69caf5 --- /dev/null +++ b/source/ledger/ledger-model/src/test/resources/bftsmart.config @@ -0,0 +1,144 @@ +# Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags +# +# Licensed 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. + +############################################ +####### Communication Configurations ####### +############################################ + +#HMAC algorithm used to authenticate messages between processes (HmacMD5 is the default value) +#This parameter is not currently being used being used +#system.authentication.hmacAlgorithm = HmacSHA1 + +#Specify if the communication system should use a thread to send data (true or false) +system.communication.useSenderThread = true + +#Force all processes to use the same public/private keys pair and secret key. This is useful when deploying experiments +#and benchmarks, but must not be used in production systems. +system.communication.defaultkeys = true + +############################################ +### Replication Algorithm Configurations ### +############################################ + +#Timeout to asking for a client request +system.totalordermulticast.timeout = 2000 + + +#Maximum batch size (in number of messages) +system.totalordermulticast.maxbatchsize = 400 + +#Number of nonces (for non-determinism actions) generated +system.totalordermulticast.nonces = 10 + +#if verification of leader-generated timestamps are increasing +#it can only be used on systems in which the network clocks +#are synchronized +system.totalordermulticast.verifyTimestamps = false + +#Quantity of messages that can be stored in the receive queue of the communication system +system.communication.inQueueSize = 500000 + +# Quantity of messages that can be stored in the send queue of each replica +system.communication.outQueueSize = 500000 + +#Set to 1 if SMaRt should use signatures, set to 0 if otherwise +system.communication.useSignatures = 0 + +#Set to 1 if SMaRt should use MAC's, set to 0 if otherwise +system.communication.useMACs = 1 + +#Set to 1 if SMaRt should use the standard output to display debug messages, set to 0 if otherwise +system.debug = 0 + +#Print information about the replica when it is shutdown +system.shutdownhook = true + +############################################ +###### State Transfer Configurations ####### +############################################ + +#Activate the state transfer protocol ('true' to activate, 'false' to de-activate) +system.totalordermulticast.state_transfer = true + +#Maximum ahead-of-time message not discarded +system.totalordermulticast.highMark = 10000 + +#Maximum ahead-of-time message not discarded when the replica is still on EID 0 (after which the state transfer is triggered) +system.totalordermulticast.revival_highMark = 10 + +#Number of ahead-of-time messages necessary to trigger the state transfer after a request timeout occurs +system.totalordermulticast.timeout_highMark = 200 + +############################################ +###### Log and Checkpoint Configurations ### +############################################ + +system.totalordermulticast.log = true +system.totalordermulticast.log_parallel = false +system.totalordermulticast.log_to_disk = false +system.totalordermulticast.sync_log = false + +#Period at which BFT-SMaRt requests the state to the application (for the state transfer state protocol) +system.totalordermulticast.checkpoint_period = 1000 +system.totalordermulticast.global_checkpoint_period = 120000 + +system.totalordermulticast.checkpoint_to_disk = false +system.totalordermulticast.sync_ckp = false + + +############################################ +###### Reconfiguration Configurations ###### +############################################ + +#The ID of the trust third party (TTP) +system.ttp.id = 7002 + +#This sets if the system will function in Byzantine or crash-only mode. Set to "true" to support Byzantine faults +system.bft = true + +#Custom View Storage; +#view.storage.handler=bftsmart.reconfiguration.views.DefaultViewStorage + + +#Number of servers in the group +system.servers.num = 4 + +#Maximum number of faulty replicas +system.servers.f = 1 + +#Replicas ID for the initial view, separated by a comma. +# The number of replicas in this parameter should be equal to that specified in 'system.servers.num' +system.initial.view = 0,1,2,3 + +#Configuration of all node servers; +#PubKey of node server with specified ID, with base58 encoding. +system.server.0.pubkey= +system.server.0.network.host=127.0.0.1 +system.server.0.network.port=8900 +system.server.0.network.secure=false + +system.server.1.pubkey= +system.server.1.network.host=127.0.0.1 +system.server.1.network.port=8910 +system.server.1.network.secure=false + +system.server.2.pubkey= +system.server.2.network.host=127.0.0.1 +system.server.2.network.port=8920 +system.server.2.network.secure=false + +system.server.3.pubkey= +system.server.3.network.host=127.0.0.1 +system.server.3.network.port=8920 +system.server.3.network.secure=false diff --git a/source/ledger/ledger-model/src/test/resources/keys/parti2.pub b/source/ledger/ledger-model/src/test/resources/keys/parti2.pub new file mode 100644 index 00000000..dde44b8e --- /dev/null +++ b/source/ledger/ledger-model/src/test/resources/keys/parti2.pub @@ -0,0 +1 @@ +3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x \ No newline at end of file diff --git a/source/ledger/ledger-model/src/test/resources/ledger.init b/source/ledger/ledger-model/src/test/resources/ledger.init new file mode 100644 index 00000000..ebbd8872 --- /dev/null +++ b/source/ledger/ledger-model/src/test/resources/ledger.init @@ -0,0 +1,158 @@ + +#账本的种子;一段16进制字符,最长可以包含64个字符;可以用字符“-”分隔,以便更容易读取; +ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe + +#账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; +ledger.name=test + +#声明账本的创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 +created-time=2019-08-01 14:26:58.069+0800 + + +#----------------------------------------------- +# 初始的角色名称列表;可选项; +# 角色名称不区分大小写,最长不超过20个字符;多个角色名称之间用半角的逗点“,”分隔; +# 系统会预置一个默认角色“DEFAULT”,所有未指定角色的用户都以赋予该角色的权限;若初始化时未配置默认角色的权限,则为默认角色分配所有权限; +# +# 注:如果声明了角色,但未声明角色对应的权限清单,这会忽略该角色的初始化; +# +security.roles=DEFAULT, ADMIN, MANAGER, GUEST + +# 赋予角色的账本权限清单;可选项; +# 可选的权限如下; +# AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, +# REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, +# SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +# APPROVE_TX, CONSENSUS_TX +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT + +# 赋予角色的交易权限清单;可选项; +# 可选的权限如下; +# DIRECT_OPERATION, CONTRACT_OPERATION +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 其它角色的配置示例; +# 系统管理员角色:只能操作全局性的参数配置和用户注册,只能执行直接操作指令; +security.role.ADMIN.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER +security.role.ADMIN.tx-privileges=DIRECT_OPERATION + +# 业务主管角色:只能够执行账本数据相关的操作,包括注册用户、注册数据账户、注册合约、升级合约、写入数据等;能够执行直接操作指令和调用合约; +security.role.MANAGER.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +security.role.MANAGER.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 访客角色:不具备任何的账本权限,只有数据读取的操作;也只能够通过调用合约来读取数据; +security.role.GUEST.ledger-privileges= +security.role.GUEST.tx-privileges=CONTRACT_OPERATION + + + +#----------------------------------------------- +#共识服务提供者;必须; +consensus.service-provider=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider + +#共识服务的参数配置;必须; +consensus.conf=classpath:bftsmart.config + +#密码服务提供者列表,以英文逗点“,”分隔;必须; +crypto.service-providers=com.jd.blockchain.crypto.service.classic.ClassicCryptoService, \ +com.jd.blockchain.crypto.service.sm.SMCryptoService + + +#参与方的个数,后续以 cons_parti.id 分别标识每一个参与方的配置; +cons_parti.count=4 + +#第0个参与方的名称; +cons_parti.0.name=jd.com +#第0个参与方的公钥文件路径; +cons_parti.0.pubkey-path=keys/jd-com.pub +#第0个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.0.pubkey=3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9 +#第0个参与方的角色清单;可选项; +cons_parti.0.roles=ADMIN, MANAGER +#第0个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.0.roles-policy=UNION +#第0个参与方的共识服务的主机地址; +cons_parti.0.consensus.host=127.0.0.1 +#第0个参与方的共识服务的端口; +cons_parti.0.consensus.port=8900 +#第0个参与方的共识服务是否开启安全连接; +cons_parti.0.consensus.secure=true +#第0个参与方的账本初始服务的主机; +cons_parti.0.initializer.host=127.0.0.1 +#第0个参与方的账本初始服务的端口; +cons_parti.0.initializer.port=8800 +#第0个参与方的账本初始服务是否开启安全连接; +cons_parti.0.initializer.secure=true + +#第1个参与方的名称; +cons_parti.1.name=at.com +#第1个参与方的公钥文件路径; +cons_parti.1.pubkey-path=keys/at-com.pub +#第1个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.1.pubkey=3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX +#第1个参与方的角色清单;可选项; +cons_parti.1.roles=MANAGER +#第1个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.1.roles-policy=UNION +#第1个参与方的共识服务的主机地址; +cons_parti.1.consensus.host=127.0.0.1 +#第1个参与方的共识服务的端口; +cons_parti.1.consensus.port=8910 +#第1个参与方的共识服务是否开启安全连接; +cons_parti.1.consensus.secure=false +#第1个参与方的账本初始服务的主机; +cons_parti.1.initializer.host=127.0.0.1 +#第1个参与方的账本初始服务的端口; +cons_parti.1.initializer.port=8810 +#第1个参与方的账本初始服务是否开启安全连接; +cons_parti.1.initializer.secure=false + +#第2个参与方的名称; +cons_parti.2.name=bt.com +#第2个参与方的公钥文件路径; +cons_parti.2.pubkey-path=classpath:keys/parti2.pub +#第2个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.2.pubkey= +#第2个参与方的角色清单;可选项; +cons_parti.2.roles=MANAGER +#第2个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.2.roles-policy=UNION +#第2个参与方的共识服务的主机地址; +cons_parti.2.consensus.host=127.0.0.1 +#第2个参与方的共识服务的端口; +cons_parti.2.consensus.port=8920 +#第2个参与方的共识服务是否开启安全连接; +cons_parti.2.consensus.secure=false +#第2个参与方的账本初始服务的主机; +cons_parti.2.initializer.host=127.0.0.1 +#第2个参与方的账本初始服务的端口; +cons_parti.2.initializer.port=8820 +#第2个参与方的账本初始服务是否开启安全连接; +cons_parti.2.initializer.secure=true + +#第3个参与方的名称; +cons_parti.3.name=xt.com +#第3个参与方的公钥文件路径; +cons_parti.3.pubkey-path=keys/xt-com.pub +#第3个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.3.pubkey=3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk +#第3个参与方的角色清单;可选项; +cons_parti.3.roles=GUEST +#第3个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.3.roles-policy=INTERSECT +#第3个参与方的共识服务的主机地址; +cons_parti.3.consensus.host=127.0.0.1 +#第3个参与方的共识服务的端口; +cons_parti.3.consensus.port=8930 +#第3个参与方的共识服务是否开启安全连接; +cons_parti.3.consensus.secure=false +#第3个参与方的账本初始服务的主机; +cons_parti.3.initializer.host=127.0.0.1 +#第3个参与方的账本初始服务的端口; +cons_parti.3.initializer.port=8830 +#第3个参与方的账本初始服务是否开启安全连接; +cons_parti.3.initializer.secure=false From ae3af3ea725248c0b53a226e0fd1a541f0d6a792 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Fri, 6 Sep 2019 16:49:56 +0800 Subject: [PATCH 082/124] Refactored LedgerDataset: extract readonly interface LedgerDataQuery; --- .../jd/blockchain/ledger/core/AccountSet.java | 1 + .../blockchain/ledger/core/BaseAccount.java | 1 + .../ledger/core/ContractAccount.java | 1 + .../ledger/core/ContractAccountQuery.java | 29 +++++ .../ledger/core/ContractAccountSet.java | 8 +- .../blockchain/ledger/core/DataAccount.java | 1 + .../ledger/core/DataAccountQuery.java | 32 ++++++ .../ledger/core/DataAccountSet.java | 7 +- .../ledger/core/LedgerAdminDataQuery.java | 12 ++ .../ledger/core/LedgerAdminDataset.java | 10 +- .../ledger/core/LedgerDataQuery.java | 19 ++++ .../ledger/core/LedgerDataSetImpl.java | 98 ----------------- .../blockchain/ledger/core/LedgerDataset.java | 104 +++++++++++++++--- .../ledger/core/LedgerInitializer.java | 3 +- .../ledger/core/LedgerQueryService.java | 38 +++---- .../ledger/core/LedgerRepository.java | 16 +-- .../ledger/core/LedgerRepositoryImpl.java | 22 ++-- .../core/LedgerTransactionalEditor.java | 10 +- .../ledger/core/MerkleDataEntry.java | 1 + .../blockchain/ledger/core/MerkleDataSet.java | 2 + .../ledger/core/MerkleProvable.java | 1 + .../jd/blockchain/ledger/core/MerkleTree.java | 3 + .../ledger/core/OperationHandle.java | 2 +- .../ledger/core/ParticipantDataset.java | 8 +- .../ledger/core/RolePrivilegeDataset.java | 1 + .../core/TransactionBatchProcessor.java | 4 +- .../ledger/core/TransactionEngineImpl.java | 14 +-- .../ledger/core/TransactionSet.java | 1 + .../ledger/core/UserAccountQuery.java | 31 ++++++ .../ledger/core/UserAccountSet.java | 9 +- .../ledger/core/UserRoleDataset.java | 1 + .../AbstractLedgerOperationHandle.java | 5 +- ...tractContractEventSendOperationHandle.java | 9 +- .../ContractCodeDeployOperationHandle.java | 3 +- .../DataAccountKVSetOperationHandle.java | 5 +- .../DataAccountRegisterOperationHandle.java | 3 +- .../RolesConfigureOperationHandle.java | 3 +- .../handles/UserAuthorizeOperationHandle.java | 3 +- .../handles/UserRegisterOperationHandle.java | 3 +- .../ledger/core/ContractInvokingTest.java | 11 +- .../ledger/core/LedgerManagerTest.java | 12 +- .../ledger/core/MerkleDataSetTest.java | 2 +- .../ledger/core/MerkleTreeTest.java | 6 +- .../core/TransactionBatchProcessorTest.java | 13 ++- .../jd/blockchain/ledger}/MerkleDataNode.java | 2 +- .../com/jd/blockchain/ledger}/MerkleNode.java | 2 +- .../jd/blockchain/ledger}/MerkleProof.java | 2 +- .../ledger/ParticipantDataQuery.java | 29 +++++ .../peer/web/LedgerQueryController.java | 44 ++++---- .../peer/web/ManagementController.java | 3 +- .../jd/blockchain/intgr/IntegrationTest.java | 4 +- .../intgr/perf/LedgerInitializeTest.java | 4 +- .../intgr/perf/LedgerInitializeWebTest.java | 4 +- .../intgr/perf/LedgerPerformanceTest.java | 6 +- .../intgr/IntegrationTestAll4Redis.java | 4 +- .../intgr/IntegrationTestDataAccount.java | 4 +- .../initializer/LedgerInitializeTest.java | 4 +- .../LedgerInitializeWeb4Nodes.java | 2 +- .../ledger/LedgerBlockGeneratingTest.java | 4 +- .../blockchain/mocker/MockerNodeContext.java | 5 +- .../handler/MockerContractExeHandle.java | 11 +- 61 files changed, 447 insertions(+), 255 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataQuery.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/MerkleDataNode.java (78%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/MerkleNode.java (86%) rename source/ledger/{ledger-core/src/main/java/com/jd/blockchain/ledger/core => ledger-model/src/main/java/com/jd/blockchain/ledger}/MerkleProof.java (97%) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantDataQuery.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java index f0ec9748..341e22cc 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java @@ -12,6 +12,7 @@ import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java index 7f499363..1c9b778f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java @@ -8,6 +8,7 @@ import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BlockchainIdentityData; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java index 23354356..9f4a8622 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java @@ -5,6 +5,7 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.BytesData; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.ContractInfo; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.utils.Bytes; public class ContractAccount implements ContractInfo { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java new file mode 100644 index 00000000..4013a239 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java @@ -0,0 +1,29 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.utils.Bytes; + +public interface ContractAccountQuery { + + AccountHeader[] getAccounts(int fromIndex, int count); + + HashDigest getRootHash(); + + /** + * 返回合约总数; + * + * @return + */ + long getTotalCount(); + + MerkleProof getProof(Bytes address); + + boolean contains(Bytes address); + + ContractAccount getContract(Bytes address); + + ContractAccount getContract(Bytes address, long version); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java index ad51fd63..89d3ab3d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java @@ -5,12 +5,13 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class ContractAccountSet implements MerkleProvable, Transactional { +public class ContractAccountSet implements MerkleProvable, Transactional, ContractAccountQuery { private AccountSet accountSet; @@ -25,6 +26,7 @@ public class ContractAccountSet implements MerkleProvable, Transactional { accountSet = new AccountSet(dataRootHash, cryptoSetting, prefix, exStorage, verStorage, readonly, accessPolicy); } + @Override public AccountHeader[] getAccounts(int fromIndex, int count) { return accountSet.getAccounts(fromIndex,count); } @@ -47,6 +49,7 @@ public class ContractAccountSet implements MerkleProvable, Transactional { * * @return */ + @Override public long getTotalCount() { return accountSet.getTotalCount(); } @@ -56,15 +59,18 @@ public class ContractAccountSet implements MerkleProvable, Transactional { return accountSet.getProof(address); } + @Override public boolean contains(Bytes address) { return accountSet.contains(address); } + @Override public ContractAccount getContract(Bytes address) { BaseAccount accBase = accountSet.getAccount(address); return new ContractAccount(accBase); } + @Override public ContractAccount getContract(Bytes address, long version) { BaseAccount accBase = accountSet.getAccount(address, version); return new ContractAccount(accBase); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java index 93286676..cee3c29e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java @@ -8,6 +8,7 @@ import com.jd.blockchain.ledger.BytesData; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.KVDataEntry; import com.jd.blockchain.ledger.KVDataObject; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.utils.Bytes; public class DataAccount implements AccountHeader, MerkleProvable { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java new file mode 100644 index 00000000..b7cc8d43 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java @@ -0,0 +1,32 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.utils.Bytes; + +public interface DataAccountQuery { + + AccountHeader[] getAccounts(int fromIndex, int count); + + HashDigest getRootHash(); + + long getTotalCount(); + + /** + * 返回账户的存在性证明; + */ + MerkleProof getProof(Bytes address); + + /** + * 返回数据账户;
        + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + DataAccount getDataAccount(Bytes address); + + DataAccount getDataAccount(Bytes address, long version); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java index 97bdb107..dbc77437 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java @@ -5,12 +5,13 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class DataAccountSet implements MerkleProvable, Transactional { +public class DataAccountSet implements MerkleProvable, Transactional, DataAccountQuery { private AccountSet accountSet; @@ -25,6 +26,7 @@ public class DataAccountSet implements MerkleProvable, Transactional { accountSet = new AccountSet(dataRootHash, cryptoSetting, prefix, exStorage, verStorage, readonly, accessPolicy); } + @Override public AccountHeader[] getAccounts(int fromIndex, int count) { return accountSet.getAccounts(fromIndex, count); } @@ -42,6 +44,7 @@ public class DataAccountSet implements MerkleProvable, Transactional { return accountSet.getRootHash(); } + @Override public long getTotalCount() { return accountSet.getTotalCount(); } @@ -67,6 +70,7 @@ public class DataAccountSet implements MerkleProvable, Transactional { * @param address * @return */ + @Override public DataAccount getDataAccount(Bytes address) { BaseAccount accBase = accountSet.getAccount(address); if (accBase == null) { @@ -75,6 +79,7 @@ public class DataAccountSet implements MerkleProvable, Transactional { return new DataAccount(accBase); } + @Override public DataAccount getDataAccount(Bytes address, long version) { BaseAccount accBase = accountSet.getAccount(address, version); return new DataAccount(accBase); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java new file mode 100644 index 00000000..05c62138 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java @@ -0,0 +1,12 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.ParticipantDataQuery; + +public interface LedgerAdminDataQuery { + + LedgerAdminInfo getAdminInfo(); + + ParticipantDataQuery getParticipantDataset(); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index 501e8d7d..1d2c1023 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -23,7 +23,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { +public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, LedgerAdminInfo { static { DataContractRegistry.register(LedgerMetadata.class); @@ -108,6 +108,11 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { public UserRolesSettings getUserRoles() { return userRoles; } + + @Override + public LedgerAdminInfo getAdminInfo() { + return this; + } /** * 初始化账本的管理账户; @@ -290,7 +295,8 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminInfo { return participants.getParticipants(); } - ParticipantDataset getParticipantDataset() { + @Override + public ParticipantDataset getParticipantDataset() { return participants; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataQuery.java new file mode 100644 index 00000000..df70d0c0 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataQuery.java @@ -0,0 +1,19 @@ +package com.jd.blockchain.ledger.core; + +/** + * {@link LedgerDataset} 表示账本在某一个区块上的数据集合; + * + * @author huanghaiquan + * + */ +public interface LedgerDataQuery{ + + LedgerAdminDataQuery getAdminDataset(); + + UserAccountQuery getUserAccountSet(); + + DataAccountQuery getDataAccountSet(); + + ContractAccountQuery getContractAccountset(); + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java deleted file mode 100644 index 613990aa..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataSetImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.utils.Transactional; - -public class LedgerDataSetImpl implements LedgerDataset, Transactional { - - private LedgerAdminDataset adminDataset; - - private UserAccountSet userAccountSet; - - private DataAccountSet dataAccountSet; - - private ContractAccountSet contractAccountSet; - - private boolean readonly; - - /** - * Create new block; - * - * @param adminAccount - * @param userAccountSet - * @param dataAccountSet - * @param contractAccountSet - * @param readonly - */ - public LedgerDataSetImpl(LedgerAdminDataset adminAccount, UserAccountSet userAccountSet, - DataAccountSet dataAccountSet, ContractAccountSet contractAccountSet, boolean readonly) { - this.adminDataset = adminAccount; - this.userAccountSet = userAccountSet; - this.dataAccountSet = dataAccountSet; - this.contractAccountSet = contractAccountSet; - - this.readonly = readonly; - } - - @Override - public LedgerAdminDataset getAdminDataset() { - return adminDataset; - } - - @Override - public UserAccountSet getUserAccountSet() { - return userAccountSet; - } - - @Override - public DataAccountSet getDataAccountSet() { - return dataAccountSet; - } - - @Override - public ContractAccountSet getContractAccountset() { - return contractAccountSet; - } - - @Override - public boolean isUpdated() { - return adminDataset.isUpdated() || userAccountSet.isUpdated() || dataAccountSet.isUpdated() - || contractAccountSet.isUpdated(); - } - - @Override - public void commit() { - if (readonly) { - throw new IllegalStateException("Readonly ledger dataset which cann't been committed!"); - } - if (!isUpdated()) { - return; - } - - adminDataset.commit(); - userAccountSet.commit(); - dataAccountSet.commit(); - contractAccountSet.commit(); - } - - @Override - public void cancel() { - adminDataset.cancel(); - userAccountSet.cancel(); - dataAccountSet.cancel(); - contractAccountSet.cancel(); - } - - @Override - public boolean isReadonly() { - return readonly; - } - - void setReadonly() { - this.readonly = true; - this.adminDataset.setReadonly(); - this.userAccountSet.setReadonly(); - this.dataAccountSet.setReadonly(); - this.contractAccountSet.setReadonly(); - } - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java index 46e91855..9f840d35 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerDataset.java @@ -1,21 +1,97 @@ package com.jd.blockchain.ledger.core; -/** - * {@link LedgerDataset} 表示账本在某一个区块上的数据集合; - * - * @author huanghaiquan - * - */ -public interface LedgerDataset{ - - boolean isReadonly(); +import com.jd.blockchain.utils.Transactional; - LedgerAdminDataset getAdminDataset(); +public class LedgerDataset implements LedgerDataQuery, Transactional { - UserAccountSet getUserAccountSet(); + private LedgerAdminDataset adminDataset; - DataAccountSet getDataAccountSet(); + private UserAccountSet userAccountSet; - ContractAccountSet getContractAccountset(); + private DataAccountSet dataAccountSet; -} + private ContractAccountSet contractAccountSet; + + private boolean readonly; + + /** + * Create new block; + * + * @param adminAccount + * @param userAccountSet + * @param dataAccountSet + * @param contractAccountSet + * @param readonly + */ + public LedgerDataset(LedgerAdminDataset adminAccount, UserAccountSet userAccountSet, + DataAccountSet dataAccountSet, ContractAccountSet contractAccountSet, boolean readonly) { + this.adminDataset = adminAccount; + this.userAccountSet = userAccountSet; + this.dataAccountSet = dataAccountSet; + this.contractAccountSet = contractAccountSet; + + this.readonly = readonly; + } + + @Override + public LedgerAdminDataset getAdminDataset() { + return adminDataset; + } + + @Override + public UserAccountSet getUserAccountSet() { + return userAccountSet; + } + + @Override + public DataAccountSet getDataAccountSet() { + return dataAccountSet; + } + + @Override + public ContractAccountSet getContractAccountset() { + return contractAccountSet; + } + + @Override + public boolean isUpdated() { + return adminDataset.isUpdated() || userAccountSet.isUpdated() || dataAccountSet.isUpdated() + || contractAccountSet.isUpdated(); + } + + @Override + public void commit() { + if (readonly) { + throw new IllegalStateException("Readonly ledger dataset which cann't been committed!"); + } + if (!isUpdated()) { + return; + } + + adminDataset.commit(); + userAccountSet.commit(); + dataAccountSet.commit(); + contractAccountSet.commit(); + } + + @Override + public void cancel() { + adminDataset.cancel(); + userAccountSet.cancel(); + dataAccountSet.cancel(); + contractAccountSet.cancel(); + } + + public boolean isReadonly() { + return readonly; + } + + void setReadonly() { + this.readonly = true; + this.adminDataset.setReadonly(); + this.userAccountSet.setReadonly(); + this.dataAccountSet.setReadonly(); + this.contractAccountSet.setReadonly(); + } + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index 5c7116e2..8c503494 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -62,7 +62,8 @@ public class LedgerInitializer { } private static SecurityInitSettings createDefaultSecurityInitSettings() { - throw new IllegalStateException("Not implemented!"); + // TODO throw new IllegalStateException("Not implemented!"); + return null; } public static LedgerInitializer create(LedgerInitSetting initSetting) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java index c57baa97..0b85c8c6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java @@ -107,7 +107,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getDataAccountCount(HashDigest ledgerHash, long height) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); } @@ -115,7 +115,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getDataAccountCount(HashDigest ledgerHash, HashDigest blockHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); } @@ -123,7 +123,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getDataAccountTotalCount(HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); } @@ -131,7 +131,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getUserCount(HashDigest ledgerHash, long height) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); } @@ -139,7 +139,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getUserCount(HashDigest ledgerHash, HashDigest blockHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); } @@ -147,7 +147,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getUserTotalCount(HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); } @@ -155,7 +155,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getContractCount(HashDigest ledgerHash, long height) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); } @@ -163,7 +163,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getContractCount(HashDigest ledgerHash, HashDigest blockHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); } @@ -171,7 +171,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getContractTotalCount(HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); } @@ -254,7 +254,7 @@ public class LedgerQueryService implements BlockchainQueryService { public UserInfo getUser(HashDigest ledgerHash, String address) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getUser(address); } @@ -263,7 +263,7 @@ public class LedgerQueryService implements BlockchainQueryService { public AccountHeader getDataAccount(HashDigest ledgerHash, String address) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getDataAccount(Bytes.fromBase58(address)); } @@ -274,7 +274,7 @@ public class LedgerQueryService implements BlockchainQueryService { } LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; @@ -322,7 +322,7 @@ public class LedgerQueryService implements BlockchainQueryService { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; @@ -353,7 +353,7 @@ public class LedgerQueryService implements BlockchainQueryService { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccount.getDataEntriesTotalCount()); @@ -365,7 +365,7 @@ public class LedgerQueryService implements BlockchainQueryService { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); return dataAccount.getDataEntriesTotalCount(); @@ -375,7 +375,7 @@ public class LedgerQueryService implements BlockchainQueryService { public ContractInfo getContract(HashDigest ledgerHash, String address) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getContract(Bytes.fromBase58(address)); } @@ -383,7 +383,7 @@ public class LedgerQueryService implements BlockchainQueryService { public AccountHeader[] getUsers(HashDigest ledgerHash, int fromIndex, int count) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotalCount()); return userAccountSet.getAccounts(pages[0], pages[1]); } @@ -392,7 +392,7 @@ public class LedgerQueryService implements BlockchainQueryService { public AccountHeader[] getDataAccounts(HashDigest ledgerHash, int fromIndex, int count) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotalCount()); return dataAccountSet.getAccounts(pages[0], pages[1]); } @@ -401,7 +401,7 @@ public class LedgerQueryService implements BlockchainQueryService { public AccountHeader[] getContractAccounts(HashDigest ledgerHash, int fromIndex, int count) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotalCount()); return contractAccountSet.getAccounts(pages[0], pages[1]); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java index 73400223..a8790ab0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java @@ -58,17 +58,17 @@ public interface LedgerRepository extends Closeable { LedgerBlock getBlock(HashDigest hash); - LedgerDataset getDataSet(LedgerBlock block); + LedgerDataQuery getDataSet(LedgerBlock block); TransactionSet getTransactionSet(LedgerBlock block); - UserAccountSet getUserAccountSet(LedgerBlock block); + UserAccountQuery getUserAccountSet(LedgerBlock block); - DataAccountSet getDataAccountSet(LedgerBlock block); + DataAccountQuery getDataAccountSet(LedgerBlock block); - ContractAccountSet getContractAccountSet(LedgerBlock block); + ContractAccountQuery getContractAccountSet(LedgerBlock block); - default LedgerDataset getDataSet() { + default LedgerDataQuery getDataSet() { return getDataSet(getLatestBlock()); } @@ -76,15 +76,15 @@ public interface LedgerRepository extends Closeable { return getTransactionSet(getLatestBlock()); } - default UserAccountSet getUserAccountSet() { + default UserAccountQuery getUserAccountSet() { return getUserAccountSet(getLatestBlock()); } - default DataAccountSet getDataAccountSet() { + default DataAccountQuery getDataAccountSet() { return getDataAccountSet(getLatestBlock()); } - default ContractAccountSet getContractAccountSet() { + default ContractAccountQuery getContractAccountSet() { return getContractAccountSet(getLatestBlock()); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 45da206c..32053225 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -313,7 +313,7 @@ class LedgerRepositoryImpl implements LedgerRepository { } @Override - public UserAccountSet getUserAccountSet(LedgerBlock block) { + public UserAccountQuery getUserAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); // UserAccountSet userAccountSet = null; if (height == block.getHeight()) { @@ -341,7 +341,7 @@ class LedgerRepositoryImpl implements LedgerRepository { } @Override - public DataAccountSet getDataAccountSet(LedgerBlock block) { + public DataAccountQuery getDataAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); // DataAccountSet dataAccountSet = null; if (height == block.getHeight()) { @@ -370,7 +370,7 @@ class LedgerRepositoryImpl implements LedgerRepository { } @Override - public ContractAccountSet getContractAccountSet(LedgerBlock block) { + public ContractAccountQuery getContractAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); // ContractAccountSet contractAccountSet = null; if (height == block.getHeight()) { @@ -426,7 +426,7 @@ class LedgerRepositoryImpl implements LedgerRepository { UserAccountSet userAccountSet = createUserAccountSet(block, cryptoSetting); DataAccountSet dataAccountSet = createDataAccountSet(block, cryptoSetting); ContractAccountSet contractAccountSet = createContractAccountSet(block, cryptoSetting); - return new LedgerDataSetImpl(adminDataset, userAccountSet, dataAccountSet, contractAccountSet, true); + return new LedgerDataset(adminDataset, userAccountSet, dataAccountSet, contractAccountSet, true); } @Override @@ -475,7 +475,7 @@ class LedgerRepositoryImpl implements LedgerRepository { return BLOCK_PREFIX.concat(blockHash); } - static LedgerDataSetImpl newDataSet(LedgerInitSetting initSetting, String keyPrefix, + static LedgerDataset newDataSet(LedgerInitSetting initSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) { LedgerAdminDataset adminAccount = new LedgerAdminDataset(initSetting, keyPrefix, ledgerExStorage, ledgerVerStorage); @@ -509,7 +509,7 @@ class LedgerRepositoryImpl implements LedgerRepository { ContractAccountSet contractAccountSet = new ContractAccountSet(adminAccount.getSettings().getCryptoSetting(), contractsetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); - LedgerDataSetImpl newDataSet = new LedgerDataSetImpl(adminAccount, userAccountSet, dataAccountSet, + LedgerDataset newDataSet = new LedgerDataset(adminAccount, userAccountSet, dataAccountSet, contractAccountSet, false); return newDataSet; @@ -529,7 +529,7 @@ class LedgerRepositoryImpl implements LedgerRepository { return transactionSet; } - static LedgerDataSetImpl loadDataSet(LedgerDataSnapshot dataSnapshot, CryptoSetting cryptoSetting, String keyPrefix, + static LedgerDataset loadDataSet(LedgerDataSnapshot dataSnapshot, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { LedgerAdminDataset adminAccount = new LedgerAdminDataset(dataSnapshot.getAdminAccountHash(), keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); @@ -545,7 +545,7 @@ class LedgerRepositoryImpl implements LedgerRepository { ContractAccountSet contractAccountSet = loadContractAccountSet(dataSnapshot.getContractAccountSetHash(), cryptoSetting, keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); - LedgerDataSetImpl dataset = new LedgerDataSetImpl(adminAccount, userAccountSet, dataAccountSet, + LedgerDataset dataset = new LedgerDataset(adminAccount, userAccountSet, dataAccountSet, contractAccountSet, readonly); return dataset; @@ -695,15 +695,15 @@ class LedgerRepositoryImpl implements LedgerRepository { return ledgerDataset; } - public ContractAccountSet getContractAccountSet() { + public ContractAccountQuery getContractAccountSet() { return ledgerDataset.getContractAccountset(); } - public DataAccountSet getDataAccountSet() { + public DataAccountQuery getDataAccountSet() { return ledgerDataset.getDataAccountSet(); } - public UserAccountSet getUserAccountSet() { + public UserAccountQuery getUserAccountSet() { return ledgerDataset.getUserAccountSet(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java index 70658ae2..ea7bb7ed 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionalEditor.java @@ -72,7 +72,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { /** * 最后提交的账本数据集; */ - private volatile LedgerDataSetImpl latestLedgerDataset; + private volatile LedgerDataset latestLedgerDataset; /** * 最后提交的交易集合; @@ -235,7 +235,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { // init storage of new transaction; BufferedKVStorage txBufferedStorage = new BufferedKVStorage(baseStorage, baseStorage, false); - LedgerDataSetImpl txDataset = null; + LedgerDataset txDataset = null; TransactionSet txset = null; if (previousTxSnapshot == null) { // load the starting point of the new transaction; @@ -470,7 +470,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { private TransactionRequest txRequest; - private LedgerDataSetImpl dataset; + private LedgerDataset dataset; private TransactionSet txset; @@ -484,7 +484,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { private HashDigest txRootHash; - private LedgerTransactionContextImpl(TransactionRequest txRequest, LedgerDataSetImpl dataset, + private LedgerTransactionContextImpl(TransactionRequest txRequest, LedgerDataset dataset, TransactionSet txset, BufferedKVStorage storage, LedgerTransactionalEditor editor) { this.txRequest = txRequest; this.dataset = dataset; @@ -494,7 +494,7 @@ public class LedgerTransactionalEditor implements LedgerEditor { } @Override - public LedgerDataSetImpl getDataset() { + public LedgerDataset getDataset() { return dataset; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataEntry.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataEntry.java index 527e7115..3dd99df0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataEntry.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataEntry.java @@ -1,5 +1,6 @@ package com.jd.blockchain.ledger.core; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.VersioningKVEntry; public interface MerkleDataEntry { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java index ae37b25c..16927b97 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleDataSet.java @@ -3,6 +3,8 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.MerkleDataNode; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; import com.jd.blockchain.storage.service.VersioningKVEntry; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java index 2f93bad7..f778279f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java @@ -1,6 +1,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.utils.Bytes; public interface MerkleProvable { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleTree.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleTree.java index 7c3c077c..d86facba 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleTree.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleTree.java @@ -20,6 +20,9 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.HashFunction; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.MerkleDataNode; +import com.jd.blockchain.ledger.MerkleNode; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; import com.jd.blockchain.utils.Bytes; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java index d7bf15e8..763ea51a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java @@ -27,6 +27,6 @@ public interface OperationHandle { * @return */ BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); + LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java index 3185ae91..71e71199 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantDataset.java @@ -4,14 +4,16 @@ import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantDataQuery; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class ParticipantDataset implements Transactional, MerkleProvable { +public class ParticipantDataset implements Transactional, MerkleProvable, ParticipantDataQuery { static { DataContractRegistry.register(ParticipantNode.class); @@ -54,6 +56,7 @@ public class ParticipantDataset implements Transactional, MerkleProvable { dataset.cancel(); } + @Override public long getParticipantCount() { return dataset.getDataCount(); } @@ -77,6 +80,7 @@ public class ParticipantDataset implements Transactional, MerkleProvable { return address; } + @Override public boolean contains(Bytes address) { Bytes key = encodeKey(address); long latestVersion = dataset.getVersion(key); @@ -92,6 +96,7 @@ public class ParticipantDataset implements Transactional, MerkleProvable { * @param address * @return */ + @Override public ParticipantNode getParticipant(Bytes address) { Bytes key = encodeKey(address); byte[] bytes = dataset.getValue(key); @@ -101,6 +106,7 @@ public class ParticipantDataset implements Transactional, MerkleProvable { return BinaryProtocol.decode(bytes); } + @Override public ParticipantNode[] getParticipants() { byte[][] bytes = dataset.getLatestValues(0, (int) dataset.getDataCount()); ParticipantNode[] pns = new ParticipantNode[bytes.length]; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java index 7e7bd238..14673435 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/RolePrivilegeDataset.java @@ -6,6 +6,7 @@ import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerPrivilege; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.ledger.PrivilegeSet; import com.jd.blockchain.ledger.Privileges; import com.jd.blockchain.ledger.RolePrivilegeSettings; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index 37c47906..9a2ddcef 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -46,7 +46,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { private LedgerEditor newBlockEditor; - private LedgerDataset previousBlockDataset; + private LedgerDataQuery previousBlockDataset; private OperationHandleRegisteration opHandles; @@ -65,7 +65,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { * @param opHandles 操作处理对象注册表; */ public TransactionBatchProcessor(LedgerSecurityManager securityManager, LedgerEditor newBlockEditor, - LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService) { + LedgerDataQuery previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService) { this.securityManager = securityManager; this.newBlockEditor = newBlockEditor; this.previousBlockDataset = previousBlockDataset; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index d9d47840..900b6f28 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -3,10 +3,10 @@ package com.jd.blockchain.ledger.core; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.LedgerBlock; import org.springframework.beans.factory.annotation.Autowired; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.service.TransactionBatchProcess; import com.jd.blockchain.service.TransactionEngine; @@ -40,11 +40,11 @@ public class TransactionEngineImpl implements TransactionEngine { LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - LedgerDataset previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); - LedgerAdminDataset previousAdminDataset = previousBlockDataset.getAdminDataset(); - LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getRolePrivileges(), - previousAdminDataset.getUserRoles()); + LedgerAdminDataQuery previousAdminDataset = previousBlockDataset.getAdminDataset(); + LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getAdminInfo().getRolePrivileges(), + previousAdminDataset.getAdminInfo().getUserRoles()); batch = new InnerTransactionBatchProcessor(ledgerHash, securityManager, newBlockEditor, previousBlockDataset, opHdlRegs, ledgerService, ledgerBlock.getHeight()); batchs.put(ledgerHash, batch); @@ -75,7 +75,7 @@ public class TransactionEngineImpl implements TransactionEngine { * @param opHandles 操作处理对象注册表; */ public InnerTransactionBatchProcessor(HashDigest ledgerHash, LedgerSecurityManager securityManager, - LedgerEditor newBlockEditor, LedgerDataset previousBlockDataset, OperationHandleRegisteration opHandles, + LedgerEditor newBlockEditor, LedgerDataQuery previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService, long blockHeight) { super(securityManager, newBlockEditor, previousBlockDataset, opHandles, ledgerService); this.ledgerHash = ledgerHash; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java index c96ce3a2..24cb6416 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java @@ -6,6 +6,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java new file mode 100644 index 00000000..3d920b5c --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java @@ -0,0 +1,31 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.utils.Bytes; + +public interface UserAccountQuery { + + AccountHeader[] getAccounts(int fromIndex, int count); + + /** + * 返回用户总数; + * + * @return + */ + long getTotalCount(); + + HashDigest getRootHash(); + + MerkleProof getProof(Bytes key); + + UserAccount getUser(String address); + + UserAccount getUser(Bytes address); + + boolean contains(Bytes address); + + UserAccount getUser(Bytes address, long version); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java index d6100ea6..f1e8bbc3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java @@ -5,6 +5,7 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; @@ -14,7 +15,7 @@ import com.jd.blockchain.utils.Transactional; * @author huanghaiquan * */ -public class UserAccountSet implements Transactional, MerkleProvable { +public class UserAccountSet implements Transactional, MerkleProvable, UserAccountQuery { private AccountSet accountSet; @@ -30,6 +31,7 @@ public class UserAccountSet implements Transactional, MerkleProvable { accessPolicy); } + @Override public AccountHeader[] getAccounts(int fromIndex, int count) { return accountSet.getAccounts(fromIndex,count); } @@ -39,6 +41,7 @@ public class UserAccountSet implements Transactional, MerkleProvable { * * @return */ + @Override public long getTotalCount() { return accountSet.getTotalCount(); } @@ -61,19 +64,23 @@ public class UserAccountSet implements Transactional, MerkleProvable { return accountSet.getProof(key); } + @Override public UserAccount getUser(String address) { return getUser(Bytes.fromBase58(address)); } + @Override public UserAccount getUser(Bytes address) { BaseAccount baseAccount = accountSet.getAccount(address); return new UserAccount(baseAccount); } + @Override public boolean contains(Bytes address) { return accountSet.contains(address); } + @Override public UserAccount getUser(Bytes address, long version) { BaseAccount baseAccount = accountSet.getAccount(address, version); return new UserAccount(baseAccount); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java index 9a88e39b..2f4eb514 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java @@ -7,6 +7,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.AuthorizationException; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.ledger.RoleSet; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.UserRoles; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java index 89bc5cf6..58444d3b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java @@ -4,6 +4,7 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -44,7 +45,7 @@ public abstract class AbstractLedgerOperationHandle impleme @Override public final BytesValue process(Operation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); @@ -68,5 +69,5 @@ public abstract class AbstractLedgerOperationHandle impleme * @param ledgerService */ protected abstract void doProcess(T op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); + LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java index edcb5db3..ccbce284 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java @@ -10,7 +10,8 @@ import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.core.ContractAccount; -import com.jd.blockchain.ledger.core.ContractAccountSet; +import com.jd.blockchain.ledger.core.ContractAccountQuery; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerService; @@ -31,7 +32,7 @@ public abstract class AbtractContractEventSendOperationHandle implements Operati @Override public BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); securityPolicy.checkEndpoints(TransactionPermission.CONTRACT_OPERATION, MultiIdsPolicy.AT_LEAST_ONE); @@ -44,11 +45,11 @@ public abstract class AbtractContractEventSendOperationHandle implements Operati } private BytesValue doProcess(TransactionRequestExtension request, ContractEventSendOperation contractOP, - LedgerDataset newBlockDataset, LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, + LedgerDataset newBlockDataset, LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { // 先从账本校验合约的有效性; // 注意:必须在前一个区块的数据集中进行校验,因为那是经过共识的数据;从当前新区块链数据集校验则会带来攻击风险:未经共识的合约得到执行; - ContractAccountSet contractSet = previousBlockDataset.getContractAccountset(); + ContractAccountQuery contractSet = previousBlockDataset.getContractAccountset(); if (!contractSet.contains(contractOP.getContractAddress())) { throw new LedgerException(String.format("Contract was not registered! --[ContractAddress=%s]", contractOP.getContractAddress())); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java index 6e0d68b8..9d975227 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -17,7 +18,7 @@ public class ContractCodeDeployOperationHandle extends AbstractLedgerOperationHa @Override protected void doProcess(ContractCodeDeployOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { // TODO: 校验合约代码的正确性; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index 76f55220..6fd498e2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -2,10 +2,11 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.DataAccountDoesNotExistException; import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataVersionConflictException; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; +import com.jd.blockchain.ledger.DataVersionConflictException; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.DataAccount; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -22,7 +23,7 @@ public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHand @Override protected void doProcess(DataAccountKVSetOperation kvWriteOp, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java index 06fe0746..2e0f872e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java @@ -3,6 +3,7 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -18,7 +19,7 @@ public class DataAccountRegisterOperationHandle extends AbstractLedgerOperationH @Override protected void doProcess(DataAccountRegisterOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataset previousBlockDataset, + TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { // TODO: 请求者应该提供数据账户的公钥签名,以更好地确保注册人对该地址和公钥具有合法使用权; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java index d0f95989..7f3c51bb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java @@ -5,6 +5,7 @@ import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.RolesConfigureOperation; import com.jd.blockchain.ledger.RolesConfigureOperation.RolePrivilegeEntry; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -20,7 +21,7 @@ public class RolesConfigureOperationHandle extends AbstractLedgerOperationHandle @Override protected void doProcess(RolesConfigureOperation operation, LedgerDataset newBlockDataset, - TransactionRequestExtension request, LedgerDataset previousBlockDataset, + TransactionRequestExtension request, LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java index 26ce7c4a..b5fee9bb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java @@ -10,6 +10,7 @@ import com.jd.blockchain.ledger.UserAuthorizeOperation; import com.jd.blockchain.ledger.UserAuthorizeOperation.UserRolesEntry; import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -25,7 +26,7 @@ public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle< @Override protected void doProcess(UserAuthorizeOperation operation, LedgerDataset newBlockDataset, - TransactionRequestExtension request, LedgerDataset previousBlockDataset, + TransactionRequestExtension request, LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java index 6a399e71..6307968d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java @@ -3,6 +3,7 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIdsPolicy; @@ -19,7 +20,7 @@ public class UserRegisterOperationHandle extends AbstractLedgerOperationHandle + * 如果不存在,则返回 null; + * + * @param address + * @return + */ + ParticipantNode getParticipant(Bytes address); + + ParticipantNode[] getParticipants(); + +} \ No newline at end of file diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java index 87eaf11a..992cf1a0 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java @@ -14,14 +14,14 @@ import org.springframework.web.bind.annotation.RestController; import com.jd.blockchain.contract.ContractException; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.core.ContractAccountSet; +import com.jd.blockchain.ledger.core.ContractAccountQuery; import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.DataAccountSet; +import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.ParticipantCertData; import com.jd.blockchain.ledger.core.TransactionSet; -import com.jd.blockchain.ledger.core.UserAccountSet; +import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.QueryUtil; @@ -145,7 +145,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHeight") long height) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); } @@ -155,7 +155,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHash") HashDigest blockHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); } @@ -164,7 +164,7 @@ public class LedgerQueryController implements BlockchainQueryService { public long getDataAccountTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); } @@ -174,7 +174,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHeight") long height) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); } @@ -184,7 +184,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHash") HashDigest blockHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); } @@ -193,7 +193,7 @@ public class LedgerQueryController implements BlockchainQueryService { public long getUserTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); } @@ -203,7 +203,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHeight") long height) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); } @@ -213,7 +213,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHash") HashDigest blockHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); } @@ -222,7 +222,7 @@ public class LedgerQueryController implements BlockchainQueryService { public long getContractTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); } @@ -320,7 +320,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "address") String address) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getUser(address); } @@ -330,7 +330,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "address") String address) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getDataAccount(Bytes.fromBase58(address)); } @@ -344,7 +344,7 @@ public class LedgerQueryController implements BlockchainQueryService { } LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; @@ -393,7 +393,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; @@ -428,7 +428,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccount.getDataEntriesTotalCount()); @@ -442,7 +442,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); return dataAccount.getDataEntriesTotalCount(); @@ -454,7 +454,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "address") String address) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getContract(Bytes.fromBase58(address)); } @@ -473,7 +473,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - UserAccountSet userAccountSet = ledger.getUserAccountSet(block); + UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotalCount()); return userAccountSet.getAccounts(pages[0], pages[1]); } @@ -493,7 +493,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - DataAccountSet dataAccountSet = ledger.getDataAccountSet(block); + DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotalCount()); return dataAccountSet.getAccounts(pages[0], pages[1]); } @@ -505,7 +505,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { LedgerRepository ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - ContractAccountSet contractAccountSet = ledger.getContractAccountSet(block); + ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotalCount()); return contractAccountSet.getAccounts(pages[0], pages[1]); } diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index 7449e248..9ad0a94b 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -47,6 +47,7 @@ import com.jd.blockchain.ledger.TransactionContentBody; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.core.LedgerAdminDataQuery; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.peer.ConsensusRealm; @@ -122,7 +123,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(BftsmartConsensusSettings.class); DataContractRegistry.register(BftsmartNodeSettings.class); - DataContractRegistry.register(LedgerAdminInfo.class); + DataContractRegistry.register(LedgerAdminDataQuery.class); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java index e6c999e8..f00b50dd 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java @@ -35,7 +35,7 @@ import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.UserInfo; -import com.jd.blockchain.ledger.core.DataAccountSet; +import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; @@ -619,7 +619,7 @@ public class IntegrationTest { LedgerBlock backgroundLedgerBlock = ledgerOfNode0.retrieveLatestBlock(); // 验证合约中的赋值,外部可以获得; - DataAccountSet dataAccountSet = ledgerOfNode0.getDataAccountSet(backgroundLedgerBlock); + DataAccountQuery dataAccountSet = ledgerOfNode0.getDataAccountSet(backgroundLedgerBlock); AsymmetricKeypair key = Crypto.getSignatureFunction("ED25519").generateKeypair(); PubKey pubKey = key.getPubKey(); Bytes dataAddress = AddressEncoding.generateAddress(pubKey); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index 39618d9d..431c6596 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -27,7 +27,7 @@ import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; -import com.jd.blockchain.ledger.core.UserAccountSet; +import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; //import com.jd.blockchain.storage.service.utils.MemoryBasedDb; import com.jd.blockchain.tools.initializer.DBConnectionConfig; @@ -113,7 +113,7 @@ public class LedgerInitializeTest { LedgerBlock genesisBlock = ledger0.getLatestBlock(); - UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); + UserAccountQuery userset0 = ledger0.getUserAccountSet(genesisBlock); PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index 7b49bfb7..f174b18f 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -32,7 +32,7 @@ import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; -import com.jd.blockchain.ledger.core.UserAccountSet; +import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; //import com.jd.blockchain.storage.service.utils.MemoryBasedDb; @@ -298,7 +298,7 @@ public class LedgerInitializeWebTest { LedgerBlock genesisBlock = ledger0.getLatestBlock(); - UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); + UserAccountQuery userset0 = ledger0.getUserAccountSet(genesisBlock); PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index 03243aa4..c1ac727f 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -40,7 +40,7 @@ import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; @@ -291,7 +291,7 @@ public class LedgerPerformanceTest { ConsoleUtils.info("\r\n\r\n================= 准备测试交易 [执行合约] ================="); LedgerBlock latestBlock = ledger.getLatestBlock(); - LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); LedgerEditor newEditor = ledger.createNextBlock(); TransactionBatchProcessor txProc = new TransactionBatchProcessor(DEFAULT_SECURITY_MANAGER, newEditor, previousDataSet, opHandler, ledgerManager); @@ -324,7 +324,7 @@ public class LedgerPerformanceTest { long batchStartTs = System.currentTimeMillis(); for (int i = 0; i < batchCount; i++) { LedgerBlock latestBlock = ledger.getLatestBlock(); - LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); if (statistic) { ConsoleUtils.info("------ 开始执行交易, 即将生成区块[高度:%s] ------", (latestBlock.getHeight() + 1)); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java index 4ee5c5e8..43df7bde 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java @@ -4,7 +4,7 @@ import com.jd.blockchain.crypto.*; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.DataAccountSet; +import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; @@ -445,7 +445,7 @@ public class IntegrationTestAll4Redis { assertEquals(txResp.getBlockHeight(), backgroundLedgerBlock.getHeight()); // 验证合约中的赋值,外部可以获得; - DataAccountSet dataAccountSet = ledgerRepository.getDataAccountSet(backgroundLedgerBlock); + DataAccountQuery dataAccountSet = ledgerRepository.getDataAccountSet(backgroundLedgerBlock); AsymmetricKeypair key = Crypto.getSignatureFunction("ED25519").generateKeypair(); PubKey pubKey = key.getPubKey(); Bytes dataAddress = AddressEncoding.generateAddress(pubKey); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java index 4efd7ae5..e11891a9 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java @@ -29,7 +29,7 @@ import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; -import com.jd.blockchain.ledger.core.DataAccountSet; +import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.sdk.BlockchainService; @@ -187,7 +187,7 @@ public class IntegrationTestDataAccount { LedgerRepository ledgerRepository = ledgerManager.register(ledgerHashs[0], memoryBasedDb.getStorageService()); - DataAccountSet dataAccountSet = ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()); + DataAccountQuery dataAccountSet = ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()); TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHashs[0]); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index 813c964c..6d18f24f 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -34,7 +34,7 @@ import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.UserAccount; -import com.jd.blockchain.ledger.core.UserAccountSet; +import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; @@ -143,7 +143,7 @@ public class LedgerInitializeTest { assertEquals(0, genesisBlock.getHeight()); assertEquals(ledgerHash0, genesisBlock.getHash()); - UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); + UserAccountQuery userset0 = ledger0.getUserAccountSet(genesisBlock); PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index 939811a1..6909c155 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -138,7 +138,7 @@ public class LedgerInitializeWeb4Nodes { assertEquals(0, genesisBlock.getHeight()); assertEquals(ledgerHash0, genesisBlock.getHash()); - UserAccountSet userset0 = ledger0.getUserAccountSet(genesisBlock); + UserAccountQuery userset0 = ledger0.getUserAccountSet(genesisBlock); PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index 27f53934..0e1b2755 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -27,7 +27,7 @@ import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; @@ -77,7 +77,7 @@ public class LedgerBlockGeneratingTest { LedgerBlock latestBlock = ledger.getLatestBlock(); assertEquals(height + i, latestBlock.getHeight()); - LedgerDataset previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); ConsoleUtils.info("------ 开始执行交易, 即将生成区块[%s] ------", (latestBlock.getHeight() + 1)); long startTs = System.currentTimeMillis(); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index ab2167a2..26f54dc5 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -60,13 +60,12 @@ import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; -import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerSecurityManager; -import com.jd.blockchain.ledger.core.OperationHandleRegisteration; import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.mocker.config.MockerConstant; @@ -465,7 +464,7 @@ public class MockerNodeContext implements BlockchainQueryService { public OperationResult[] txProcess(TransactionRequest txRequest) { LedgerEditor newEditor = ledgerRepository.createNextBlock(); LedgerBlock latestBlock = ledgerRepository.getLatestBlock(); - LedgerDataset previousDataSet = ledgerRepository.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledgerRepository.getDataSet(latestBlock); TransactionBatchProcessor txProc = new TransactionBatchProcessor(getSecurityManager(), newEditor, previousDataSet, opHandler, ledgerManager); TransactionResponse txResp = txProc.schedule(txRequest); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index 174e1f86..d278de13 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -9,7 +9,14 @@ import com.jd.blockchain.contract.ContractException; import com.jd.blockchain.contract.EventProcessingAware; import com.jd.blockchain.contract.LedgerContext; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.*; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.BytesValueEncoding; +import com.jd.blockchain.ledger.BytesValueList; +import com.jd.blockchain.ledger.ContractEventSendOperation; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerQueryService; @@ -30,7 +37,7 @@ public class MockerContractExeHandle implements OperationHandle { @Override public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestExtension request, - LedgerDataset previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { ContractEventSendOperation contractOP = (ContractEventSendOperation) op; HashDigest txHash = request.getTransactionContent().getHash(); From a418cf05fa9a2de95219042d47c4d508914b9b2e Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Sun, 8 Sep 2019 16:05:29 +0800 Subject: [PATCH 083/124] Refactored the process of ledger init operation: uniquely processed by TransactionBatchProcessor; --- .../jd/blockchain/ledger/core/AccountSet.java | 4 - .../DefaultOperationHandleRegisteration.java | 105 ++++++--- .../ledger/core/EmptyLedgerDataset.java | 209 ++++++++++++++++++ .../core/FullPermissionedSecurityManager.java | 98 ++++++++ .../ledger/core/LedgerAdminDataset.java | 4 +- .../ledger/core/LedgerInitializer.java | 33 +-- .../ledger/core/LedgerRepositoryImpl.java | 125 ----------- .../core/LedgerSecurityManagerImpl.java | 166 +++++++++++--- ...ultiIdsPolicy.java => MultiIDsPolicy.java} | 4 +- .../core/OperationHandleRegisteration.java | 4 +- .../ledger/core/SecurityPolicy.java | 49 +++- .../core/TransactionBatchProcessor.java | 79 +++---- .../ledger/core/TransactionEngineImpl.java | 10 +- .../AbstractLedgerOperationHandle.java | 4 +- ...tractContractEventSendOperationHandle.java | 4 +- .../ContractCodeDeployOperationHandle.java | 4 +- .../DataAccountKVSetOperationHandle.java | 4 +- .../DataAccountRegisterOperationHandle.java | 4 +- .../handles/LedgerInitOperationHandle.java | 27 +++ .../RolesConfigureOperationHandle.java | 4 +- .../handles/UserAuthorizeOperationHandle.java | 4 +- .../handles/UserRegisterOperationHandle.java | 4 +- .../ledger/core/ContractInvokingTest.java | 19 +- .../core/LedgerSecurityManagerTest.java | 26 ++- .../core/TransactionBatchProcessorTest.java | 8 +- .../intgr/perf/LedgerPerformanceTest.java | 39 +++- .../blockchain/mocker/MockerNodeContext.java | 8 +- 27 files changed, 724 insertions(+), 325 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/FullPermissionedSecurityManager.java rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{MultiIdsPolicy.java => MultiIDsPolicy.java} (74%) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java index 341e22cc..dc4a59cd 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java @@ -70,10 +70,6 @@ public class AccountSet implements Transactional, MerkleProvable { this.accessPolicy = accessPolicy; } - // public HashDigest getAccountRootHash() { - // return merkleDataset.getRootHash(); - // } - @Override public HashDigest getRootHash() { return merkleDataset.getRootHash(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java index 7ab3b129..6dd34e0a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java @@ -1,20 +1,31 @@ package com.jd.blockchain.ledger.core; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.springframework.stereotype.Component; import com.jd.blockchain.ledger.LedgerException; +import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; import com.jd.blockchain.ledger.core.handles.JVMContractEventSendOperationHandle; +import com.jd.blockchain.ledger.core.handles.LedgerInitOperationHandle; import com.jd.blockchain.ledger.core.handles.RolesConfigureOperationHandle; import com.jd.blockchain.ledger.core.handles.UserAuthorizeOperationHandle; import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; +import com.jd.blockchain.transaction.ContractCodeDeployOpTemplate; +import com.jd.blockchain.transaction.ContractEventSendOpTemplate; +import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; +import com.jd.blockchain.transaction.DataAccountRegisterOpTemplate; +import com.jd.blockchain.transaction.LedgerInitOpTemplate; +import com.jd.blockchain.transaction.RolesConfigureOpTemplate; +import com.jd.blockchain.transaction.UserAuthorizeOpTemplate; +import com.jd.blockchain.transaction.UserRegisterOpTemplate; @Component public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { @@ -23,51 +34,74 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis private Map, OperationHandle> handles = new ConcurrentHashMap<>(); - private Map, OperationHandle> cacheMapping = new ConcurrentHashMap<>(); - static { - addDefaultHandle(new RolesConfigureOperationHandle()); - addDefaultHandle(new UserAuthorizeOperationHandle()); - addDefaultHandle(new DataAccountKVSetOperationHandle()); - addDefaultHandle(new DataAccountRegisterOperationHandle()); - addDefaultHandle(new UserRegisterOperationHandle()); - addDefaultHandle(new ContractCodeDeployOperationHandle()); - addDefaultHandle(new JVMContractEventSendOperationHandle()); + registerDefaultHandle(new LedgerInitOperationHandle()); + + registerDefaultHandle(new RolesConfigureOperationHandle()); + + registerDefaultHandle(new UserAuthorizeOperationHandle()); + + registerDefaultHandle(new UserRegisterOperationHandle()); + + registerDefaultHandle(new DataAccountKVSetOperationHandle()); + + registerDefaultHandle(new DataAccountRegisterOperationHandle()); + + registerDefaultHandle(new ContractCodeDeployOperationHandle()); + + registerDefaultHandle(new JVMContractEventSendOperationHandle()); } - private static void addDefaultHandle(OperationHandle handle) { + private static void registerDefaultHandle(OperationHandle handle) { DEFAULT_HANDLES.put(handle.getOperationType(), handle); } /** - * 以最高优先级插入一个操作处理器; + * 注册操作处理器;此方法将覆盖默认的操作处理器配置; * * @param handle */ public void registerHandle(OperationHandle handle) { + List> opTypes = new ArrayList>(); + for (Class opType : handles.keySet()) { + if (opType.isAssignableFrom(handle.getOperationType())) { + opTypes.add(opType); + } + } + + for (Class opType : opTypes) { + handles.put(opType, handle); + } handles.put(handle.getOperationType(), handle); } private OperationHandle getRegisteredHandle(Class operationType) { OperationHandle hdl = handles.get(operationType); if (hdl == null) { - for (Entry, OperationHandle> entry : handles.entrySet()) { - if (entry.getKey().isAssignableFrom(operationType)) { - hdl = entry.getValue(); + hdl = DEFAULT_HANDLES.get(operationType); + + //按“操作类型”的继承关系匹配; + if (hdl == null) { + for (Class opType : handles.keySet()) { + if (opType.isAssignableFrom(operationType)) { + hdl = handles.get(opType); + break; + } } } - } - return hdl; - } - - private OperationHandle getDefaultHandle(Class operationType) { - OperationHandle hdl = DEFAULT_HANDLES.get(operationType); - if (hdl == null) { - for (Entry, OperationHandle> entry : DEFAULT_HANDLES.entrySet()) { - if (entry.getKey().isAssignableFrom(operationType)) { - hdl = entry.getValue(); + + if (hdl == null) { + for (Class opType : DEFAULT_HANDLES.keySet()) { + if (opType.isAssignableFrom(operationType)) { + hdl = DEFAULT_HANDLES.get(opType); + break; + } } } + + if (hdl != null) { + handles.put(operationType, hdl); + } } return hdl; } @@ -80,20 +114,19 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis * java.lang.Class) */ @Override - public OperationHandle getHandle(Class operationType) { - OperationHandle hdl = cacheMapping.get(operationType); - if (hdl != null) { - return hdl; - } - hdl = getRegisteredHandle(operationType); + public OperationHandle getHandle(Class operationType) { + OperationHandle hdl = getRegisteredHandle(operationType); if (hdl == null) { - hdl = getDefaultHandle(operationType); - if (hdl == null) { - throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); - } + throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); } - cacheMapping.put(operationType, hdl); return hdl; } + private static class OpHandleStub { + + private Class operationType; + + private OperationHandle operationHandle; + + } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java new file mode 100644 index 00000000..64e08117 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java @@ -0,0 +1,209 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.ledger.ParticipantDataQuery; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.utils.Bytes; + +public class EmptyLedgerDataset implements LedgerDataQuery { + + private static final LedgerAdminDataQuery EMPTY_ADMIN_DATA = new EmptyAdminData(); + + private static final UserAccountQuery EMPTY_USER_ACCOUNTS = new EmptyUserAccountSet(); + + private static final DataAccountQuery EMPTY_DATA_ACCOUNTS = new EmptyDataAccountSet(); + + private static final ContractAccountQuery EMPTY_CONTRACT_ACCOUNTS = new EmptyContractAccountSet(); + + private static final ParticipantDataQuery EMPTY_PARTICIPANTS = new EmptyParticipantData(); + + @Override + public LedgerAdminDataQuery getAdminDataset() { + return EMPTY_ADMIN_DATA; + } + + @Override + public UserAccountQuery getUserAccountSet() { + return EMPTY_USER_ACCOUNTS; + } + + @Override + public DataAccountQuery getDataAccountSet() { + return EMPTY_DATA_ACCOUNTS; + } + + @Override + public ContractAccountQuery getContractAccountset() { + return EMPTY_CONTRACT_ACCOUNTS; + } + + + private static class EmptyAdminData implements LedgerAdminDataQuery{ + + + @Override + public LedgerAdminInfo getAdminInfo() { + return null; + } + + @Override + public ParticipantDataQuery getParticipantDataset() { + return EMPTY_PARTICIPANTS; + } + + } + + private static class EmptyParticipantData implements ParticipantDataQuery{ + + @Override + public HashDigest getRootHash() { + return null; + } + + @Override + public MerkleProof getProof(Bytes key) { + return null; + } + + @Override + public long getParticipantCount() { + return 0; + } + + @Override + public boolean contains(Bytes address) { + return false; + } + + @Override + public ParticipantNode getParticipant(Bytes address) { + return null; + } + + @Override + public ParticipantNode[] getParticipants() { + return null; + } + + } + + private static class EmptyUserAccountSet implements UserAccountQuery{ + + @Override + public AccountHeader[] getAccounts(int fromIndex, int count) { + return null; + } + + @Override + public long getTotalCount() { + return 0; + } + + @Override + public HashDigest getRootHash() { + return null; + } + + @Override + public MerkleProof getProof(Bytes key) { + return null; + } + + @Override + public UserAccount getUser(String address) { + return null; + } + + @Override + public UserAccount getUser(Bytes address) { + return null; + } + + @Override + public boolean contains(Bytes address) { + return false; + } + + @Override + public UserAccount getUser(Bytes address, long version) { + return null; + } + + + } + + private static class EmptyDataAccountSet implements DataAccountQuery{ + + @Override + public AccountHeader[] getAccounts(int fromIndex, int count) { + return null; + } + + @Override + public HashDigest getRootHash() { + return null; + } + + @Override + public long getTotalCount() { + return 0; + } + + @Override + public MerkleProof getProof(Bytes address) { + return null; + } + + @Override + public DataAccount getDataAccount(Bytes address) { + return null; + } + + @Override + public DataAccount getDataAccount(Bytes address, long version) { + return null; + } + + } + + private static class EmptyContractAccountSet implements ContractAccountQuery{ + + @Override + public AccountHeader[] getAccounts(int fromIndex, int count) { + return null; + } + + @Override + public HashDigest getRootHash() { + return null; + } + + @Override + public long getTotalCount() { + return 0; + } + + @Override + public MerkleProof getProof(Bytes address) { + return null; + } + + @Override + public boolean contains(Bytes address) { + return false; + } + + @Override + public ContractAccount getContract(Bytes address) { + return null; + } + + @Override + public ContractAccount getContract(Bytes address, long version) { + return null; + } + } +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/FullPermissionedSecurityManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/FullPermissionedSecurityManager.java new file mode 100644 index 00000000..c217ee34 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/FullPermissionedSecurityManager.java @@ -0,0 +1,98 @@ +package com.jd.blockchain.ledger.core; + +import java.util.Set; + +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSecurityException; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.utils.Bytes; + +class FullPermissionedSecurityManager implements LedgerSecurityManager { + + public static final FullPermissionedSecurityManager INSTANCE = new FullPermissionedSecurityManager(); + + @Override + public SecurityPolicy createSecurityPolicy(Set endpoints, Set nodes) { + return new FullPermissionedPolicy(endpoints, nodes); + } + + private static class FullPermissionedPolicy implements SecurityPolicy { + + private Set endpoints; + private Set nodes; + + public FullPermissionedPolicy(Set endpoints, Set nodes) { + this.endpoints = endpoints; + this.nodes = nodes; + } + + @Override + public Set getEndpoints() { + return endpoints; + } + + @Override + public Set getNodes() { + return nodes; + } + + @Override + public boolean isEndpointEnable(LedgerPermission permission, MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public boolean isEndpointEnable(TransactionPermission permission, MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public boolean isNodeEnable(LedgerPermission permission, MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public boolean isNodeEnable(TransactionPermission permission, MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public void checkEndpointPermission(LedgerPermission permission, MultiIDsPolicy midPolicy) + throws LedgerSecurityException { + } + + @Override + public void checkEndpointPermission(TransactionPermission permission, MultiIDsPolicy midPolicy) + throws LedgerSecurityException { + } + + @Override + public void checkNodePermission(LedgerPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { + } + + @Override + public void checkNodePermission(TransactionPermission permission, MultiIDsPolicy midPolicy) + throws LedgerSecurityException { + } + + @Override + public boolean isEndpointValid(MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public boolean isNodeValid(MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public void checkEndpointValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException { + } + + @Override + public void checkNodeValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException { + } + + } + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index 1d2c1023..c4961b12 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -108,7 +108,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, public UserRolesSettings getUserRoles() { return userRoles; } - + @Override public LedgerAdminInfo getAdminInfo() { return this; @@ -392,7 +392,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, return; } participants.cancel(); - metadata = new LedgerMetadataInfo(origMetadata); + metadata =origMetadata == null ? new LedgerMetadataInfo() : new LedgerMetadataInfo(origMetadata); } public static class LedgerMetadataInfo implements LedgerMetadata_V2 { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index 8c503494..0f506716 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -9,14 +9,12 @@ import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.SecurityInitSettings; import com.jd.blockchain.ledger.TransactionBuilder; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.KVStorageService; import com.jd.blockchain.transaction.SignatureUtils; import com.jd.blockchain.transaction.TxBuilder; @@ -24,6 +22,14 @@ import com.jd.blockchain.transaction.TxRequestBuilder; public class LedgerInitializer { + private static final FullPermissionedSecurityManager FULL_PERMISSION_SECURITY_MANAGER = new FullPermissionedSecurityManager(); + + private static final LedgerDataQuery EMPTY_LEDGER_DATA_QUERY = new EmptyLedgerDataset(); + + private static final OperationHandleRegisteration DEFAULT_OP_HANDLE_REG = new DefaultOperationHandleRegisteration(); + + private LedgerService EMPTY_LEDGERS = new LedgerManager(); + private LedgerInitSetting initSetting; private TransactionContent initTxContent; @@ -36,6 +42,8 @@ public class LedgerInitializer { private volatile boolean canceled = false; + private TransactionBatchResultHandle txResultsHandle; + /** * 初始化生成的账本hash;
        * @@ -115,7 +123,7 @@ public class LedgerInitializer { throw new LedgerInitException("The ledger has been canceled!"); } committed = true; - this.ledgerEditor.commit(); + this.txResultsHandle.commit(); } public void cancel() { @@ -148,18 +156,13 @@ public class LedgerInitializer { TransactionRequest txRequest = txReqBuilder.buildRequest(); - LedgerTransactionContext txCtx = ledgerEditor.newTransaction(txRequest); - Operation[] ops = txRequest.getTransactionContent().getOperations(); - // 注册用户; 注:第一个操作是 LedgerInitOperation; - // TODO:暂时只支持注册用户的初始化操作; - for (int i = 1; i < ops.length; i++) { - UserRegisterOperation userRegOP = (UserRegisterOperation) ops[i]; - txCtx.getDataset().getUserAccountSet().register(userRegOP.getUserID().getAddress(), - userRegOP.getUserID().getPubKey()); - } + TransactionBatchProcessor txProcessor = new TransactionBatchProcessor(FULL_PERMISSION_SECURITY_MANAGER, + ledgerEditor, EMPTY_LEDGER_DATA_QUERY, DEFAULT_OP_HANDLE_REG, EMPTY_LEDGERS); - txCtx.commit(TransactionState.SUCCESS, null); + txProcessor.schedule(txRequest); - return ledgerEditor.prepare(); + txResultsHandle = txProcessor.prepare(); + return txResultsHandle.getBlock(); } + } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 32053225..55c9d6d6 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -109,16 +109,6 @@ class LedgerRepositoryImpl implements LedgerRepository { return latestState.block; } -// private LedgerState getLatestState() { -// LedgerState state = latestState; -// if (state == null) { -// LedgerBlock latestBlock = innerGetBlock(innerGetLatestBlockHeight()); -// state = new LedgerState(latestBlock); -// latestState = state; -// } -// return state; -// } - /** * 重新检索加载最新的状态; * @@ -264,20 +254,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public TransactionSet getTransactionSet(LedgerBlock block) { long height = getLatestBlockHeight(); -// TransactionSet transactionSet = null; if (height == block.getHeight()) { -// // 缓存最近一个区块的数据; -// LedgerState state = getLatestState(); -// transactionSet = state.transactionSet; -// if (transactionSet == null) { -// LedgerAdminInfo adminAccount = getAdminInfo(block); -// transactionSet = loadTransactionSet(block.getTransactionSetHash(), -// adminAccount.getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, -// true); -// state.transactionSet = transactionSet; -// } -// return transactionSet; - // 从缓存中返回最新区块的数据集; return latestState.getTransactionSet(); } @@ -290,18 +267,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public LedgerAdminDataset getAdminInfo(LedgerBlock block) { long height = getLatestBlockHeight(); -// LedgerAdminDataset adminAccount = null; if (height == block.getHeight()) { -// // 缓存读; -// LedgerState state = getLatestState(); -// adminAccount = state.adminAccount; -// if (adminAccount == null) { -// adminAccount = new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, -// versioningStorage, true); -// state.adminAccount = adminAccount; -// } -// return adminAccount; - return latestState.getAdminDataset(); } @@ -315,20 +281,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public UserAccountQuery getUserAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); -// UserAccountSet userAccountSet = null; if (height == block.getHeight()) { -// // 缓存读; -// LedgerState state = getLatestState(); -// userAccountSet = state.userAccountSet; -// if (userAccountSet == null) { -// LedgerAdminDataset adminAccount = getAdminInfo(block); -// userAccountSet = loadUserAccountSet(block.getUserAccountSetHash(), -// adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, -// versioningStorage, true); -// state.userAccountSet = userAccountSet; -// } -// return userAccountSet; - return latestState.getUserAccountSet(); } LedgerAdminDataset adminAccount = getAdminInfo(block); @@ -343,20 +296,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public DataAccountQuery getDataAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); -// DataAccountSet dataAccountSet = null; if (height == block.getHeight()) { -// // 缓存读; -// LedgerState state = getLatestState(); -// dataAccountSet = state.dataAccountSet; -// if (dataAccountSet == null) { -// LedgerAdminDataset adminAccount = getAdminInfo(block); -// dataAccountSet = loadDataAccountSet(block.getDataAccountSetHash(), -// adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, -// versioningStorage, true); -// state.dataAccountSet = dataAccountSet; -// } -// return dataAccountSet; - return latestState.getDataAccountSet(); } @@ -372,20 +312,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public ContractAccountQuery getContractAccountSet(LedgerBlock block) { long height = getLatestBlockHeight(); -// ContractAccountSet contractAccountSet = null; if (height == block.getHeight()) { -// // 缓存读; -// LedgerState state = getLatestState(); -// contractAccountSet = state.contractAccountSet; -// if (contractAccountSet == null) { -// LedgerAdminDataset adminAccount = getAdminInfo(block); -// contractAccountSet = loadContractAccountSet(block.getContractAccountSetHash(), -// adminAccount.getPreviousSetting().getCryptoSetting(), keyPrefix, exPolicyStorage, -// versioningStorage, true); -// state.contractAccountSet = contractAccountSet; -// } -// return contractAccountSet; - return latestState.getContractAccountSet(); } @@ -401,17 +328,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public LedgerDataset getDataSet(LedgerBlock block) { long height = getLatestBlockHeight(); -// LedgerDataSet ledgerDataSet = null; if (height == block.getHeight()) { -// // 缓存读; -// LedgerState state = getLatestState(); -// ledgerDataSet = state.ledgerDataSet; -// if (ledgerDataSet == null) { -// ledgerDataSet = innerDataSet(block); -// state.ledgerDataSet = ledgerDataSet; -// } -// return ledgerDataSet; - return latestState.getLedgerDataset(); } @@ -463,15 +380,10 @@ class LedgerRepositoryImpl implements LedgerRepository { } static Bytes encodeLedgerIndexKey(HashDigest ledgerHash) { - // return LEDGER_PREFIX + Base58Utils.encode(ledgerHash.toBytes()); - // return new Bytes(ledgerHash.toBytes()).concatTo(LEDGER_PREFIX); return LEDGER_PREFIX.concat(ledgerHash); } static Bytes encodeBlockStorageKey(HashDigest blockHash) { - // String key = ByteArray.toBase58(blockHash.toBytes()); - // return BLOCK_PREFIX + key; - return BLOCK_PREFIX.concat(blockHash); } @@ -483,29 +395,13 @@ class LedgerRepositoryImpl implements LedgerRepository { String usersetKeyPrefix = keyPrefix + USER_SET_PREFIX; String datasetKeyPrefix = keyPrefix + DATA_SET_PREFIX; String contractsetKeyPrefix = keyPrefix + CONTRACT_SET_PREFIX; - // String txsetKeyPrefix = keyPrefix + TRANSACTION_SET_PREFIX; - // UserAccountSet userAccountSet = new - // UserAccountSet(adminAccount.getSetting().getCryptoSetting(), - // PrefixAppender.prefix(USER_SET_PREFIX, ledgerExStorage), - // PrefixAppender.prefix(USER_SET_PREFIX, ledgerVerStorage), - // DEFAULT_ACCESS_POLICY); UserAccountSet userAccountSet = new UserAccountSet(adminAccount.getSettings().getCryptoSetting(), usersetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); - // DataAccountSet dataAccountSet = new - // DataAccountSet(adminAccount.getSetting().getCryptoSetting(), - // PrefixAppender.prefix(DATA_SET_PREFIX, ledgerExStorage), - // PrefixAppender.prefix(DATA_SET_PREFIX, ledgerVerStorage), - // DEFAULT_ACCESS_POLICY); DataAccountSet dataAccountSet = new DataAccountSet(adminAccount.getSettings().getCryptoSetting(), datasetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); - // ContractAccountSet contractAccountSet = new - // ContractAccountSet(adminAccount.getSetting().getCryptoSetting(), - // PrefixAppender.prefix(CONTRACT_SET_PREFIX, ledgerExStorage), - // PrefixAppender.prefix(CONTRACT_SET_PREFIX, ledgerVerStorage), - // DEFAULT_ACCESS_POLICY); ContractAccountSet contractAccountSet = new ContractAccountSet(adminAccount.getSettings().getCryptoSetting(), contractsetKeyPrefix, ledgerExStorage, ledgerVerStorage, DEFAULT_ACCESS_POLICY); @@ -517,10 +413,6 @@ class LedgerRepositoryImpl implements LedgerRepository { static TransactionSet newTransactionSet(LedgerSettings ledgerSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) { - // TransactionSet transactionSet = new - // TransactionSet(ledgerSetting.getCryptoSetting(), - // PrefixAppender.prefix(TRANSACTION_SET_PREFIX, ledgerExStorage), - // PrefixAppender.prefix(TRANSACTION_SET_PREFIX, ledgerVerStorage)); String txsetKeyPrefix = keyPrefix + TRANSACTION_SET_PREFIX; @@ -534,8 +426,6 @@ class LedgerRepositoryImpl implements LedgerRepository { LedgerAdminDataset adminAccount = new LedgerAdminDataset(dataSnapshot.getAdminAccountHash(), keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); -// CryptoSetting cryptoSetting = adminAccount.getPreviousSetting().getCryptoSetting(); - UserAccountSet userAccountSet = loadUserAccountSet(dataSnapshot.getUserAccountSetHash(), cryptoSetting, keyPrefix, ledgerExStorage, ledgerVerStorage, readonly); @@ -554,10 +444,6 @@ class LedgerRepositoryImpl implements LedgerRepository { static UserAccountSet loadUserAccountSet(HashDigest userAccountSetHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { - // return new UserAccountSet(userAccountSetHash, cryptoSetting, - // PrefixAppender.prefix(USER_SET_PREFIX, ledgerExStorage), - // PrefixAppender.prefix(USER_SET_PREFIX, ledgerVerStorage), readonly, - // DEFAULT_ACCESS_POLICY); String usersetKeyPrefix = keyPrefix + USER_SET_PREFIX; return new UserAccountSet(userAccountSetHash, cryptoSetting, usersetKeyPrefix, ledgerExStorage, @@ -567,10 +453,6 @@ class LedgerRepositoryImpl implements LedgerRepository { static DataAccountSet loadDataAccountSet(HashDigest dataAccountSetHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { - // return new DataAccountSet(dataAccountSetHash, cryptoSetting, - // PrefixAppender.prefix(DATA_SET_PREFIX, ledgerExStorage, - // PrefixAppender.prefix(DATA_SET_PREFIX, ledgerVerStorage), readonly, - // DEFAULT_ACCESS_POLICY); String datasetKeyPrefix = keyPrefix + DATA_SET_PREFIX; return new DataAccountSet(dataAccountSetHash, cryptoSetting, datasetKeyPrefix, ledgerExStorage, @@ -580,10 +462,6 @@ class LedgerRepositoryImpl implements LedgerRepository { static ContractAccountSet loadContractAccountSet(HashDigest contractAccountSetHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { - // return new ContractAccountSet(contractAccountSetHash, cryptoSetting, - // PrefixAppender.prefix(CONTRACT_SET_PREFIX, ledgerExStorage, - // PrefixAppender.prefix(CONTRACT_SET_PREFIX, ledgerVerStorage), readonly, - // DEFAULT_ACCESS_POLICY); String contractsetKeyPrefix = keyPrefix + CONTRACT_SET_PREFIX; return new ContractAccountSet(contractAccountSetHash, cryptoSetting, contractsetKeyPrefix, ledgerExStorage, @@ -592,9 +470,6 @@ class LedgerRepositoryImpl implements LedgerRepository { static TransactionSet loadTransactionSet(HashDigest txsetHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage, boolean readonly) { - // return new TransactionSet(txsetHash, cryptoSetting, - // PrefixAppender.prefix(TRANSACTION_SET_PREFIX, ledgerExStorage), - // PrefixAppender.prefix(TRANSACTION_SET_PREFIX, ledgerVerStorage), readonly); String txsetKeyPrefix = keyPrefix + TRANSACTION_SET_PREFIX; return new TransactionSet(txsetHash, cryptoSetting, txsetKeyPrefix, ledgerExStorage, ledgerVerStorage, diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java index 0a26de4c..e0987732 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java @@ -7,15 +7,17 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.LedgerSecurityException; +import com.jd.blockchain.ledger.ParticipantDataQuery; +import com.jd.blockchain.ledger.ParticipantDoesNotExistException; import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.UserDoesNotExistException; import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.utils.Bytes; /** @@ -36,17 +38,17 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private Map userRolesCache = new ConcurrentHashMap<>(); private Map rolesPrivilegeCache = new ConcurrentHashMap<>(); - public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserRolesSettings userRolesSettings) { + private ParticipantDataQuery participantsQuery; + private UserAccountQuery userAccountsQuery; + + public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserRolesSettings userRolesSettings, + ParticipantDataQuery participantsQuery, UserAccountQuery userAccountsQuery) { this.rolePrivilegeSettings = rolePrivilegeSettings; this.userRolesSettings = userRolesSettings; + this.participantsQuery = participantsQuery; + this.userAccountsQuery = userAccountsQuery; } - - - public static void initSecuritySettings(LedgerInitSetting initSettings, LedgerEditor editor) { - - } - - + @Override public SecurityPolicy createSecurityPolicy(Set endpoints, Set nodes) { Map endpointPrivilegeMap = new HashMap<>(); @@ -62,7 +64,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { nodePrivilegeMap.put(userAddress, userPrivileges); } - return new UserRolesSecurityPolicy(endpointPrivilegeMap, nodePrivilegeMap); + return new UserRolesSecurityPolicy(endpointPrivilegeMap, nodePrivilegeMap, participantsQuery, userAccountsQuery); } private UserRolesPrivileges getUserRolesPrivilegs(Bytes userAddress) { @@ -142,15 +144,22 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { */ private Map nodePrivilegeMap = new HashMap<>(); + private ParticipantDataQuery participantsQuery; + + private UserAccountQuery userAccountsQuery; + public UserRolesSecurityPolicy(Map endpointPrivilegeMap, - Map nodePrivilegeMap) { + Map nodePrivilegeMap, ParticipantDataQuery participantsQuery, + UserAccountQuery userAccountsQuery) { this.endpointPrivilegeMap = endpointPrivilegeMap; this.nodePrivilegeMap = nodePrivilegeMap; + this.participantsQuery = participantsQuery; + this.userAccountsQuery = userAccountsQuery; } @Override - public boolean isEnableToEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) { - if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + public boolean isEndpointEnable(LedgerPermission permission, MultiIDsPolicy midPolicy) { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { // 至少一个; for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { if (p.getLedgerPrivileges().isEnable(permission)) { @@ -158,7 +167,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } } return false; - } else if (MultiIdsPolicy.ALL == midPolicy) { + } else if (MultiIDsPolicy.ALL == midPolicy) { // 全部; for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { if (!p.getLedgerPrivileges().isEnable(permission)) { @@ -172,8 +181,8 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public boolean isEnableToEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) { - if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + public boolean isEndpointEnable(TransactionPermission permission, MultiIDsPolicy midPolicy) { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { // 至少一个; for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { if (p.getTransactionPrivileges().isEnable(permission)) { @@ -181,7 +190,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } } return false; - } else if (MultiIdsPolicy.ALL == midPolicy) { + } else if (MultiIDsPolicy.ALL == midPolicy) { // 全部; for (UserRolesPrivileges p : endpointPrivilegeMap.values()) { if (!p.getTransactionPrivileges().isEnable(permission)) { @@ -195,8 +204,8 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public boolean isEnableToNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) { - if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + public boolean isNodeEnable(LedgerPermission permission, MultiIDsPolicy midPolicy) { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { // 至少一个; for (UserRolesPrivileges p : nodePrivilegeMap.values()) { if (p.getLedgerPrivileges().isEnable(permission)) { @@ -204,7 +213,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } } return false; - } else if (MultiIdsPolicy.ALL == midPolicy) { + } else if (MultiIDsPolicy.ALL == midPolicy) { // 全部; for (UserRolesPrivileges p : nodePrivilegeMap.values()) { if (!p.getLedgerPrivileges().isEnable(permission)) { @@ -218,8 +227,8 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public boolean isEnableToNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) { - if (MultiIdsPolicy.AT_LEAST_ONE == midPolicy) { + public boolean isNodeEnable(TransactionPermission permission, MultiIDsPolicy midPolicy) { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { // 至少一个; for (UserRolesPrivileges p : nodePrivilegeMap.values()) { if (p.getTransactionPrivileges().isEnable(permission)) { @@ -227,7 +236,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } } return false; - } else if (MultiIdsPolicy.ALL == midPolicy) { + } else if (MultiIDsPolicy.ALL == midPolicy) { // 全部; for (UserRolesPrivileges p : nodePrivilegeMap.values()) { if (!p.getTransactionPrivileges().isEnable(permission)) { @@ -241,9 +250,9 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public void checkEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) + public void checkEndpointPermission(LedgerPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { - if (!isEnableToEndpoints(permission, midPolicy)) { + if (!isEndpointEnable(permission, midPolicy)) { throw new LedgerSecurityException(String.format( "The security policy [Permission=%s, Policy=%s] for endpoints rejected the current operation!", permission, midPolicy)); @@ -251,9 +260,9 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public void checkEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) + public void checkEndpointPermission(TransactionPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { - if (!isEnableToEndpoints(permission, midPolicy)) { + if (!isEndpointEnable(permission, midPolicy)) { throw new LedgerSecurityException(String.format( "The security policy [Permission=%s, Policy=%s] for endpoints rejected the current operation!", permission, midPolicy)); @@ -261,8 +270,9 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public void checkNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException { - if (!isEnableToNodes(permission, midPolicy)) { + public void checkNodePermission(LedgerPermission permission, MultiIDsPolicy midPolicy) + throws LedgerSecurityException { + if (!isNodeEnable(permission, midPolicy)) { throw new LedgerSecurityException(String.format( "The security policy [Permission=%s, Policy=%s] for nodes rejected the current operation!", permission, midPolicy)); @@ -270,9 +280,9 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { } @Override - public void checkNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) + public void checkNodePermission(TransactionPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { - if (!isEnableToNodes(permission, midPolicy)) { + if (!isNodeEnable(permission, midPolicy)) { throw new LedgerSecurityException(String.format( "The security policy [Permission=%s, Policy=%s] for nodes rejected the current operation!", permission, midPolicy)); @@ -289,6 +299,98 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { return nodePrivilegeMap.keySet(); } + @Override + public boolean isEndpointValid(MultiIDsPolicy midPolicy) { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (Bytes address : getEndpoints()) { + if (userAccountsQuery.contains(address)) { + return true; + } + } + return false; + } else if (MultiIDsPolicy.ALL == midPolicy) { + // 全部; + for (Bytes address : getEndpoints()) { + if (!userAccountsQuery.contains(address)) { + return false; + } + } + return true; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public boolean isNodeValid(MultiIDsPolicy midPolicy) { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (Bytes address : getNodes()) { + if (participantsQuery.contains(address)) { + return true; + } + } + return false; + } else if (MultiIDsPolicy.ALL == midPolicy) { + // 全部; + for (Bytes address : getNodes()) { + if (!participantsQuery.contains(address)) { + return false; + } + } + return true; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public void checkEndpointValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (Bytes address : getEndpoints()) { + if (userAccountsQuery.contains(address)) { + return; + } + } + throw new UserDoesNotExistException("All endpoint signers were not registered!"); + } else if (MultiIDsPolicy.ALL == midPolicy) { + // 全部; + for (Bytes address : getEndpoints()) { + if (!userAccountsQuery.contains(address)) { + throw new UserDoesNotExistException("The endpoint signer[" + address + "] was not registered!"); + } + } + return; + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + + @Override + public void checkNodeValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException { + if (MultiIDsPolicy.AT_LEAST_ONE == midPolicy) { + // 至少一个; + for (Bytes address : getNodes()) { + if (participantsQuery.contains(address)) { + return; + } + } + throw new ParticipantDoesNotExistException("All node signers were not registered as participant!"); + } else if (MultiIDsPolicy.ALL == midPolicy) { + // 全部; + for (Bytes address : getNodes()) { + if (!participantsQuery.contains(address)) { + throw new ParticipantDoesNotExistException( + "The node signer[" + address + "] was not registered as participant!"); + } + } + } else { + throw new IllegalArgumentException("Unsupported MultiIdsPolicy[" + midPolicy + "]!"); + } + } + } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIDsPolicy.java similarity index 74% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIDsPolicy.java index 974eb203..20f1caab 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIdsPolicy.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MultiIDsPolicy.java @@ -1,12 +1,12 @@ package com.jd.blockchain.ledger.core; /** - * 多身份的权限校验策略; + * 多重身份的校验策略; * * @author huanghaiquan * */ -public enum MultiIdsPolicy { +public enum MultiIDsPolicy { /** * 至少有一个都能通过; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java index 2953f945..1b783eb0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandleRegisteration.java @@ -1,7 +1,9 @@ package com.jd.blockchain.ledger.core; +import com.jd.blockchain.ledger.Operation; + public interface OperationHandleRegisteration { - OperationHandle getHandle(Class operationType); + OperationHandle getHandle(Class operationType); } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java index 78b85564..17d487dd 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SecurityPolicy.java @@ -30,6 +30,22 @@ public interface SecurityPolicy { */ Set getNodes(); + /** + * 终端身份是否合法; + * + * @param midPolicy + * @return + */ + boolean isEndpointValid(MultiIDsPolicy midPolicy); + + /** + * 节点身份是否合法; + * + * @param midPolicy + * @return + */ + boolean isNodeValid(MultiIDsPolicy midPolicy); + /** * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        * @@ -37,7 +53,7 @@ public interface SecurityPolicy { * @param midPolicy 针对多个签名用户的权限策略; * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; */ - boolean isEnableToEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy); + boolean isEndpointEnable(LedgerPermission permission, MultiIDsPolicy midPolicy); /** * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        @@ -46,7 +62,7 @@ public interface SecurityPolicy { * @param midPolicy 针对多个签名用户的权限策略; * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; */ - boolean isEnableToEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy); + boolean isEndpointEnable(TransactionPermission permission, MultiIDsPolicy midPolicy); /** * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        @@ -55,7 +71,7 @@ public interface SecurityPolicy { * @param midPolicy 针对多个签名用户的权限策略; * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; */ - boolean isEnableToNodes(LedgerPermission permission, MultiIdsPolicy midPolicy); + boolean isNodeEnable(LedgerPermission permission, MultiIDsPolicy midPolicy); /** * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        @@ -64,7 +80,23 @@ public interface SecurityPolicy { * @param midPolicy 针对多个签名用户的权限策略; * @return 返回 true 表示获得授权; 返回 false 表示未获得授权; */ - boolean isEnableToNodes(TransactionPermission permission, MultiIdsPolicy midPolicy); + boolean isNodeEnable(TransactionPermission permission, MultiIDsPolicy midPolicy); + + /** + * 检查终端身份的合法性; + * + * @param midPolicy + * @throws LedgerSecurityException + */ + void checkEndpointValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException; + + /** + * 检查节点身份的合法性; + * + * @param midPolicy + * @throws LedgerSecurityException + */ + void checkNodeValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException; /** * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        @@ -74,7 +106,7 @@ public interface SecurityPolicy { * @param midPolicy 针对多个签名用户的权限策略; * @throws LedgerSecurityException */ - void checkEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + void checkEndpointPermission(LedgerPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException; /** * 检查签署交易的终端用户(来自{@link TransactionRequest#getEndpointSignatures()})是否被授权了参数指定的权限;
        @@ -84,7 +116,8 @@ public interface SecurityPolicy { * @param midPolicy * @throws LedgerSecurityException */ - void checkEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + void checkEndpointPermission(TransactionPermission permission, MultiIDsPolicy midPolicy) + throws LedgerSecurityException; /** * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        @@ -94,7 +127,7 @@ public interface SecurityPolicy { * @param midPolicy * @throws LedgerSecurityException */ - void checkNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + void checkNodePermission(LedgerPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException; /** * 检查签署交易的节点参与方(来自{@link TransactionRequest#getNodeSignatures()})是否被授权了参数指定的权限;
        @@ -104,6 +137,6 @@ public interface SecurityPolicy { * @param midPolicy * @throws LedgerSecurityException */ - void checkNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException; + void checkNodePermission(TransactionPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index 9a2ddcef..cab2a4e2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -46,7 +46,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { private LedgerEditor newBlockEditor; - private LedgerDataQuery previousBlockDataset; + private LedgerDataQuery ledgerQueryer; private OperationHandleRegisteration opHandles; @@ -60,15 +60,15 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { private TransactionBatchResult batchResult; /** - * @param newBlockEditor 新区块的数据编辑器; - * @param previousBlockDataset 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; - * @param opHandles 操作处理对象注册表; + * @param newBlockEditor 新区块的数据编辑器; + * @param ledgerQueryer 账本查询器,只包含新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; + * @param opHandles 操作处理对象注册表; */ public TransactionBatchProcessor(LedgerSecurityManager securityManager, LedgerEditor newBlockEditor, - LedgerDataQuery previousBlockDataset, OperationHandleRegisteration opHandles, LedgerService ledgerService) { + LedgerDataQuery ledgerQueryer, OperationHandleRegisteration opHandles, LedgerService ledgerService) { this.securityManager = securityManager; this.newBlockEditor = newBlockEditor; - this.previousBlockDataset = previousBlockDataset; + this.ledgerQueryer = ledgerQueryer; this.opHandles = opHandles; this.ledgerService = ledgerService; } @@ -95,7 +95,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { SecurityContext.setContextUsersPolicy(securityPolicy); // 安全校验; - checkSecurity(); + checkSecurity(securityPolicy); // 验证交易请求; checkRequest(reqExt); @@ -143,23 +143,26 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { } /** - * 验证交易的参与方的权限; + * 执行安全验证; */ - private void checkSecurity() { - SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - - // 验证当前交易请求的节点参与方是否具有权限; - securityPolicy.checkNodes(LedgerPermission.APPROVE_TX, MultiIdsPolicy.AT_LEAST_ONE); + private void checkSecurity(SecurityPolicy securityPolicy) { + // 验证节点和终端身份的合法性; + // 多重身份签署的必须全部身份都合法; + securityPolicy.checkEndpointValidity(MultiIDsPolicy.ALL); + securityPolicy.checkNodeValidity(MultiIDsPolicy.ALL); + + // 验证参与方节点是否具有核准交易的权限; + securityPolicy.checkNodePermission(LedgerPermission.APPROVE_TX, MultiIDsPolicy.AT_LEAST_ONE); } private void checkRequest(TransactionRequestExtension reqExt) { // TODO: 把验签和创建交易并行化; - checkTxContent(reqExt); + checkTxContentHash(reqExt); checkEndpointSignatures(reqExt); checkNodeSignatures(reqExt); } - private void checkTxContent(TransactionRequestExtension requestExt) { + private void checkTxContentHash(TransactionRequestExtension requestExt) { TransactionContent txContent = requestExt.getTransactionContent(); if (!TxBuilder.verifyTxContentHash(txContent, txContent.getHash())) { // 由于哈希校验失败,引发IllegalTransactionException,使外部调用抛弃此交易请求; @@ -169,44 +172,34 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { } } - private void checkEndpointSignatures(TransactionRequestExtension request) { + private void checkNodeSignatures(TransactionRequestExtension request) { TransactionContent txContent = request.getTransactionContent(); - Collection endpoints = request.getEndpoints(); - if (endpoints != null) { - for (Credential endpoint : endpoints) { - if (!previousBlockDataset.getUserAccountSet().contains(endpoint.getAddress())) { - throw new UserDoesNotExistException( - "The endpoint signer[" + endpoint.getAddress() + "] was not registered!"); - } - - if (!SignatureUtils.verifyHashSignature(txContent.getHash(), endpoint.getSignature().getDigest(), - endpoint.getPubKey())) { + Collection nodes = request.getNodes(); + if (nodes != null) { + for (Credential node : nodes) { + if (!SignatureUtils.verifyHashSignature(txContent.getHash(), node.getSignature().getDigest(), + node.getPubKey())) { // 由于签名校验失败,引发IllegalTransactionException,使外部调用抛弃此交易请求; throw new IllegalTransactionException( - String.format("Wrong transaction endpoint signature! --[Tx Hash=%s][Endpoint Signer=%s]!", - request.getTransactionContent().getHash(), endpoint.getAddress()), + String.format("Wrong transaction node signature! --[Tx Hash=%s][Node Signer=%s]!", + request.getTransactionContent().getHash(), node.getAddress()), TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); } } } } - private void checkNodeSignatures(TransactionRequestExtension request) { + private void checkEndpointSignatures(TransactionRequestExtension request) { TransactionContent txContent = request.getTransactionContent(); - Collection nodes = request.getNodes(); - if (nodes != null) { - for (Credential node : nodes) { - if (!previousBlockDataset.getAdminDataset().getParticipantDataset().contains(node.getAddress())) { - throw new ParticipantDoesNotExistException( - "The node signer[" + node.getAddress() + "] was not registered to the participant set!"); - } - - if (!SignatureUtils.verifyHashSignature(txContent.getHash(), node.getSignature().getDigest(), - node.getPubKey())) { + Collection endpoints = request.getEndpoints(); + if (endpoints != null) { + for (Credential endpoint : endpoints) { + if (!SignatureUtils.verifyHashSignature(txContent.getHash(), endpoint.getSignature().getDigest(), + endpoint.getPubKey())) { // 由于签名校验失败,引发IllegalTransactionException,使外部调用抛弃此交易请求; throw new IllegalTransactionException( - String.format("Wrong transaction node signature! --[Tx Hash=%s][Node Signer=%s]!", - request.getTransactionContent().getHash(), node.getAddress()), + String.format("Wrong transaction endpoint signature! --[Tx Hash=%s][Endpoint Signer=%s]!", + request.getTransactionContent().getHash(), endpoint.getAddress()), TransactionState.IGNORED_BY_WRONG_CONTENT_SIGNATURE); } } @@ -236,14 +229,14 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { // assert; Instance of operation are one of User related operations or // DataAccount related operations; OperationHandle hdl = opHandles.getHandle(operation.getClass()); - hdl.process(operation, dataset, request, previousBlockDataset, this, ledgerService); + hdl.process(operation, dataset, request, ledgerQueryer, this, ledgerService); } }; OperationHandle opHandle; int opIndex = 0; for (Operation op : ops) { opHandle = opHandles.getHandle(op.getClass()); - BytesValue opResult = opHandle.process(op, dataset, request, previousBlockDataset, handleContext, + BytesValue opResult = opHandle.process(op, dataset, request, ledgerQueryer, handleContext, ledgerService); if (opResult != null) { operationResults.add(new OperationResultData(opIndex, opResult)); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index 900b6f28..928997fd 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -43,8 +43,10 @@ public class TransactionEngineImpl implements TransactionEngine { LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); LedgerAdminDataQuery previousAdminDataset = previousBlockDataset.getAdminDataset(); - LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getAdminInfo().getRolePrivileges(), - previousAdminDataset.getAdminInfo().getUserRoles()); + LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl( + previousAdminDataset.getAdminInfo().getRolePrivileges(), + previousAdminDataset.getAdminInfo().getUserRoles(), previousAdminDataset.getParticipantDataset(), + previousBlockDataset.getUserAccountSet()); batch = new InnerTransactionBatchProcessor(ledgerHash, securityManager, newBlockEditor, previousBlockDataset, opHdlRegs, ledgerService, ledgerBlock.getHeight()); batchs.put(ledgerHash, batch); @@ -75,8 +77,8 @@ public class TransactionEngineImpl implements TransactionEngine { * @param opHandles 操作处理对象注册表; */ public InnerTransactionBatchProcessor(HashDigest ledgerHash, LedgerSecurityManager securityManager, - LedgerEditor newBlockEditor, LedgerDataQuery previousBlockDataset, OperationHandleRegisteration opHandles, - LedgerService ledgerService, long blockHeight) { + LedgerEditor newBlockEditor, LedgerDataQuery previousBlockDataset, + OperationHandleRegisteration opHandles, LedgerService ledgerService, long blockHeight) { super(securityManager, newBlockEditor, previousBlockDataset, opHandles, ledgerService); this.ledgerHash = ledgerHash; this.blockHeight = blockHeight; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java index 58444d3b..36a2a0ae 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java @@ -7,7 +7,7 @@ import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -49,7 +49,7 @@ public abstract class AbstractLedgerOperationHandle impleme OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(TransactionPermission.DIRECT_OPERATION, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(TransactionPermission.DIRECT_OPERATION, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; @SuppressWarnings("unchecked") diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java index ccbce284..af1d95b4 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java @@ -15,7 +15,7 @@ import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerQueryService; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -35,7 +35,7 @@ public abstract class AbtractContractEventSendOperationHandle implements Operati LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(TransactionPermission.CONTRACT_OPERATION, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(TransactionPermission.CONTRACT_OPERATION, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; ContractEventSendOperation contractOP = (ContractEventSendOperation) op; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java index 9d975227..e054a1e2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java @@ -5,7 +5,7 @@ import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; @@ -26,7 +26,7 @@ public class ContractCodeDeployOperationHandle extends AbstractLedgerOperationHa // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(LedgerPermission.UPGRADE_CONTRACT, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(LedgerPermission.UPGRADE_CONTRACT, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; ContractCodeDeployOperation contractOP = (ContractCodeDeployOperation) op; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index 6fd498e2..6661a6d5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -9,7 +9,7 @@ import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; @@ -27,7 +27,7 @@ public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHand OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; DataAccount account = newBlockDataset.getDataAccountSet().getDataAccount(kvWriteOp.getAccountAddress()); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java index 2e0f872e..0c031d5d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java @@ -6,7 +6,7 @@ import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; @@ -25,7 +25,7 @@ public class DataAccountRegisterOperationHandle extends AbstractLedgerOperationH // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(LedgerPermission.REGISTER_DATA_ACCOUNT, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(LedgerPermission.REGISTER_DATA_ACCOUNT, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; DataAccountRegisterOperation dataAccountRegOp = (DataAccountRegisterOperation) op; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java new file mode 100644 index 00000000..39862be0 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java @@ -0,0 +1,27 @@ +package com.jd.blockchain.ledger.core.handles; + +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.core.LedgerDataQuery; +import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.OperationHandle; +import com.jd.blockchain.ledger.core.OperationHandleContext; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; + +public class LedgerInitOperationHandle implements OperationHandle { + + @Override + public Class getOperationType() { + return LedgerInitOperation.class; + } + + @Override + public BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, + LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + // 对初始化操作不需要做任何处理; + return null; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java index 7f3c51bb..3ae02b4e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java @@ -8,7 +8,7 @@ import com.jd.blockchain.ledger.RolesConfigureOperation.RolePrivilegeEntry; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; @@ -25,7 +25,7 @@ public class RolesConfigureOperationHandle extends AbstractLedgerOperationHandle OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(LedgerPermission.CONFIGURE_ROLES, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(LedgerPermission.CONFIGURE_ROLES, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; RolePrivilegeEntry[] rpcfgs = operation.getRoles(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java index b5fee9bb..b4747dbf 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java @@ -13,7 +13,7 @@ import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; @@ -30,7 +30,7 @@ public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle< OperationHandleContext handleContext, LedgerService ledgerService) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); - securityPolicy.checkEndpoints(LedgerPermission.CONFIGURE_ROLES, MultiIdsPolicy.AT_LEAST_ONE); + securityPolicy.checkEndpointPermission(LedgerPermission.CONFIGURE_ROLES, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java index 6307968d..3b8f043d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java @@ -6,7 +6,7 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; @@ -24,7 +24,7 @@ public class UserRegisterOperationHandle extends AbstractLedgerOperationHandle endpoints = new HashMap<>(); @@ -146,16 +152,16 @@ public class LedgerSecurityManagerTest { // 终端节点有 ADMIN 和 OPERATOR 两种角色的合并权限; if (p == LedgerPermission.REGISTER_USER || p == LedgerPermission.REGISTER_DATA_ACCOUNT || p == LedgerPermission.WRITE_DATA_ACCOUNT) { - assertTrue(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertTrue(policy.isEndpointEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } else { - assertFalse(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertFalse(policy.isEndpointEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } if (p == LedgerPermission.APPROVE_TX) { // 共识参与方只有 PLATFORM 角色的权限:核准交易; - assertTrue(policy.isEnableToNodes(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertTrue(policy.isNodeEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } else { - assertFalse(policy.isEnableToNodes(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertFalse(policy.isNodeEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } } @@ -163,12 +169,12 @@ public class LedgerSecurityManagerTest { for (TransactionPermission p : transactionPermissions) { // 终端节点有 ADMIN 和 OPERATOR 两种角色的合并权限; if (p == TransactionPermission.DIRECT_OPERATION || p == TransactionPermission.CONTRACT_OPERATION) { - assertTrue(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertTrue(policy.isEndpointEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } else { - assertFalse(policy.isEnableToEndpoints(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertFalse(policy.isEndpointEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } - assertFalse(policy.isEnableToNodes(p, MultiIdsPolicy.AT_LEAST_ONE)); + assertFalse(policy.isNodeEnable(p, MultiIDsPolicy.AT_LEAST_ONE)); } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java index 8e7b68b5..1f7dbbc1 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java @@ -122,10 +122,10 @@ public class TransactionBatchProcessorTest { LedgerSecurityManager securityManager = Mockito.mock(LedgerSecurityManager.class); SecurityPolicy securityPolicy = Mockito.mock(SecurityPolicy.class); - when(securityPolicy.isEnableToEndpoints(any(LedgerPermission.class), any())).thenReturn(true); - when(securityPolicy.isEnableToEndpoints(any(TransactionPermission.class), any())).thenReturn(true); - when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); - when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isEndpointEnable(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEndpointEnable(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isNodeEnable(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isNodeEnable(any(TransactionPermission.class), any())).thenReturn(true); when(securityManager.createSecurityPolicy(any(), any())).thenReturn(securityPolicy); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index c1ac727f..e8769ee4 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -45,7 +45,7 @@ import com.jd.blockchain.ledger.core.LedgerEditor; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.LedgerSecurityManager; -import com.jd.blockchain.ledger.core.MultiIdsPolicy; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; import com.jd.blockchain.service.TransactionBatchResultHandle; @@ -676,44 +676,61 @@ public class LedgerPerformanceTest { } @Override - public boolean isEnableToEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) { + public boolean isEndpointEnable(LedgerPermission permission, MultiIDsPolicy midPolicy) { return true; } @Override - public boolean isEnableToEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) { + public boolean isEndpointEnable(TransactionPermission permission, MultiIDsPolicy midPolicy) { return true; } @Override - public boolean isEnableToNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) { + public boolean isNodeEnable(LedgerPermission permission, MultiIDsPolicy midPolicy) { return true; } @Override - public boolean isEnableToNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) { - // TODO Auto-generated method stub - return false; + public boolean isNodeEnable(TransactionPermission permission, MultiIDsPolicy midPolicy) { + return true; } @Override - public void checkEndpoints(LedgerPermission permission, MultiIdsPolicy midPolicy) + public void checkEndpointPermission(LedgerPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { } @Override - public void checkEndpoints(TransactionPermission permission, MultiIdsPolicy midPolicy) + public void checkEndpointPermission(TransactionPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { } @Override - public void checkNodes(LedgerPermission permission, MultiIdsPolicy midPolicy) throws LedgerSecurityException { + public void checkNodePermission(LedgerPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { } @Override - public void checkNodes(TransactionPermission permission, MultiIdsPolicy midPolicy) + public void checkNodePermission(TransactionPermission permission, MultiIDsPolicy midPolicy) throws LedgerSecurityException { } + @Override + public boolean isEndpointValid(MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public boolean isNodeValid(MultiIDsPolicy midPolicy) { + return true; + } + + @Override + public void checkEndpointValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException { + } + + @Override + public void checkNodeValidity(MultiIDsPolicy midPolicy) throws LedgerSecurityException { + } + } } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 26f54dc5..d062ea23 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -451,10 +451,10 @@ public class MockerNodeContext implements BlockchainQueryService { LedgerSecurityManager securityManager = Mockito.mock(LedgerSecurityManager.class); SecurityPolicy securityPolicy = Mockito.mock(SecurityPolicy.class); - when(securityPolicy.isEnableToEndpoints(any(LedgerPermission.class), any())).thenReturn(true); - when(securityPolicy.isEnableToEndpoints(any(TransactionPermission.class), any())).thenReturn(true); - when(securityPolicy.isEnableToNodes(any(LedgerPermission.class), any())).thenReturn(true); - when(securityPolicy.isEnableToNodes(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isEndpointEnable(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isEndpointEnable(any(TransactionPermission.class), any())).thenReturn(true); + when(securityPolicy.isNodeEnable(any(LedgerPermission.class), any())).thenReturn(true); + when(securityPolicy.isNodeEnable(any(TransactionPermission.class), any())).thenReturn(true); when(securityManager.createSecurityPolicy(any(), any())).thenReturn(securityPolicy); From d8be1b4666bedd366c8bce5c29b3b98e3c6367b7 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Sun, 8 Sep 2019 23:10:36 +0800 Subject: [PATCH 084/124] Completed init security settings by ledger init properties file; --- .../ledger/core/LedgerInitializer.java | 63 +++- .../ledger/LedgerInitProperties.java | 77 ++++- .../blockchain/ledger/SecurityInitData.java | 44 ++- .../blockchain/ledger/UserAuthInitData.java | 40 +++ .../transaction/LedgerInitData.java | 18 +- .../ledger/LedgerInitPropertiesTest.java | 12 +- .../ledger/SecurityInitDataTest.java | 8 +- .../src/test/resources/ledger.init | 6 + .../intgr/consensus/ConsensusTest.java | 5 +- .../intgr/perf/GlobalPerformanceTest.java | 10 +- .../intgr/perf/LedgerInitializeTest.java | 11 +- .../intgr/perf/LedgerInitializeWebTest.java | 20 +- .../com/jd/blockchain/intgr/perf/Utils.java | 8 +- .../initializer/LedgerInitializeTest.java | 9 +- .../LedgerInitializeWeb4SingleStepsTest.java | 28 +- source/tools/pom.xml | 2 +- .../tools/initializer/LedgerInitCommand.java | 4 +- .../tools/initializer/LedgerInitProcess.java | 7 +- .../web/LedgerInitConfiguration.java | 281 ++++++++++++++++++ .../web/LedgerInitializeWebController.java | 182 ++++-------- .../mocker/MockerLedgerInitializer.java | 58 ++-- .../blockchain/mocker/MockerNodeContext.java | 8 +- .../jd/blockchain/utils/PropertiesUtils.java | 16 + .../com/jd/blockchain/utils/StringUtils.java | 4 + 24 files changed, 663 insertions(+), 258 deletions(-) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java create mode 100644 source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index 0f506716..ca159135 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -8,12 +8,18 @@ import com.jd.blockchain.ledger.BlockchainIdentityData; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitException; +import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.RoleInitSettings; +import com.jd.blockchain.ledger.RolesConfigureOperation; import com.jd.blockchain.ledger.SecurityInitSettings; import com.jd.blockchain.ledger.TransactionBuilder; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.UserAuthInitSettings; +import com.jd.blockchain.ledger.UserAuthorizeOperation; +import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.KVStorageService; import com.jd.blockchain.transaction.SignatureUtils; @@ -74,23 +80,64 @@ public class LedgerInitializer { return null; } - public static LedgerInitializer create(LedgerInitSetting initSetting) { - return create(initSetting, createDefaultSecurityInitSettings()); - } +// public static LedgerInitializer create(LedgerInitSetting initSetting) { +// return create(initSetting, createDefaultSecurityInitSettings()); +// } public static LedgerInitializer create(LedgerInitSetting initSetting, SecurityInitSettings securityInitSettings) { - // 生成初始化交易; - TransactionBuilder initTxBuilder = new TxBuilder(null);// 账本初始化交易的账本 hash 为 null; + // 生成创世交易; + TransactionContent initTxContent = buildGenesisTransaction(initSetting, securityInitSettings); + + return new LedgerInitializer(initSetting, initTxContent); + } + + /** + * 根据初始化配置,生成创始交易; + *

        + * + * “创世交易”按顺序由以下操作组成:
        + * (1) 账本初始化 {@link LedgerInitOperation}:此操作仅用于锚定了原始的交易配置,对应的 + * {@link OperationHandle} 执行空操作,由“创世交易”其余的操作来表达对账本的实际修改;
        + * (2) 注册用户 {@link UserRegisterOperation}:有一项或者多项;
        + * (3) 配置角色 {@link RolesConfigureOperation}:有一项或者多项;
        + * (4) 授权用户 {@link UserAuthorizeOperation}:有一项或者多项;
        + * + * @param initSetting + * @param securityInitSettings + * @return + */ + public static TransactionContent buildGenesisTransaction(LedgerInitSetting initSetting, + SecurityInitSettings securityInitSettings) { + // 账本初始化交易的账本 hash 为 null; + TransactionBuilder initTxBuilder = new TxBuilder(null); + + // 定义账本初始化操作; initTxBuilder.ledgers().create(initSetting); + + // TODO: 注册参与方; 目前由 LedgerInitSetting 定义,在 LedgerAdminDataset 中解释执行; + + //  注册用户; for (ParticipantNode p : initSetting.getConsensusParticipants()) { // TODO:暂时只支持注册用户的初始化操作; BlockchainIdentity superUserId = new BlockchainIdentityData(p.getPubKey()); initTxBuilder.users().register(superUserId); } - // 账本初始化配置声明的创建时间来初始化交易时间戳;注:不能用本地时间,因为共识节点之间的本地时间系统不一致; - TransactionContent initTxContent = initTxBuilder.prepareContent(initSetting.getCreatedTime()); - return new LedgerInitializer(initSetting, initTxContent); + // 配置角色; + for (RoleInitSettings roleSettings : securityInitSettings.getRoles()) { + initTxBuilder.security().roles().configure(roleSettings.getRoleName()) + .enable(roleSettings.getLedgerPermissions()).enable(roleSettings.getTransactionPermissions()); + } + + // 授权用户; + for (UserAuthInitSettings userAuthSettings : securityInitSettings.getUserAuthorizations()) { + initTxBuilder.security().authorziations().forUser(userAuthSettings.getUserAddress()) + .authorize(userAuthSettings.getRoles()) + .setPolicy(userAuthSettings.getPolicy()); + } + + // 账本初始化配置声明的创建时间来初始化交易时间戳;注:不能用本地时间,因为共识节点之间的本地时间系统不一致; + return initTxBuilder.prepareContent(initSetting.getCreatedTime()); } public SignatureDigest signTransaction(PrivKey privKey) { diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java index f515eec5..e19977fe 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java @@ -15,6 +15,7 @@ import com.jd.blockchain.consts.Global; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.LedgerInitProperties.CryptoProperties; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.PropertiesUtils; import com.jd.blockchain.utils.StringUtils; @@ -72,6 +73,10 @@ public class LedgerInitProperties { // 密码服务提供者列表,以英文逗点“,”分隔;必须; public static final String CRYPTO_SERVICE_PROVIDERS = "crypto.service-providers"; + // 从存储中加载账本数据时,是否校验哈希;可选; + public static final String CRYPTO_VRIFY_HASH = "crypto.verify-hash"; + // 哈希算法; + public static final String CRYPTO_HASH_ALGORITHM = "crypto.hash-algorithm"; public static final String CRYPTO_SERVICE_PROVIDERS_SPLITTER = ","; @@ -81,13 +86,15 @@ public class LedgerInitProperties { private RoleInitData[] roles; - private List consensusParticipants = new ArrayList<>(); + private List consensusParticipants = new ArrayList<>(); private String consensusProvider; private Properties consensusConfig; - private String[] cryptoProviders; +// private String[] cryptoProviders; + + private CryptoProperties cryptoProperties = new CryptoProperties(); private long createdTime; @@ -115,7 +122,7 @@ public class LedgerInitProperties { return consensusParticipants.size(); } - public List getConsensusParticipants() { + public List getConsensusParticipants() { return consensusParticipants; } @@ -127,12 +134,15 @@ public class LedgerInitProperties { return consensusParticipants.toArray(participantNodes); } - public String[] getCryptoProviders() { - return cryptoProviders.clone(); + public CryptoProperties getCryptoProperties() { + return cryptoProperties; } - public void setCryptoProviders(String[] cryptoProviders) { - this.cryptoProviders = cryptoProviders; + public void setCryptoProperties(CryptoProperties cryptoProperties) { + if (cryptoProperties == null) { + cryptoProperties = new CryptoProperties(); + } + this.cryptoProperties = cryptoProperties; } /** @@ -141,8 +151,8 @@ public class LedgerInitProperties { * @param id 从 1 开始; 小于等于 {@link #getConsensusParticipantCount()}; * @return */ - public ConsensusParticipantConfig getConsensusParticipant(int id) { - for (ConsensusParticipantConfig p : consensusParticipants) { + public ParticipantProperties getConsensusParticipant(int id) { + for (ParticipantProperties p : consensusParticipants) { if (p.getId() == id) { return p; } @@ -159,7 +169,7 @@ public class LedgerInitProperties { this.ledgerSeed = ledgerSeed; } - public void addConsensusParticipant(ConsensusParticipantConfig participant) { + public void addConsensusParticipant(ParticipantProperties participant) { consensusParticipants.add(participant); } @@ -249,7 +259,14 @@ public class LedgerInitProperties { for (int i = 0; i < cryptoProviders.length; i++) { cryptoProviders[i] = cryptoProviders[i].trim(); } - initProps.cryptoProviders = cryptoProviders; + initProps.cryptoProperties.setProviders(cryptoProviders); + // 哈希校验选项; + boolean verifyHash = PropertiesUtils.getBooleanOptional(props, CRYPTO_VRIFY_HASH, false); + initProps.cryptoProperties.setVerifyHash(verifyHash); + // 哈希算法; + String hashAlgorithm = PropertiesUtils.getOptionalProperty(props, CRYPTO_HASH_ALGORITHM); + initProps.cryptoProperties.setHashAlgorithm(hashAlgorithm); + // 解析参与方节点列表; int partCount = getInt(PropertiesUtils.getRequiredProperty(props, PART_COUNT)); @@ -260,7 +277,7 @@ public class LedgerInitProperties { throw new IllegalArgumentException(String.format("Property[%s] is less than 4!", PART_COUNT)); } for (int i = 0; i < partCount; i++) { - ConsensusParticipantConfig parti = new ConsensusParticipantConfig(); + ParticipantProperties parti = new ParticipantProperties(); parti.setId(i); @@ -363,13 +380,47 @@ public class LedgerInitProperties { this.roles = roles; } + public static class CryptoProperties { + + private String[] providers; + + private boolean verifyHash; + + private String hashAlgorithm; + + public String[] getProviders() { + return providers; + } + + public void setProviders(String[] providers) { + this.providers = providers; + } + + public boolean isVerifyHash() { + return verifyHash; + } + + public void setVerifyHash(boolean verifyHash) { + this.verifyHash = verifyHash; + } + + public String getHashAlgorithm() { + return hashAlgorithm; + } + + public void setHashAlgorithm(String hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + } + + } + /** * 参与方配置信息; * * @author huanghaiquan * */ - public static class ConsensusParticipantConfig implements ParticipantNode { + public static class ParticipantProperties implements ParticipantNode { private int id; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java index a3d2e62a..626adef1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java @@ -1,34 +1,54 @@ package com.jd.blockchain.ledger; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.jd.blockchain.utils.Bytes; public class SecurityInitData implements SecurityInitSettings { - private List roles = new ArrayList(); + private Map roles = new LinkedHashMap<>(); + + private Map userAuthentications = new LinkedHashMap<>(); @Override public RoleInitData[] getRoles() { - return roles.toArray(new RoleInitData[roles.size()]); + return roles.values().toArray(new RoleInitData[roles.size()]); + } + + public int getRolesCount() { + return roles.size(); } public void setRoles(RoleInitData[] roles) { - List list = new ArrayList(); + Map newRoles = new LinkedHashMap<>(); for (RoleInitData r : roles) { - list.add(r); + newRoles.put(r.getRoleName(), r); } - this.roles = list; + this.roles = newRoles; } - public void add(String roleName, LedgerPermission[] ledgerPermissions, + public boolean containsRole(String roleName) { + return roles.containsKey(roleName); + } + + public void addRole(String roleName, LedgerPermission[] ledgerPermissions, TransactionPermission[] transactionPermissions) { RoleInitData roleInitData = new RoleInitData(roleName, ledgerPermissions, transactionPermissions); - roles.add(roleInitData); + roles.put(roleName, roleInitData); } @Override - public UserAuthInitSettings[] getUserAuthorizations() { - // TODO Auto-generated method stub - return null; + public UserAuthInitData[] getUserAuthorizations() { + return userAuthentications.values().toArray(new UserAuthInitData[userAuthentications.size()]); + } + + public void addUserAuthencation(Bytes address, String[] roles, RolesPolicy policy) { + UserAuthInitData userAuth = new UserAuthInitData(); + userAuth.setUserAddress(address); + userAuth.setRoles(roles); + userAuth.setPolicy(policy); + + userAuthentications.put(address, userAuth); } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java new file mode 100644 index 00000000..6866c991 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java @@ -0,0 +1,40 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.utils.Bytes; + +public class UserAuthInitData implements UserAuthInitSettings { + + private Bytes userAddress; + + private String[] roles; + + private RolesPolicy policy; + + public void setUserAddress(Bytes userAddress) { + this.userAddress = userAddress; + } + + public void setRoles(String[] roles) { + this.roles = roles; + } + + public void setPolicy(RolesPolicy policy) { + this.policy = policy; + } + + @Override + public Bytes getUserAddress() { + return userAddress; + } + + @Override + public String[] getRoles() { + return roles; + } + + @Override + public RolesPolicy getPolicy() { + return policy; + } + +} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java index 35215e53..6de96680 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java @@ -1,22 +1,22 @@ package com.jd.blockchain.transaction; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.utils.Bytes; -public class LedgerInitData implements LedgerInitSetting { +public class LedgerInitData implements LedgerInitSetting { private byte[] ledgerSeed; private ParticipantNode[] consensusParticipants; private CryptoSetting cryptoSetting; - + private String consensusProvider; private Bytes consensusSettings; - + private long createdTime; @Override @@ -55,11 +55,15 @@ public class LedgerInitData implements LedgerInitSetting { this.consensusSettings = consensusSettings; } + public void setConsensusSettings(byte[] consensusSettings) { + this.consensusSettings = new Bytes(consensusSettings); + } + @Override public String getConsensusProvider() { return consensusProvider; } - + public void setConsensusProvider(String consensusProvider) { this.consensusProvider = consensusProvider; } @@ -68,7 +72,7 @@ public class LedgerInitData implements LedgerInitSetting { public long getCreatedTime() { return createdTime; } - + public void setCreatedTime(long createdTime) { this.createdTime = createdTime; } diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java index c3f320b8..ea17f4d2 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/LedgerInitPropertiesTest.java @@ -23,7 +23,7 @@ import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.LedgerInitProperties; -import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; +import com.jd.blockchain.ledger.LedgerInitProperties.ParticipantProperties; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.RoleInitData; import com.jd.blockchain.ledger.RolesPolicy; @@ -145,7 +145,7 @@ public class LedgerInitPropertiesTest { assertEquals("com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider", initProps.getConsensusProvider()); - String[] cryptoProviders = initProps.getCryptoProviders(); + String[] cryptoProviders = initProps.getCryptoProperties().getProviders(); assertEquals(2, cryptoProviders.length); assertEquals("com.jd.blockchain.crypto.service.classic.ClassicCryptoService", cryptoProviders[0]); assertEquals("com.jd.blockchain.crypto.service.sm.SMCryptoService", cryptoProviders[1]); @@ -153,7 +153,7 @@ public class LedgerInitPropertiesTest { // 验证参与方信息; assertEquals(4, initProps.getConsensusParticipantCount()); - ConsensusParticipantConfig part0 = initProps.getConsensusParticipant(0); + ParticipantProperties part0 = initProps.getConsensusParticipant(0); assertEquals("jd.com", part0.getName()); PubKey pubKey0 = KeyGenUtils.decodePubKey("3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9"); assertEquals(pubKey0, part0.getPubKey()); @@ -163,19 +163,19 @@ public class LedgerInitPropertiesTest { assertArrayEquals(new String[] { "ADMIN", "MANAGER" }, part0.getRoles()); assertEquals(RolesPolicy.UNION, part0.getRolesPolicy()); - ConsensusParticipantConfig part1 = initProps.getConsensusParticipant(1); + ParticipantProperties part1 = initProps.getConsensusParticipant(1); assertEquals(false, part1.getInitializerAddress().isSecure()); PubKey pubKey1 = KeyGenUtils.decodePubKey("3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX"); assertEquals(pubKey1, part1.getPubKey()); assertArrayEquals(new String[] { "MANAGER" }, part1.getRoles()); assertEquals(RolesPolicy.UNION, part1.getRolesPolicy()); - ConsensusParticipantConfig part2 = initProps.getConsensusParticipant(2); + ParticipantProperties part2 = initProps.getConsensusParticipant(2); assertEquals("7VeRAr3dSbi1xatq11ZcF7sEPkaMmtZhV9shonGJWk9T4pLe", part2.getPubKey().toBase58()); assertArrayEquals(new String[] { "MANAGER" }, part2.getRoles()); assertEquals(RolesPolicy.UNION, part2.getRolesPolicy()); - ConsensusParticipantConfig part3 = initProps.getConsensusParticipant(3); + ParticipantProperties part3 = initProps.getConsensusParticipant(3); PubKey pubKey3 = KeyGenUtils.decodePubKey("3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"); assertEquals(pubKey3, part3.getPubKey()); assertArrayEquals(new String[] { "GUEST" }, part3.getRoles()); diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java index 6bf9d1f5..c573fcf6 100644 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java +++ b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/SecurityInitDataTest.java @@ -48,17 +48,17 @@ public class SecurityInitDataTest { SecurityInitData securityInitData = new SecurityInitData(); - securityInitData.add("DEFAULT", + securityInitData.addRole("DEFAULT", new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, new TransactionPermission[] { TransactionPermission.CONTRACT_OPERATION }); - securityInitData.add("ADMIN", + securityInitData.addRole("ADMIN", new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION }); - securityInitData.add("R1", + securityInitData.addRole("R1", new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, null); - securityInitData.add("R2", null, new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + securityInitData.addRole("R2", null, new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION }); String json = JSONSerializeUtils.serializeToJSON(securityInitData, true); diff --git a/source/ledger/ledger-model/src/test/resources/ledger.init b/source/ledger/ledger-model/src/test/resources/ledger.init index ebbd8872..97c68e85 100644 --- a/source/ledger/ledger-model/src/test/resources/ledger.init +++ b/source/ledger/ledger-model/src/test/resources/ledger.init @@ -61,6 +61,12 @@ consensus.conf=classpath:bftsmart.config crypto.service-providers=com.jd.blockchain.crypto.service.classic.ClassicCryptoService, \ com.jd.blockchain.crypto.service.sm.SMCryptoService +#从存储中加载账本数据时,是否校验哈希;可选; +crypto.verify-hash=true + +#哈希算法; +crypto.hash-algorithm=SHA256; + #参与方的个数,后续以 cons_parti.id 分别标识每一个参与方的配置; cons_parti.count=4 diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java index 05493cdb..7d9f8019 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java @@ -398,9 +398,8 @@ public class ConsensusTest { return invoker.start(); } - public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, - ConsensusSettings csProps) { - return controller.prepareLocalPermission(id, privKey, setting, csProps); + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties initProps) { + return controller.prepareLocalPermission(id, privKey, initProps); } public boolean consensusPermission(PrivKey privKey) { diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java index 516fb8d4..47ad09d8 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java @@ -186,9 +186,8 @@ public class GlobalPerformanceTest { LedgerInitProperties initSetting = loadInitSetting_integration(); Properties props = Utils.loadConsensusSetting(); ConsensusProvider csProvider = getConsensusProvider(); - ConsensusSettings csProps = csProvider.getSettingsFactory() - .getConsensusSettingsBuilder() - .createSettings(props, Utils.loadParticipantNodes()); + ConsensusSettings csProps = csProvider.getSettingsFactory().getConsensusSettingsBuilder().createSettings(props, + Utils.loadParticipantNodes()); // 启动服务器; NetworkAddress initAddr0 = initSetting.getConsensusParticipant(0).getInitializerAddress(); @@ -400,9 +399,8 @@ public class GlobalPerformanceTest { return invoker.start(); } - public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, - ConsensusSettings csProps) { - return controller.prepareLocalPermission(id, privKey, setting, csProps); + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties initProps) { + return controller.prepareLocalPermission(id, privKey, initProps); } public boolean consensusPermission(PrivKey privKey) { diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index 431c6596..e94fcfaa 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -203,19 +203,18 @@ public class LedgerInitializeTest { return invoker.start(); } - public AsyncCallback startInit(int currentId, PrivKey privKey, LedgerInitProperties setting, + public AsyncCallback startInit(int currentId, PrivKey privKey, LedgerInitProperties initProps, DBConnectionConfig dbConnConfig, Prompter prompter, boolean autoVerifyHash) { - CryptoConfig cryptoSetting = new CryptoConfig(); - cryptoSetting.setAutoVerifyHash(autoVerifyHash); - cryptoSetting.setHashAlgorithm(Crypto.getAlgorithm("SHA256")); + initProps.getCryptoProperties().setVerifyHash(autoVerifyHash); + initProps.getCryptoProperties().setHashAlgorithm("SHA256"); - partiKey = new AsymmetricKeypair(setting.getConsensusParticipant(0).getPubKey(), privKey); + partiKey = new AsymmetricKeypair(initProps.getConsensusParticipant(0).getPubKey(), privKey); ThreadInvoker invoker = new ThreadInvoker() { @Override protected HashDigest invoke() throws Exception { - return initProcess.initialize(currentId, privKey, setting, dbConnConfig, prompter, cryptoSetting); + return initProcess.initialize(currentId, privKey, initProps, dbConnConfig, prompter); } }; diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index f174b18f..f5db2162 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -42,6 +42,7 @@ import com.jd.blockchain.tools.initializer.LedgerInitCommand; import com.jd.blockchain.tools.initializer.LedgerInitProcess; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.HttpInitConsensServiceFactory; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; import com.jd.blockchain.utils.Bytes; @@ -112,10 +113,12 @@ public class LedgerInitializeWebTest { PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); // 测试生成“账本初始化许可”; - LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); - LedgerInitProposal permission1 = testPreparePermisssion(node1, privkey1, initSetting, csProps); - LedgerInitProposal permission2 = testPreparePermisssion(node2, privkey2, initSetting, csProps); - LedgerInitProposal permission3 = testPreparePermisssion(node3, privkey3, initSetting, csProps); + LedgerInitConfiguration initConfig = LedgerInitConfiguration.create(initSetting); + initConfig.setConsensusSettings(csProvider, csProps); + LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initConfig); + LedgerInitProposal permission1 = testPreparePermisssion(node1, privkey1, initConfig); + LedgerInitProposal permission2 = testPreparePermisssion(node2, privkey2, initConfig); + LedgerInitProposal permission3 = testPreparePermisssion(node3, privkey3, initConfig); TransactionContent initTxContent0 = node0.getInitTxContent(); TransactionContent initTxContent1 = node1.getInitTxContent(); @@ -206,8 +209,8 @@ public class LedgerInitializeWebTest { } private LedgerInitProposal testPreparePermisssion(NodeWebContext node, PrivKey privKey, - LedgerInitProperties setting, ConsensusSettings csProps) { - LedgerInitProposal permission = node.preparePermision(privKey, setting, csProps); + LedgerInitConfiguration setting) { + LedgerInitProposal permission = node.preparePermision(privKey, setting); return permission; } @@ -457,9 +460,8 @@ public class LedgerInitializeWebTest { return invoker.start(); } - public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, - ConsensusSettings csProps) { - return controller.prepareLocalPermission(id, privKey, setting, csProps); + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitConfiguration initConfig) { + return controller.prepareLocalPermission(id, privKey, initConfig); } public boolean consensusPermission(PrivKey privKey) { diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index f6f92da4..cd779963 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -26,6 +26,7 @@ import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.LedgerConfiguration; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; @@ -35,6 +36,7 @@ import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.InitConsensusServiceFactory; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; import com.jd.blockchain.utils.Bytes; @@ -169,12 +171,16 @@ public class Utils { ConsensusSettings csProps, ConsensusProvider consensusProvider, DBConnectionConfig dbConnConfig, Prompter prompter, CryptoSetting cryptoSetting) { + LedgerInitConfiguration ledgerInitConfig = LedgerInitConfiguration.create(setting); + ledgerInitConfig.getLedgerSettings().setCryptoSetting(cryptoSetting); + partiKey = new AsymmetricKeypair(setting.getConsensusParticipant(0).getPubKey(), privKey); ThreadInvoker invoker = new ThreadInvoker() { @Override protected HashDigest invoke() throws Exception { - return initProcess.initialize(currentId, privKey, setting, dbConnConfig, prompter, cryptoSetting); + + return initProcess.initialize(currentId, privKey, setting, dbConnConfig, prompter); } }; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index 6d18f24f..e667b51a 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -27,6 +27,8 @@ import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.RolesConfigureOperation; +import com.jd.blockchain.ledger.UserAuthorizeOperation; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; @@ -57,6 +59,8 @@ public class LedgerInitializeTest { static { DataContractRegistry.register(LedgerInitOperation.class); DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(RolesConfigureOperation.class); + DataContractRegistry.register(UserAuthorizeOperation.class); } private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), @@ -257,13 +261,16 @@ public class LedgerInitializeTest { cryptoSetting.setSupportedProviders(supportedProviders); cryptoSetting.setAutoVerifyHash(autoVerifyHash); cryptoSetting.setHashAlgorithm(Crypto.getAlgorithm("SHA256")); + + setting.getCryptoProperties().setHashAlgorithm("SHA256"); + setting.getCryptoProperties().setVerifyHash(autoVerifyHash); partiKey = new AsymmetricKeypair(setting.getConsensusParticipant(0).getPubKey(), privKey); ThreadInvoker invoker = new ThreadInvoker() { @Override protected HashDigest invoke() throws Exception { - return initProcess.initialize(currentId, privKey, setting, dbConnConfig, prompter, cryptoSetting); + return initProcess.initialize(currentId, privKey, setting, dbConnConfig, prompter); } }; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java index e79afbf4..f627a8d2 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java @@ -10,7 +10,6 @@ import java.io.InputStream; import java.util.Properties; import java.util.concurrent.CountDownLatch; -import com.jd.blockchain.transaction.SignatureUtils; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.ClassPathResource; @@ -40,9 +39,10 @@ import com.jd.blockchain.tools.initializer.LedgerInitCommand; import com.jd.blockchain.tools.initializer.LedgerInitProcess; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.tools.initializer.web.HttpInitConsensServiceFactory; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitializeWebController; -import com.jd.blockchain.transaction.TxRequestBuilder; +import com.jd.blockchain.transaction.SignatureUtils; import com.jd.blockchain.utils.concurrent.ThreadInvoker; import com.jd.blockchain.utils.concurrent.ThreadInvoker.AsyncCallback; import com.jd.blockchain.utils.io.BytesUtils; @@ -79,9 +79,8 @@ public class LedgerInitializeWeb4SingleStepsTest { // 加载共识配置; Properties props = loadConsensusSetting(consensusConfig.getConfigPath()); ConsensusProvider csProvider = LedgerInitConsensusConfig.getConsensusProvider(consensusConfig.getProvider()); - ConsensusSettings csProps = csProvider.getSettingsFactory() - .getConsensusSettingsBuilder() - .createSettings(props, Utils.loadParticipantNodes()); + ConsensusSettings csProps = csProvider.getSettingsFactory().getConsensusSettingsBuilder().createSettings(props, + Utils.loadParticipantNodes()); // 启动服务器; NetworkAddress initAddr0 = initSetting.getConsensusParticipant(0).getInitializerAddress(); @@ -116,10 +115,12 @@ public class LedgerInitializeWeb4SingleStepsTest { PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); // 测试生成“账本初始化许可”; - LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initSetting, csProps); - LedgerInitProposal permission1 = testPreparePermisssion(node1, privkey1, initSetting, csProps); - LedgerInitProposal permission2 = testPreparePermisssion(node2, privkey2, initSetting, csProps); - LedgerInitProposal permission3 = testPreparePermisssion(node3, privkey3, initSetting, csProps); + LedgerInitConfiguration initConfig = LedgerInitConfiguration.create(initSetting); + initConfig.setConsensusSettings(csProvider, csProps); + LedgerInitProposal permission0 = testPreparePermisssion(node0, privkey0, initConfig); + LedgerInitProposal permission1 = testPreparePermisssion(node1, privkey1, initConfig); + LedgerInitProposal permission2 = testPreparePermisssion(node2, privkey2, initConfig); + LedgerInitProposal permission3 = testPreparePermisssion(node3, privkey3, initConfig); TransactionContent initTxContent0 = node0.getInitTxContent(); TransactionContent initTxContent1 = node1.getInitTxContent(); @@ -241,8 +242,8 @@ public class LedgerInitializeWeb4SingleStepsTest { } private LedgerInitProposal testPreparePermisssion(NodeWebContext node, PrivKey privKey, - LedgerInitProperties setting, ConsensusSettings csProps) { - LedgerInitProposal permission = node.preparePermision(privKey, setting, csProps); + LedgerInitConfiguration setting) { + LedgerInitProposal permission = node.preparePermision(privKey, setting); assertEquals(node.getId(), permission.getParticipantId()); assertNotNull(permission.getTransactionSignature()); @@ -385,9 +386,8 @@ public class LedgerInitializeWeb4SingleStepsTest { return invoker.start(); } - public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitProperties setting, - ConsensusSettings csProps) { - return controller.prepareLocalPermission(id, privKey, setting, csProps); + public LedgerInitProposal preparePermision(PrivKey privKey, LedgerInitConfiguration setting) { + return controller.prepareLocalPermission(id, privKey, setting); } public boolean consensusPermission(PrivKey privKey) { diff --git a/source/tools/pom.xml b/source/tools/pom.xml index d10fd85f..72142be9 100644 --- a/source/tools/pom.xml +++ b/source/tools/pom.xml @@ -15,7 +15,7 @@ tools-initializer tools-initializer-booter tools-capability - tools-mocker + \ No newline at end of file diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index b288c3d7..c39da297 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -16,7 +16,7 @@ import com.jd.blockchain.crypto.KeyGenUtils; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.LedgerInitProperties; -import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; +import com.jd.blockchain.ledger.LedgerInitProperties.ParticipantProperties; import com.jd.blockchain.ledger.core.LedgerManager; import com.jd.blockchain.tools.initializer.LedgerBindingConfig.BindingConfig; import com.jd.blockchain.utils.ArgumentSet; @@ -94,7 +94,7 @@ public class LedgerInitCommand { // 加载全部公钥; int currId = -1; for (int i = 0; i < ledgerInitProperties.getConsensusParticipantCount(); i++) { - ConsensusParticipantConfig partiConf = ledgerInitProperties.getConsensusParticipant(i); + ParticipantProperties partiConf = ledgerInitProperties.getConsensusParticipant(i); // String partiAddress = partiConf.getAddress(); // if (partiAddress == null) { // if (partiConf.getPubKeyPath() != null) { diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java index ffa52d4f..71cc6816 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitProcess.java @@ -4,6 +4,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; /** * @@ -28,13 +29,13 @@ public interface LedgerInitProcess { /** * @param currentId * @param privKey - * @param ledgerInitProps + * @param ledgerInitConfig * @param dbConnConfig * @param prompter * @param cryptoSetting * @return */ - HashDigest initialize(int currentId, PrivKey privKey, LedgerInitProperties ledgerInitProps, - DBConnectionConfig dbConnConfig, Prompter prompter, CryptoSetting cryptoSetting); + HashDigest initialize(int currentId, PrivKey privKey, LedgerInitConfiguration ledgerInitConfig, + DBConnectionConfig dbConnConfig, Prompter prompter); } diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java new file mode 100644 index 00000000..6dc9b98b --- /dev/null +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java @@ -0,0 +1,281 @@ +package com.jd.blockchain.tools.initializer.web; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import com.jd.blockchain.consensus.ConsensusProvider; +import com.jd.blockchain.consensus.ConsensusProviders; +import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.CryptoAlgorithm; +import com.jd.blockchain.crypto.CryptoProvider; +import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; +import com.jd.blockchain.crypto.service.sm.SMCryptoService; +import com.jd.blockchain.ledger.CryptoSetting; +import com.jd.blockchain.ledger.LedgerInitException; +import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.LedgerInitProperties.CryptoProperties; +import com.jd.blockchain.ledger.LedgerInitProperties.ParticipantProperties; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.SecurityInitData; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.core.CryptoConfig; +import com.jd.blockchain.ledger.core.LedgerSecurityManager; +import com.jd.blockchain.transaction.LedgerInitData; +import com.jd.blockchain.utils.StringUtils; + +public class LedgerInitConfiguration { + + private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), + SMCryptoService.class.getName() }; + + private static final String DEFAULT_HASH_ALGORITHM = "SHA256"; + + private ParticipantProperties[] participants; + + private ConsensusConfig consensusConfiguration; + + private CryptoConfig cryptoConfig; + + private ConsensusConfig consensusConfig; + + private LedgerInitData ledgerSettings; + + private SecurityInitData securitySettings; + + public ParticipantProperties[] getParticipants() { + return participants; + } + + public int getParticipantCount() { + return participants.length; + } + + /** + * @param id + * @return + */ + public ParticipantProperties getParticipant(int id) { + // 注:解析的过程确保了参与方列表是升序排列,且列表中第一个参与方的 id 为 0, id 以 1 递增; + return participants[id]; + } + + public ConsensusConfig getConsensusConfiguration() { + return consensusConfiguration; + } + + public CryptoConfig getCryptoConfig() { + return cryptoConfig; + } + + public ConsensusConfig getConsensusConfig() { + return consensusConfig; + } + + public LedgerInitData getLedgerSettings() { + return ledgerSettings; + } + + public SecurityInitData getSecuritySettings() { + return securitySettings; + } + + private LedgerInitConfiguration() { + } + + public void setConsensusSettings(ConsensusProvider consensusProvider, ConsensusSettings consensusSettings) { + byte[] consensusSettingBytes = encodeConsensusSettings(consensusProvider, consensusSettings); + ledgerSettings.setConsensusProvider(consensusProvider.getName()); + ledgerSettings.setConsensusSettings(consensusSettingBytes); + } + + public static LedgerInitConfiguration create(LedgerInitProperties ledgerInitProps) { + LedgerInitConfiguration ledgerConfig = new LedgerInitConfiguration(); + + CryptoConfig cryptoConfig = createCryptoConfig(ledgerInitProps.getCryptoProperties()); + ledgerConfig.cryptoConfig = cryptoConfig; + + ConsensusConfig consensusConfig = createConsensusConfig(ledgerInitProps); + ledgerConfig.consensusConfig = consensusConfig; + + ParticipantProperties[] participants = resolveParticipants(ledgerInitProps); + ledgerConfig.participants = participants; + + LedgerInitData ledgerSettings = createLedgerInitSettings(ledgerInitProps, cryptoConfig, consensusConfig, + participants); + ledgerConfig.ledgerSettings = ledgerSettings; + + SecurityInitData securitySettings = createSecurityInitSettings(ledgerInitProps, participants); + ledgerConfig.securitySettings = securitySettings; + + return ledgerConfig; + } + + private static ConsensusConfig createConsensusConfig(LedgerInitProperties initProps) { + ConsensusProvider consensusProvider = ConsensusProviders.getProvider(initProps.getConsensusProvider()); + + Properties csProps = initProps.getConsensusConfig(); + ConsensusSettings protocolSettings = consensusProvider.getSettingsFactory().getConsensusSettingsBuilder() + .createSettings(csProps, initProps.getConsensusParticipantNodes()); + + ConsensusConfig config = new ConsensusConfig(); + config.setProvider(consensusProvider); + config.setProtocolSettings(protocolSettings); + + return config; + } + + private static CryptoConfig createCryptoConfig(CryptoProperties cryptoProperties) { + // 总是包含默认的提供者; + Set cryptoProviderNames = new LinkedHashSet(); + for (String providerName : SUPPORTED_PROVIDERS) { + cryptoProviderNames.add(providerName); + } + if (cryptoProperties.getProviders() != null) { + for (String providerName : cryptoProperties.getProviders()) { + cryptoProviderNames.add(providerName); + } + } + CryptoProvider[] cryptoProviders = new CryptoProvider[cryptoProviderNames.size()]; + int i = 0; + for (String providerName : cryptoProviderNames) { + cryptoProviders[i] = Crypto.getProvider(providerName); + i++; + } + + String hashAlgorithmName = StringUtils.trim(cryptoProperties.getHashAlgorithm()); + hashAlgorithmName = hashAlgorithmName.length() == 0 ? DEFAULT_HASH_ALGORITHM : hashAlgorithmName; + CryptoAlgorithm hashAlgorithm = Crypto.getAlgorithm(hashAlgorithmName); + + CryptoConfig cryptoConfig = new CryptoConfig(); + cryptoConfig.setSupportedProviders(cryptoProviders); + cryptoConfig.setAutoVerifyHash(cryptoProperties.isVerifyHash()); + cryptoConfig.setHashAlgorithm(hashAlgorithm); + + return cryptoConfig; + } + + private static SecurityInitData createSecurityInitSettings(LedgerInitProperties ledgerInitProps, + ParticipantProperties[] participants) { + // 设置角色; + SecurityInitData securityInitData = new SecurityInitData(); + securityInitData.setRoles(ledgerInitProps.getRoles()); + // 如果没有默认角色,则创建“默认”角色; + if (securityInitData.getRolesCount() == 0) { + securityInitData.addRole(LedgerSecurityManager.DEFAULT_ROLE, LedgerPermission.values(), + TransactionPermission.values()); + } else if (!securityInitData.containsRole(LedgerSecurityManager.DEFAULT_ROLE)) { + // 如果定义了角色,则必须显式地定义“默认”角色; + throw new LedgerInitException("Miss definition of role[DEFAULT]!"); + } + + // 设置授权; + for (ParticipantProperties partiProps : participants) { + String[] roles = partiProps.getRoles(); + for (String role : roles) { + if (!securityInitData.containsRole(role)) { + throw new LedgerInitException( + String.format("The role[%s] authenticated to participant[%s-%s] is not defined!", role, + partiProps.getId(), partiProps.getName())); + } + } + securityInitData.addUserAuthencation(partiProps.getAddress(), roles, partiProps.getRolesPolicy()); + } + + return securityInitData; + } + + private static LedgerInitData createLedgerInitSettings(LedgerInitProperties ledgerProps, + CryptoSetting cryptoSetting, ConsensusConfig consensusConfig, ParticipantProperties[] participants) { + // 创建初始化配置; + LedgerInitData initSetting = new LedgerInitData(); + initSetting.setLedgerSeed(ledgerProps.getLedgerSeed()); + initSetting.setCryptoSetting(cryptoSetting); + + initSetting.setConsensusParticipants(participants); + + initSetting.setCreatedTime(ledgerProps.getCreatedTime()); + + // 创建共识配置; + try { + byte[] consensusSettingsBytes = encodeConsensusSettings(consensusConfig.getProvider(), + consensusConfig.protocolSettings); + initSetting.setConsensusProvider(consensusConfig.getProvider().getName()); + initSetting.setConsensusSettings(consensusSettingsBytes); + } catch (Exception e) { + throw new LedgerInitException("Create default consensus config failed! --" + e.getMessage(), e); + } + + return initSetting; + } + + public static byte[] encodeConsensusSettings(ConsensusProvider consensusProvider, + ConsensusSettings consensusSettings) { + return consensusProvider.getSettingsFactory().getConsensusSettingsEncoder().encode(consensusSettings); + } + + /** + * 解析参与方列表; + * + * @param ledgerInitProps + * @return + */ + private static ParticipantProperties[] resolveParticipants(LedgerInitProperties ledgerInitProps) { + List partiList = ledgerInitProps.getConsensusParticipants(); + ParticipantProperties[] parties = new ParticipantProperties[partiList.size()]; + parties = partiList.toArray(parties); + ParticipantProperties[] orderedParties = sortAndVerify(parties); + + return orderedParties; + } + + /** + * 对参与者列表按照 id 进行升序排列,并校验id是否从 1 开始且没有跳跃; + * + * @param parties + * @return + */ + private static ParticipantProperties[] sortAndVerify(ParticipantProperties[] parties) { + Arrays.sort(parties, new Comparator() { + @Override + public int compare(ParticipantProperties o1, ParticipantProperties o2) { + return o1.getId() - o2.getId(); + } + }); + for (int i = 0; i < parties.length; i++) { + if (parties[i].getId() != i) { + throw new LedgerInitException( + "The ids of participants are not match their positions in the participant-list!"); + } + } + return parties; + } + + public static class ConsensusConfig { + + private ConsensusProvider provider; + + private ConsensusSettings protocolSettings; + + public ConsensusSettings getProtocolSettings() { + return protocolSettings; + } + + public void setProtocolSettings(ConsensusSettings protocolSettings) { + this.protocolSettings = protocolSettings; + } + + public ConsensusProvider getProvider() { + return provider; + } + + public void setProvider(ConsensusProvider provider) { + this.provider = provider; + } + } +} diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index cd30494a..87ffc108 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -3,9 +3,7 @@ package com.jd.blockchain.tools.initializer.web; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.List; -import java.util.Properties; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -18,28 +16,19 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.consensus.ConsensusProvider; -import com.jd.blockchain.consensus.ConsensusProviders; -import com.jd.blockchain.consensus.ConsensusSettings; import com.jd.blockchain.crypto.Crypto; -import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.crypto.SignatureFunction; -import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; -import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.LedgerInitProperties; -import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; -import com.jd.blockchain.ledger.LedgerInitSetting; +import com.jd.blockchain.ledger.LedgerInitProperties.ParticipantProperties; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerInitProposalData; @@ -51,9 +40,7 @@ import com.jd.blockchain.tools.initializer.InitializingStep; import com.jd.blockchain.tools.initializer.LedgerInitProcess; import com.jd.blockchain.tools.initializer.Prompter; import com.jd.blockchain.transaction.DigitalSignatureBlob; -import com.jd.blockchain.transaction.LedgerInitData; import com.jd.blockchain.transaction.SignatureUtils; -import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.concurrent.InvocationResult; import com.jd.blockchain.utils.io.BytesUtils; import com.jd.blockchain.utils.net.NetworkAddress; @@ -71,20 +58,17 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI DataContractRegistry.register(TransactionRequest.class); } - private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), - SMCryptoService.class.getName() }; - private static final String DEFAULT_SIGN_ALGORITHM = "ED25519"; private final SignatureFunction SIGN_FUNC; - private volatile LedgerInitProposal localPermission; + private volatile LedgerInitConfiguration ledgerInitConfig; private volatile LedgerInitializer initializer; - private volatile int currentId = -1; + private volatile LedgerInitProposal localPermission; - private volatile LedgerInitSetting ledgerInitSetting; + private volatile int currentId = -1; private volatile LedgerInitProposal[] permissions; @@ -92,8 +76,6 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private volatile Prompter prompter; - private volatile ConsensusProvider consensusProvider; - private volatile LedgerInitDecision localDecision; private volatile DecisionResultHandle[] decisions; @@ -138,35 +120,36 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI this.prompter = prompter; } - private void setConsensusProvider(ConsensusProvider consensusProvider) { - this.consensusProvider = consensusProvider; - } - @Override public HashDigest initialize(int currentId, PrivKey privKey, LedgerInitProperties ledgerInitProps, DBConnectionConfig dbConnConfig, Prompter prompter) { - return initialize(currentId, privKey, ledgerInitProps, dbConnConfig, prompter, createDefaultCryptoSetting()); + LedgerInitConfiguration initConfig = LedgerInitConfiguration.create(ledgerInitProps); + return initialize(currentId, privKey, initConfig, dbConnConfig, prompter); } @Override - public HashDigest initialize(int currentId, PrivKey privKey, LedgerInitProperties ledgerInitProps, - DBConnectionConfig dbConnConfig, Prompter prompter, CryptoSetting cryptoSetting) { - - if (this.ledgerInitSetting != null) { + public HashDigest initialize(int currentId, PrivKey privKey, LedgerInitConfiguration initConfig, + DBConnectionConfig dbConnConfig, Prompter prompter) { + if (initConfig == null) { + throw new IllegalArgumentException("Ledger init configuration is null"); + } + if (this.ledgerInitConfig != null) { throw new IllegalStateException("ledger init process has already started."); } setPrompter(prompter); - Properties csProps = ledgerInitProps.getConsensusConfig(); - ConsensusProvider csProvider = ConsensusProviders.getProvider(ledgerInitProps.getConsensusProvider()); - ConsensusSettings csSettings = csProvider.getSettingsFactory().getConsensusSettingsBuilder() - .createSettings(csProps, ledgerInitProps.getConsensusParticipantNodes()); - setConsensusProvider(csProvider); +// Properties csProps = ledgerInitProps.getConsensusConfig(); +// ConsensusProvider csProvider = ConsensusProviders.getProvider(ledgerInitProps.getConsensusProvider()); +// ConsensusSettings csSettings = csProvider.getSettingsFactory().getConsensusSettingsBuilder() +// .createSettings(csProps, ledgerInitProps.getConsensusParticipantNodes()); +// setConsensusProvider(csProvider); prompter.info("Init settings and sign permision..."); - prepareLocalPermission(currentId, privKey, ledgerInitProps, csSettings, cryptoSetting); + this.ledgerInitConfig = initConfig; + + prepareLocalPermission(currentId, privKey, ledgerInitConfig); prompter.confirm(InitializingStep.PERMISSION_READY.toString(), "Ledger init permission has already prepared! Any key to continue..."); @@ -212,7 +195,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI // 生成签名决定; this.localDecision = makeDecision(currentId, initializer.getLedgerHash(), privKey); - this.decisions = new DecisionResultHandle[this.ledgerInitSetting.getConsensusParticipants().length]; + this.decisions = new DecisionResultHandle[ledgerInitConfig.getParticipantCount()]; for (int i = 0; i < decisions.length; i++) { // 参与者的 id 是依次递增的; this.decisions[i] = new DecisionResultHandle(i); @@ -223,7 +206,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI } private DigitalSignature[] getNodesSignatures() { - ParticipantNode[] parties = this.ledgerInitSetting.getConsensusParticipants(); + ParticipantNode[] parties = this.ledgerInitConfig.getParticipants(); DigitalSignature[] signatures = new DigitalSignature[parties.length]; for (int i = 0; i < parties.length; i++) { PubKey pubKey = parties[i].getPubKey(); @@ -298,78 +281,57 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI return allPermitted; } - public CryptoSetting createDefaultCryptoSetting() { - CryptoProvider[] supportedProviders = new CryptoProvider[SUPPORTED_PROVIDERS.length]; - for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { - supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); - } - CryptoConfig defCryptoSetting = new CryptoConfig(); - defCryptoSetting.setSupportedProviders(supportedProviders); - defCryptoSetting.setAutoVerifyHash(true); - defCryptoSetting.setHashAlgorithm(Crypto.getAlgorithm("SHA256")); - - return defCryptoSetting; - } - - public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, - ConsensusSettings consensusProps) { - CryptoSetting defCryptoSetting = createDefaultCryptoSetting(); - return prepareLocalPermission(currentId, privKey, ledgerProps, consensusProps, defCryptoSetting); - } - - public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, - ConsensusSettings csSettings, CryptoSetting cryptoSetting) { +// public CryptoSetting createDefaultCryptoSetting() { +// CryptoProvider[] supportedProviders = new CryptoProvider[SUPPORTED_PROVIDERS.length]; +// for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { +// supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); +// } +// CryptoConfig defCryptoSetting = new CryptoConfig(); +// defCryptoSetting.setSupportedProviders(supportedProviders); +// defCryptoSetting.setAutoVerifyHash(true); +// defCryptoSetting.setHashAlgorithm(Crypto.getAlgorithm("SHA256")); +// +// return defCryptoSetting; +// } +// + public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, + LedgerInitProperties ledgerInitProps) { + LedgerInitConfiguration ledgerInitConfiguration = LedgerInitConfiguration.create(ledgerInitProps); + return prepareLocalPermission(currentId, privKey, ledgerInitConfiguration); + } + + public LedgerInitProposal prepareLocalPermission(int currentId, PrivKey privKey, + LedgerInitConfiguration ledgerInitConfig) { // 创建初始化配置; - LedgerInitData initSetting = new LedgerInitData(); - initSetting.setLedgerSeed(ledgerProps.getLedgerSeed()); - initSetting.setCryptoSetting(cryptoSetting); - - List partiList = ledgerProps.getConsensusParticipants(); - ConsensusParticipantConfig[] parties = new ConsensusParticipantConfig[partiList.size()]; - parties = partiList.toArray(parties); - ConsensusParticipantConfig[] orderedParties = sortAndVerify(parties); - initSetting.setConsensusParticipants(orderedParties); - initSetting.setCreatedTime(ledgerProps.getCreatedTime()); - - // 创建默认的共识配置; - try { - // ConsensusConfig csConfig = new ConsensusConfig(); - byte[] csSettingBytes = consensusProvider.getSettingsFactory().getConsensusSettingsEncoder() - .encode(csSettings); - initSetting.setConsensusProvider(consensusProvider.getName()); - initSetting.setConsensusSettings(new Bytes(csSettingBytes)); - } catch (Exception e) { - throw new LedgerInitException("Create default consensus config failed! --" + e.getMessage(), e); - } - - if (currentId < 0 || currentId >= orderedParties.length) { + ParticipantProperties[] participants = ledgerInitConfig.getParticipants(); + if (currentId < 0 || currentId >= participants.length) { throw new LedgerInitException("Your id is out of bound of participant list!"); } this.currentId = currentId; - this.ledgerInitSetting = initSetting; // 校验当前的公钥、私钥是否匹配; byte[] testBytes = BytesUtils.toBytes(currentId); SignatureDigest testSign = SIGN_FUNC.sign(privKey, testBytes); - PubKey myPubKey = orderedParties[currentId].getPubKey(); + PubKey myPubKey = participants[currentId].getPubKey(); if (!SIGN_FUNC.verify(testSign, myPubKey, testBytes)) { throw new LedgerInitException("Your pub-key specified in the init-settings isn't match your priv-key!"); } - this.initializerAddresses = new NetworkAddress[orderedParties.length]; + this.initializerAddresses = new NetworkAddress[participants.length]; // 记录每个参与方的账本初始化服务地址; - for (int i = 0; i < orderedParties.length; i++) { - initializerAddresses[i] = orderedParties[i].getInitializerAddress(); + for (int i = 0; i < participants.length; i++) { + initializerAddresses[i] = participants[i].getInitializerAddress(); } // 初始化账本; - this.initializer = LedgerInitializer.create(ledgerInitSetting); + this.initializer = LedgerInitializer.create(ledgerInitConfig.getLedgerSettings(), + ledgerInitConfig.getSecuritySettings()); // 对初始交易签名,生成当前参与者的账本初始化许可; SignatureDigest permissionSign = initializer.signTransaction(privKey); LedgerInitProposalData permission = new LedgerInitProposalData(currentId, permissionSign); this.currentId = currentId; - this.permissions = new LedgerInitProposal[initSetting.getConsensusParticipants().length]; + this.permissions = new LedgerInitProposal[ledgerInitConfig.getParticipantCount()]; this.permissions[currentId] = permission; this.localPermission = permission; @@ -397,7 +359,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private boolean startRequestPermissions(int currentId, PrivKey privKey) { SignatureDigest reqAuthSign = signPermissionRequest(currentId, privKey); - ParticipantNode[] participants = ledgerInitSetting.getConsensusParticipants(); + ParticipantNode[] participants = ledgerInitConfig.getParticipants(); // 异步请求结果列表;不包括已经获得许可的参与方; InvocationResult[] results = new InvocationResult[participants.length]; @@ -457,7 +419,8 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI continue; } - if (!SignatureUtils.verifySignature(initializer.getTransactionContent(), permission.getTransactionSignature(), pubKey)) { + if (!SignatureUtils.verifySignature(initializer.getTransactionContent(), + permission.getTransactionSignature(), pubKey)) { prompter.error("Invalid permission from participant! --[Id=%s][name=%s]", participants[i].getAddress(), participants[i].getName()); allPermitted = false; @@ -477,7 +440,8 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI } public SignatureDigest signPermissionRequest(int requesterId, PrivKey privKey) { - byte[] reqAuthBytes = BytesUtils.concat(BytesUtils.toBytes(requesterId), ledgerInitSetting.getLedgerSeed()); + byte[] reqAuthBytes = BytesUtils.concat(BytesUtils.toBytes(requesterId), + ledgerInitConfig.getLedgerSettings().getLedgerSeed()); SignatureDigest reqAuthSign = SIGN_FUNC.sign(privKey, reqAuthBytes); return reqAuthSign; } @@ -523,7 +487,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI throw new LedgerInitException("There is a id conflict!"); } int retry = 0; - while (currentId == -1 || ledgerInitSetting == null || localPermission == null) { + while (currentId == -1 || ledgerInitConfig == null || localPermission == null) { // 本地尚未完成初始化; if (retry < 30) { try { @@ -537,11 +501,12 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI retry++; } - ParticipantNode[] participants = ledgerInitSetting.getConsensusParticipants(); + ParticipantNode[] participants = ledgerInitConfig.getParticipants(); if (requesterId < 0 || requesterId >= participants.length) { throw new LedgerInitException("The id of requester is out of the bound of participant list!"); } - byte[] requestCodeBytes = BytesUtils.concat(BytesUtils.toBytes(requesterId), ledgerInitSetting.getLedgerSeed()); + byte[] requestCodeBytes = BytesUtils.concat(BytesUtils.toBytes(requesterId), + ledgerInitConfig.getLedgerSettings().getLedgerSeed()); PubKey requesterPubKey = participants[requesterId].getPubKey(); if (!SIGN_FUNC.verify(signature, requesterPubKey, requestCodeBytes)) { throw new LedgerInitException("The requester signature is invalid!"); @@ -680,8 +645,7 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI } // 检查签名; - PubKey targetPubKey = ledgerInitSetting.getConsensusParticipants()[targetDecision.getParticipantId()] - .getPubKey(); + PubKey targetPubKey = ledgerInitConfig.getParticipant(targetDecision.getParticipantId()).getPubKey(); byte[] deciBytes = getDecisionBytes(targetDecision.getParticipantId(), targetDecision.getLedgerHash()); if ((!SIGN_FUNC.verify(targetDecision.getSignature(), targetPubKey, deciBytes)) && resultHandle.getValue() == null) { @@ -733,28 +697,6 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI } } - /** - * 对参与者列表按照 id 进行升序排列,并校验id是否从 1 开始且没有跳跃; - * - * @param parties - * @return - */ - private ConsensusParticipantConfig[] sortAndVerify(ConsensusParticipantConfig[] parties) { - Arrays.sort(parties, new Comparator() { - @Override - public int compare(ConsensusParticipantConfig o1, ConsensusParticipantConfig o2) { - return o1.getId() - o2.getId(); - } - }); - for (int i = 0; i < parties.length; i++) { - if (parties[i].getId() != i) { - throw new LedgerInitException( - "The ids of participants are not match their positions in the participant-list!"); - } - } - return parties; - } - private static class DecisionResultHandle extends InvocationResult { private final int PARTICIPANT_ID; diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java index 2a922b4a..938e12bd 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerLedgerInitializer.java @@ -24,7 +24,7 @@ import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.LedgerInitProperties; -import com.jd.blockchain.ledger.LedgerInitProperties.ConsensusParticipantConfig; +import com.jd.blockchain.ledger.LedgerInitProperties.ParticipantProperties; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.TransactionContent; @@ -40,6 +40,7 @@ import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; import com.jd.blockchain.tools.initializer.Prompter; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; import com.jd.blockchain.tools.initializer.web.LedgerInitConsensusService; import com.jd.blockchain.tools.initializer.web.LedgerInitDecisionData; import com.jd.blockchain.transaction.DigitalSignatureBlob; @@ -74,7 +75,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon private volatile int currentId = -1; - private volatile LedgerInitSetting ledgerInitSetting; + private volatile LedgerInitConfiguration ledgerInitConfig; // private volatile LedgerInitPermission[] permissions; // private volatile LedgerInitPermission permission; @@ -130,25 +131,22 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon @Override public HashDigest initialize(int currentId, PrivKey privKey, LedgerInitProperties ledgerInitProps, DBConnectionConfig dbConnConfig, Prompter prompter) { - return initialize(currentId, privKey, ledgerInitProps, dbConnConfig, prompter, createDefaultCryptoSetting()); + LedgerInitConfiguration ledgerInitConfig = LedgerInitConfiguration.create(ledgerInitProps); + return initialize(currentId, privKey, ledgerInitConfig, dbConnConfig, prompter); } @Override - public synchronized HashDigest initialize(int currentId, PrivKey privKey, LedgerInitProperties ledgerInitProps, - DBConnectionConfig dbConnConfig, Prompter prompter, CryptoSetting cryptoSetting) { - - if (this.ledgerInitSetting != null) { + public synchronized HashDigest initialize(int currentId, PrivKey privKey, LedgerInitConfiguration ledgerInitProps, + DBConnectionConfig dbConnConfig, Prompter prompter) { + if (this.ledgerInitConfig != null) { throw new IllegalStateException("ledger init process has already started."); } setPrompter(prompter); - ConsensusProvider csProvider = ConsensusProviders.getProvider(ledgerInitProps.getConsensusProvider()); - setConsensusProvider(csProvider); - prompter.info("Init settings and sign permision..."); - prepareLocalProposal(currentId, privKey, ledgerInitProps, null, cryptoSetting); + prepareLocalProposal(currentId, privKey, ledgerInitProps); try { // 连接数据库; @@ -180,7 +178,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon // 生成签名决定; this.localDecision = makeDecision(currentId, initializer.getLedgerHash(), privKey); - this.decisions = new DecisionResultHandle[this.ledgerInitSetting.getConsensusParticipants().length]; + this.decisions = new DecisionResultHandle[this.ledgerInitConfig.getParticipantCount()]; for (int i = 0; i < decisions.length; i++) { // 参与者的 id 是依次递增的; this.decisions[i] = new DecisionResultHandle(i); @@ -191,7 +189,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon } private DigitalSignature getNodeSignatures() { - ParticipantNode parti = this.ledgerInitSetting.getConsensusParticipants()[currentId]; + ParticipantNode parti = this.ledgerInitConfig.getParticipant(currentId); PubKey pubKey = parti.getPubKey(); SignatureDigest signDigest = this.localPermission.getTransactionSignature(); DigitalSignatureBlob digitalSignature = new DigitalSignatureBlob(pubKey, signDigest); @@ -230,44 +228,24 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon return defCryptoSetting; } - public LedgerInitProposal prepareLocalProposal(int currentId, PrivKey privKey, LedgerInitProperties ledgerProps, - ConsensusSettings csSettings, CryptoSetting cryptoSetting) { - // 创建初始化配置; - LedgerInitData initSetting = new LedgerInitData(); - initSetting.setLedgerSeed(ledgerProps.getLedgerSeed()); - initSetting.setCryptoSetting(cryptoSetting); - - List partiList = ledgerProps.getConsensusParticipants(); - ConsensusParticipantConfig[] parties = partiList.toArray(new ConsensusParticipantConfig[partiList.size()]); - ConsensusParticipantConfig[] orderedParties = sortAndVerify(parties); - initSetting.setConsensusParticipants(orderedParties); - - // 创建默认的共识配置; - try { - byte[] csSettingBytes = new byte[1024]; - new Random().nextBytes(csSettingBytes); - - initSetting.setConsensusProvider(consensusProvider.getName()); - initSetting.setConsensusSettings(new Bytes(csSettingBytes)); - } catch (Exception e) { - throw new LedgerInitException("Create default consensus config failed! --" + e.getMessage(), e); - } + public LedgerInitProposal prepareLocalProposal(int currentId, PrivKey privKey, + LedgerInitConfiguration ledgerInitConfig) { - if (currentId < 0 || currentId >= orderedParties.length) { + if (currentId < 0 || currentId >= ledgerInitConfig.getParticipantCount()) { throw new LedgerInitException("Your id is out of bound of participant list!"); } this.currentId = currentId; - this.ledgerInitSetting = initSetting; // 校验当前的公钥、私钥是否匹配; byte[] testBytes = BytesUtils.toBytes(currentId); SignatureDigest testSign = SIGN_FUNC.sign(privKey, testBytes); - PubKey myPubKey = orderedParties[currentId].getPubKey(); + PubKey myPubKey = ledgerInitConfig.getParticipant(currentId).getPubKey(); if (!SIGN_FUNC.verify(testSign, myPubKey, testBytes)) { throw new LedgerInitException("Your pub-key specified in the init-settings isn't match your priv-key!"); } // 初始化; - this.initializer = LedgerInitializer.create(ledgerInitSetting); + this.initializer = LedgerInitializer.create(ledgerInitConfig.getLedgerSettings(), + ledgerInitConfig.getSecuritySettings()); // 对初始交易签名,生成当前参与者的账本初始化许可; SignatureDigest permissionSign = SignatureUtils.sign(initializer.getTransactionContent(), privKey); @@ -337,7 +315,7 @@ public class MockerLedgerInitializer implements LedgerInitProcess, LedgerInitCon * @param parties * @return */ - private ConsensusParticipantConfig[] sortAndVerify(ConsensusParticipantConfig[] parties) { + private ParticipantProperties[] sortAndVerify(ParticipantProperties[] parties) { Arrays.sort(parties, (o1, o2) -> o1.getId() - o2.getId()); for (int i = 0; i < parties.length; i++) { if (parties[i].getId() != i) { diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index d062ea23..367a08c7 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -76,6 +76,7 @@ import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; @@ -185,8 +186,11 @@ public class MockerNodeContext implements BlockchainQueryService { MockerLedgerInitializer mockLedgerInitializer = new MockerLedgerInitializer(dbConnFactory, ledgerManager); - ledgerHash = mockLedgerInitializer.initialize(0, defaultKeypair.getPrivKey(), ledgerInitProperties, - dbConnectionConfig, new PresetAnswerPrompter("N"), cryptoConfig()); + LedgerInitConfiguration initConfig = LedgerInitConfiguration.create(ledgerInitProperties); + initConfig.getLedgerSettings().setCryptoSetting(cryptoConfig()); + + ledgerHash = mockLedgerInitializer.initialize(0, defaultKeypair.getPrivKey(), initConfig, dbConnectionConfig, + new PresetAnswerPrompter("N")); ledgerRepository = registerLedger(ledgerHash, dbConnectionConfig); diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java index f1fba9b1..aaaed113 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/PropertiesUtils.java @@ -251,6 +251,14 @@ public abstract class PropertiesUtils { String value = getRequiredProperty(props, key); return Boolean.parseBoolean(value); } + + public static boolean getBooleanOptional(Properties props, String key, boolean defaultValue) { + String value = getProperty(props, key, false); + if (value == null) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } /** * 返回指定的属性;
        @@ -267,6 +275,14 @@ public abstract class PropertiesUtils { public static String getOptionalProperty(Properties props, String key) { return getProperty(props, key, false); } + + public static String getOptionalProperty(Properties props, String key, String defaultValue) { + String value = getProperty(props, key, false); + if (value == null) { + return defaultValue; + } + return value; + } /** * 返回指定的属性;
        diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java index 5761f464..480d1335 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/StringUtils.java @@ -72,4 +72,8 @@ public class StringUtils { } return tokens.toArray(new String[tokens.size()]); } + + public static String trim(String str) { + return str == null ? "" : str.trim(); + } } \ No newline at end of file From 85021535a692f78cbd35bc2a8fbc01ee2e3e5b2c Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 9 Sep 2019 00:22:27 +0800 Subject: [PATCH 085/124] Added batch authorization API; --- .../handles/UserAuthorizeOperationHandle.java | 33 ++++---- .../ledger/UserAuthorizeOperation.java | 4 +- .../blockchain/transaction/UserAuthorize.java | 4 +- .../transaction/UserAuthorizeOpTemplate.java | 37 ++++----- .../src/test/resources/ledger.init | 2 +- .../sdk/samples/SDKDemo_RegisterUser.java | 81 ++++++++++--------- .../src/test/resources/ledger_init_test.init | 64 +++++++++++++++ .../com/jd/blockchain/utils/ArrayUtils.java | 1 + 8 files changed, 150 insertions(+), 76 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java index b4747dbf..b9d3614d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java @@ -18,6 +18,7 @@ import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; import com.jd.blockchain.ledger.core.SecurityPolicy; import com.jd.blockchain.ledger.core.TransactionRequestExtension; +import com.jd.blockchain.utils.Bytes; public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle { public UserAuthorizeOperationHandle() { @@ -49,21 +50,25 @@ public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle< } } } - UserRoles ur = urSettings.getUserRoles(urcfg.getUserAddress()); - if (ur == null) { - RolesPolicy policy = urcfg.getPolicy(); - if (policy == null) { - policy = RolesPolicy.UNION; - } - urSettings.addUserRoles(urcfg.getUserAddress(), policy, validRoles); - } else { - ur.addRoles(validRoles); - ur.removeRoles(urcfg.getUnauthorizedRoles()); + for (Bytes address : urcfg.getUserAddresses()) { + UserRoles ur = urSettings.getUserRoles(address); + if (ur == null) { + // 这是新的授权; + RolesPolicy policy = urcfg.getPolicy(); + if (policy == null) { + policy = RolesPolicy.UNION; + } + urSettings.addUserRoles(address, policy, validRoles); + } else { + // 更改之前的授权; + ur.addRoles(validRoles); + ur.removeRoles(urcfg.getUnauthorizedRoles()); - // 如果请求中设置了策略,才进行更新; - RolesPolicy policy = urcfg.getPolicy(); - if (policy != null) { - ur.setPolicy(policy); + // 如果请求中设置了策略,才进行更新; + RolesPolicy policy = urcfg.getPolicy(); + if (policy != null) { + ur.setPolicy(policy); + } } } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java index d46bd1a6..67e37e17 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizeOperation.java @@ -26,8 +26,8 @@ public interface UserAuthorizeOperation extends Operation { * * @return */ - @DataField(order = 0, primitiveType = PrimitiveType.BYTES) - Bytes getUserAddress(); + @DataField(order = 0, primitiveType = PrimitiveType.BYTES, list = true) + Bytes[] getUserAddresses(); /** * 要更新的多角色权限策略; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java index deb184d3..ecaaa7ef 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorize.java @@ -5,8 +5,8 @@ import com.jd.blockchain.utils.Bytes; public interface UserAuthorize { - UserRolesAuthorizer forUser(BlockchainIdentity userId); + UserRolesAuthorizer forUser(BlockchainIdentity... userId); - UserRolesAuthorizer forUser(Bytes userAddress); + UserRolesAuthorizer forUser(Bytes... userAddress); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java index 40670e8c..a8f44f87 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java @@ -1,9 +1,8 @@ package com.jd.blockchain.transaction; +import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.Map; import java.util.Set; import com.jd.blockchain.binaryproto.DataContractRegistry; @@ -21,8 +20,8 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe DataContractRegistry.register(UserRegisterOperation.class); } - private Map userAuthMap = Collections - .synchronizedMap(new LinkedHashMap()); + private Set userAuthMap = Collections + .synchronizedSet(new LinkedHashSet()); public UserAuthorizeOpTemplate() { } @@ -32,7 +31,7 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe @Override public UserRolesAuthorization[] getUserRolesAuthorizations() { - return ArrayUtils.toArray(userAuthMap.values(), UserRolesAuthorization.class); + return ArrayUtils.toArray(userAuthMap, UserRolesAuthorization.class); } @Override @@ -41,35 +40,33 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe } @Override - public UserRolesAuthorizer forUser(Bytes userAddress) { - UserRolesAuthorization userRolesAuth = userAuthMap.get(userAddress); - if (userRolesAuth == null) { - userRolesAuth = new UserRolesAuthorization(userAddress); - userAuthMap.put(userAddress, userRolesAuth); - } + public UserRolesAuthorizer forUser(Bytes... userAddresses) { + UserRolesAuthorization userRolesAuth = new UserRolesAuthorization(userAddresses); + userAuthMap.add(userRolesAuth); return userRolesAuth; } @Override - public UserRolesAuthorizer forUser(BlockchainIdentity userId) { - return forUser(userId.getAddress()); + public UserRolesAuthorizer forUser(BlockchainIdentity... userIds) { + Bytes[] addresses = Arrays.stream(userIds).map(p -> p.getAddress()).toArray(Bytes[]::new); + return forUser(addresses); } private class UserRolesAuthorization implements UserRolesAuthorizer, UserRolesEntry { - private Bytes userAddress; + private Bytes[] userAddress; private RolesPolicy policy = RolesPolicy.UNION; private Set authRoles = new LinkedHashSet(); private Set unauthRoles = new LinkedHashSet(); - private UserRolesAuthorization(Bytes userAddress) { + private UserRolesAuthorization(Bytes[] userAddress) { this.userAddress = userAddress; } @Override - public Bytes getUserAddress() { + public Bytes[] getUserAddresses() { return userAddress; } @@ -119,13 +116,13 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe } @Override - public UserRolesAuthorizer forUser(BlockchainIdentity userId) { - return UserAuthorizeOpTemplate.this.forUser(userId); + public UserRolesAuthorizer forUser(BlockchainIdentity... userIds) { + return UserAuthorizeOpTemplate.this.forUser(userIds); } @Override - public UserRolesAuthorizer forUser(Bytes userAddress) { - return UserAuthorizeOpTemplate.this.forUser(userAddress); + public UserRolesAuthorizer forUser(Bytes... userAddresses) { + return UserAuthorizeOpTemplate.this.forUser(userAddresses); } } } diff --git a/source/ledger/ledger-model/src/test/resources/ledger.init b/source/ledger/ledger-model/src/test/resources/ledger.init index 97c68e85..9ff0a35c 100644 --- a/source/ledger/ledger-model/src/test/resources/ledger.init +++ b/source/ledger/ledger-model/src/test/resources/ledger.init @@ -65,7 +65,7 @@ com.jd.blockchain.crypto.service.sm.SMCryptoService crypto.verify-hash=true #哈希算法; -crypto.hash-algorithm=SHA256; +crypto.hash-algorithm=SHA256 #参与方的个数,后续以 cons_parti.id 分别标识每一个参与方的配置; diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java index a1362a39..778e5851 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_RegisterUser.java @@ -20,60 +20,67 @@ import com.jd.blockchain.utils.ConsoleUtils; /** * 注册用户 + * * @author shaozhuguang * @create 2018/10/18 * @since 1.0.0 */ public class SDKDemo_RegisterUser { - public static void main(String[] args) { + public static void main(String[] args) { - String GATEWAY_IPADDR = "127.0.0.1"; - int GATEWAY_PORT = 8081; - if (args != null && args.length == 2) { - GATEWAY_IPADDR = args[0]; - GATEWAY_PORT = Integer.parseInt(args[1]); - } + String GATEWAY_IPADDR = "127.0.0.1"; + int GATEWAY_PORT = 8081; + if (args != null && args.length == 2) { + GATEWAY_IPADDR = args[0]; + GATEWAY_PORT = Integer.parseInt(args[1]); + } - // 注册相关class - DataContractRegistry.register(TransactionContent.class); - DataContractRegistry.register(TransactionContentBody.class); - DataContractRegistry.register(TransactionRequest.class); - DataContractRegistry.register(NodeRequest.class); - DataContractRegistry.register(EndpointRequest.class); - DataContractRegistry.register(TransactionResponse.class); + // 注册相关class + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); - PrivKey privKey = SDKDemo_Params.privkey1; - PubKey pubKey = SDKDemo_Params.pubKey1; + PrivKey privKey = SDKDemo_Params.privkey1; + PubKey pubKey = SDKDemo_Params.pubKey1; - BlockchainKeypair CLIENT_CERT = new BlockchainKeypair(SDKDemo_Params.pubKey0, SDKDemo_Params.privkey0); + BlockchainKeypair CLIENT_CERT = new BlockchainKeypair(SDKDemo_Params.pubKey0, SDKDemo_Params.privkey0); - boolean SECURE = false; - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, - CLIENT_CERT); - BlockchainService service = serviceFactory.getBlockchainService(); + boolean SECURE = false; + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IPADDR, GATEWAY_PORT, SECURE, + CLIENT_CERT); + BlockchainService service = serviceFactory.getBlockchainService(); - HashDigest[] ledgerHashs = service.getLedgerHashs(); - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); + HashDigest[] ledgerHashs = service.getLedgerHashs(); + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = service.newTransaction(ledgerHashs[0]); - //existed signer - AsymmetricKeypair keyPair = new BlockchainKeypair(pubKey, privKey); + // existed signer + AsymmetricKeypair keyPair = new BlockchainKeypair(pubKey, privKey); - BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); + BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); - // 注册 - txTemp.users().register(user.getIdentity()); + // 注册 + txTemp.users().register(user.getIdentity()); - // TX 准备就绪; - PreparedTransaction prepTx = txTemp.prepare(); + // 定义角色权限; + txTemp.security().roles().configure("MANAGER") + .enable(LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT) + .enable(TransactionPermission.CONTRACT_OPERATION); + txTemp.security().authorziations().forUser(user.getIdentity()).authorize("MANAGER"); - // 使用私钥进行签名; - prepTx.sign(keyPair); + // TX 准备就绪; + PreparedTransaction prepTx = txTemp.prepare(); - // 提交交易; - TransactionResponse transactionResponse = prepTx.commit(); + // 使用私钥进行签名; + prepTx.sign(keyPair); - ConsoleUtils.info("register user complete, result is [%s]", transactionResponse.isSuccess()); - } + // 提交交易; + TransactionResponse transactionResponse = prepTx.commit(); + + ConsoleUtils.info("register user complete, result is [%s]", transactionResponse.isSuccess()); + } } \ No newline at end of file diff --git a/source/test/test-integration/src/test/resources/ledger_init_test.init b/source/test/test-integration/src/test/resources/ledger_init_test.init index 4c4bf2d9..4253311e 100644 --- a/source/test/test-integration/src/test/resources/ledger_init_test.init +++ b/source/test/test-integration/src/test/resources/ledger_init_test.init @@ -8,6 +8,48 @@ ledger.name=TEST-LEDGER #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 +#----------------------------------------------- +# 初始的角色名称列表;可选项; +# 角色名称不区分大小写,最长不超过20个字符;多个角色名称之间用半角的逗点“,”分隔; +# 系统会预置一个默认角色“DEFAULT”,所有未指定角色的用户都以赋予该角色的权限;若初始化时未配置默认角色的权限,则为默认角色分配所有权限; +# +# 注:如果声明了角色,但未声明角色对应的权限清单,这会忽略该角色的初始化; +# +security.roles=DEFAULT, ADMIN, MANAGER, GUEST + +# 赋予角色的账本权限清单;可选项; +# 可选的权限如下; +# AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, +# REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, +# SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +# APPROVE_TX, CONSENSUS_TX +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT + +# 赋予角色的交易权限清单;可选项; +# 可选的权限如下; +# DIRECT_OPERATION, CONTRACT_OPERATION +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 其它角色的配置示例; +# 系统管理员角色:只能操作全局性的参数配置和用户注册,只能执行直接操作指令; +security.role.ADMIN.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER +security.role.ADMIN.tx-privileges=DIRECT_OPERATION + +# 业务主管角色:只能够执行账本数据相关的操作,包括注册用户、注册数据账户、注册合约、升级合约、写入数据等;能够执行直接操作指令和调用合约; +security.role.MANAGER.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +security.role.MANAGER.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 访客角色:不具备任何的账本权限,只有数据读取的操作;也只能够通过调用合约来读取数据; +security.role.GUEST.ledger-privileges= +security.role.GUEST.tx-privileges=CONTRACT_OPERATION + + + +#----------------------------------------------- #共识服务提供者;必须; consensus.service-provider=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider @@ -18,6 +60,12 @@ consensus.conf=classpath:bftsmart.config crypto.service-providers=com.jd.blockchain.crypto.service.classic.ClassicCryptoService, \ com.jd.blockchain.crypto.service.sm.SMCryptoService +#从存储中加载账本数据时,是否校验哈希;可选; +crypto.verify-hash=true + +#哈希算法; +crypto.hash-algorithm=SHA256 + #参与方的个数,后续以 cons_parti.id 分别标识每一个参与方的配置; cons_parti.count=4 @@ -27,6 +75,10 @@ cons_parti.0.name=jd.com cons_parti.0.pubkey-path=keys/jd-com.pub #第0个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.0.pubkey=3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9 +#第0个参与方的角色清单;可选项; +cons_parti.0.roles=ADMIN, MANAGER +#第0个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.0.roles-policy=UNION #第0个参与方的共识服务的主机地址; cons_parti.0.consensus.host=127.0.0.1 #第0个参与方的共识服务的端口; @@ -46,6 +98,10 @@ cons_parti.1.name=at.com cons_parti.1.pubkey-path=keys/at-com.pub #第1个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.1.pubkey=3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX +#第1个参与方的角色清单;可选项; +cons_parti.1.roles=MANAGER +#第1个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.1.roles-policy=UNION #第1个参与方的共识服务的主机地址; cons_parti.1.consensus.host=127.0.0.1 #第1个参与方的共识服务的端口; @@ -65,6 +121,10 @@ cons_parti.2.name=bt.com cons_parti.2.pubkey-path=keys/bt-com.pub #第2个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.2.pubkey=3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x +#第2个参与方的角色清单;可选项; +cons_parti.2.roles=MANAGER +#第2个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.2.roles-policy=UNION #第2个参与方的共识服务的主机地址; cons_parti.2.consensus.host=127.0.0.1 #第2个参与方的共识服务的端口; @@ -84,6 +144,10 @@ cons_parti.3.name=xt.com cons_parti.3.pubkey-path=keys/xt-com.pub #第3个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.3.pubkey=3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk +#第3个参与方的角色清单;可选项; +cons_parti.3.roles=GUEST +#第3个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.3.roles-policy=INTERSECT #第3个参与方的共识服务的主机地址; cons_parti.3.consensus.host=127.0.0.1 #第3个参与方的共识服务的端口; diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java index 3f2c10a6..79773915 100644 --- a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/ArrayUtils.java @@ -9,6 +9,7 @@ import java.util.*; */ public abstract class ArrayUtils { private ArrayUtils() { + } public static T[] singleton(T obj, Class clazz) { From ef901c6518b929b0e01942814b793044d12a3a03 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 9 Sep 2019 10:51:30 +0800 Subject: [PATCH 086/124] temp; --- .../src/main/java/com/jd/blockchain/consts/DataCodes.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index bf262ef0..26b52692 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -100,8 +100,8 @@ public interface DataCodes { // public static final int METADATA_CONSENSUS_SETTING = 0x631; // // public static final int METADATA_PARTICIPANT_INFO = 0x640; - - public static final int METADATA_CRYPTO_SETTING = 0x642; +// +// public static final int METADATA_CRYPTO_SETTING = 0x642; // public static final int METADATA_CONSENSUS_NODE = 0x630; From 6954e4eea009687b18f6090e4fdca61ebdae565c Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 9 Sep 2019 13:52:42 +0800 Subject: [PATCH 087/124] temp; --- .../handles/ParticipantRegisterOperationHandle.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java index d0e47e52..ece04036 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java @@ -40,11 +40,18 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { // metadata.setSetting(ledgerSetting); // metadata.setViewId(metadata.getViewId() + 1); - //reg participant as user - dataset.getUserAccountSet().register(identityData.getAddress(), pubKey); + + +// //reg participant as user +// dataset.getUserAccountSet().register(identityData.getAddress(), pubKey); //add new participant as consensus node adminAccount.addParticipant(participantNode); + + // Build UserRegisterOperation; + UserRegisterOperation userRegOp = null;// + handleContext.handle(userRegOp); + return null; } From 8db11f40a8851303591f0dd1de4f0c07d8569a60 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 9 Sep 2019 21:51:53 +0800 Subject: [PATCH 088/124] Fixed the security risk of deserialization; --- .../binary/BinarySerializeUtils.java | 8 ++--- .../binary/FilteredObjectInputStream.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/FilteredObjectInputStream.java diff --git a/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/BinarySerializeUtils.java b/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/BinarySerializeUtils.java index f0e2edf2..d1b1439d 100644 --- a/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/BinarySerializeUtils.java +++ b/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/BinarySerializeUtils.java @@ -4,7 +4,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; @@ -46,9 +45,10 @@ public class BinarySerializeUtils { @SuppressWarnings("unchecked") public static T deserialize(InputStream in) { try { - ObjectInputStream objIn = new ObjectInputStream(in); - Object obj = objIn.readObject(); - return (T) obj; + try(FilteredObjectInputStream objIn = new FilteredObjectInputStream(in)){ + Object obj = objIn.readObject(); + return (T) obj; + } } catch (IOException e) { throw new RuntimeIOException(e.getMessage(), e); } catch (ClassNotFoundException e) { diff --git a/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/FilteredObjectInputStream.java b/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/FilteredObjectInputStream.java new file mode 100644 index 00000000..897aa115 --- /dev/null +++ b/source/utils/utils-serialize/src/main/java/com/jd/blockchain/utils/serialize/binary/FilteredObjectInputStream.java @@ -0,0 +1,35 @@ +package com.jd.blockchain.utils.serialize.binary; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.util.HashSet; +import java.util.Set; + +public class FilteredObjectInputStream extends ObjectInputStream { + + private static final Set classBlacklist = new HashSet(); + + /** + * 把指定类型加入禁止反序列化的类型黑名单; + * + * @param className + */ + public static void addBlackList(String className) { + classBlacklist.add(className); + } + + public FilteredObjectInputStream(InputStream in) throws IOException { + super(in); + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + if (classBlacklist.contains(desc.getName())) { + throw new SecurityException("Class["+desc.getName()+"] is forbidden to deserialize because it is in the blacklist!"); + } + return super.resolveClass(desc); + } + +} From 4b48e9a22b46c66d5b9872d7d16ad87aae3c147c Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 10 Sep 2019 11:28:14 +0800 Subject: [PATCH 089/124] Initial revision of the code merger --- .../DefaultOperationHandleRegisteration.java | 13 +- .../ledger/core/LedgerAdminAccount.java | 365 ------------------ .../ledger/core/LedgerAdminDataset.java | 12 + .../ParticipantRegisterOperationHandle.java | 60 ++- ...ParticipantStateUpdateOperationHandle.java | 45 +-- .../DefaultOperationHandleRegisteration.java | 61 --- .../jd/blockchain/ledger/ParticipantInfo.java | 6 +- .../ledger/ParticipantInfoData.java | 14 +- .../sdk/converters/ClientResolveUtil.java | 26 +- .../SDK_GateWay_Participant_Regist_Test_.java | 6 +- ...ateWay_Participant_State_Update_Test_.java | 4 +- .../com/jd/blockchain/intgr/perf/Utils.java | 2 +- .../jd/blockchain/intgr/IntegrationBase.java | 4 +- .../intgr/IntegrationTest4Bftsmart.java | 12 +- .../blockchain/intgr/IntegrationTest4MQ.java | 12 +- 15 files changed, 92 insertions(+), 550 deletions(-) delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java delete mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java index 6dd34e0a..5e156ec3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java @@ -6,18 +6,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.jd.blockchain.ledger.core.handles.*; import org.springframework.stereotype.Component; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; -import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; -import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; -import com.jd.blockchain.ledger.core.handles.JVMContractEventSendOperationHandle; -import com.jd.blockchain.ledger.core.handles.LedgerInitOperationHandle; -import com.jd.blockchain.ledger.core.handles.RolesConfigureOperationHandle; -import com.jd.blockchain.ledger.core.handles.UserAuthorizeOperationHandle; -import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; import com.jd.blockchain.transaction.ContractCodeDeployOpTemplate; import com.jd.blockchain.transaction.ContractEventSendOpTemplate; import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; @@ -50,6 +43,10 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis registerDefaultHandle(new ContractCodeDeployOperationHandle()); registerDefaultHandle(new JVMContractEventSendOperationHandle()); + + registerDefaultHandle(new ParticipantRegisterOperationHandle()); + + registerDefaultHandle(new ParticipantStateUpdateOperationHandle()); } private static void registerDefaultHandle(OperationHandle handle) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java deleted file mode 100644 index 87219e84..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminAccount.java +++ /dev/null @@ -1,365 +0,0 @@ -package com.jd.blockchain.ledger.core; - -import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.LedgerSetting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.Crypto; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.HashFunction; -import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.storage.service.ExPolicyKVStorage; -import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; -import com.jd.blockchain.storage.service.VersioningKVStorage; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.Transactional; - -public class LedgerAdminAccount implements Transactional, LedgerAdministration { - - static { - DataContractRegistry.register(LedgerMetadata.class); - } - - private static Logger LOGGER = LoggerFactory.getLogger(LedgerAdminAccount.class); - - public static final String LEDGER_META_PREFIX = "MTA" + LedgerConsts.KEY_SEPERATOR; - public static final String LEDGER_PARTICIPANT_PREFIX = "PAR" + LedgerConsts.KEY_SEPERATOR; - public static final String LEDGER_PRIVILEGE_PREFIX = "PVL" + LedgerConsts.KEY_SEPERATOR; - - private final Bytes metaPrefix; - private final Bytes privilegePrefix; - - private LedgerMetadata origMetadata; - - private LedgerMetadataImpl metadata; - - /** - * 原来的账本设置; - * - *
        - * 对 LedgerMetadata 修改的新配置不能立即生效,需要达成共识后,在下一次区块计算中才生效; - */ - private LedgerSetting previousSetting; - - /** - * 账本的参与节点; - */ - private ParticipantDataSet participants; - - // /** - // * 账本的全局权限设置; - // */ - // private PrivilegeDataSet privileges; - - private ExPolicyKVStorage settingsStorage; - - private HashDigest adminAccountHash; - - private boolean readonly; - - private boolean updated; - - public HashDigest getHash() { - return adminAccountHash; - } - - public boolean isReadonly() { - return readonly; - } - - /** - * 初始化账本的管理账户; - * - *
        - * - * 只在新建账本时调用此方法; - * - * @param ledgerSeed - * @param setting - * @param partiList - * @param exPolicyStorage - * @param versioningStorage - */ - public LedgerAdminAccount(LedgerInitSetting initSetting, String keyPrefix, ExPolicyKVStorage exPolicyStorage, - VersioningKVStorage versioningStorage) { - this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); - this.privilegePrefix = Bytes.fromString(keyPrefix + LEDGER_PRIVILEGE_PREFIX); - - ParticipantNode[] parties = initSetting.getConsensusParticipants(); - if (parties.length == 0) { - throw new LedgerException("No participant!"); - } - - // 检查参与者列表是否已经按照 id 升序排列,并且 id 不冲突; - // 注:参与者的 id 要求从 0 开始编号,顺序依次递增,不允许跳空; - for (int i = 0; i < parties.length; i++) { - // if (parties[i].getAddress() != i) { - // throw new LedgerException("The id of participant isn't match the order of the - // participant list!"); - // } - } - - // 初始化元数据; - this.metadata = new LedgerMetadataImpl(); - this.metadata.setSeed(initSetting.getLedgerSeed()); - // 新配置; - this.metadata.setting = new LedgerConfiguration(initSetting.getConsensusProvider(), - initSetting.getConsensusSettings(), initSetting.getCryptoSetting()); - this.previousSetting = new LedgerConfiguration(initSetting.getConsensusProvider(), - initSetting.getConsensusSettings(), initSetting.getCryptoSetting()); - this.adminAccountHash = null; - - // 基于原配置初始化参与者列表; - String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataSet(previousSetting.getCryptoSetting(), partiPrefix, exPolicyStorage, - versioningStorage); - - for (ParticipantNode p : parties) { - this.participants.addConsensusParticipant(p); - } - - // 初始化其它属性; - this.settingsStorage = exPolicyStorage; - this.readonly = false; - } - - public LedgerAdminAccount(HashDigest adminAccountHash, String keyPrefix, ExPolicyKVStorage kvStorage, - VersioningKVStorage versioningKVStorage, boolean readonly) { - this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); - this.privilegePrefix = Bytes.fromString(keyPrefix + LEDGER_PRIVILEGE_PREFIX); - this.settingsStorage = kvStorage; - this.readonly = readonly; - this.origMetadata = loadAndVerifySettings(adminAccountHash); - this.metadata = new LedgerMetadataImpl(origMetadata); - // 复制记录一份配置作为上一个区块的原始配置,该实例仅供读取,不做修改,也不会回写到存储; - this.previousSetting = new LedgerConfiguration(metadata.getSetting()); - this.adminAccountHash = adminAccountHash; - // this.privileges = new PrivilegeDataSet(metadata.getPrivilegesHash(), - // metadata.getSetting().getCryptoSetting(), - // PrefixAppender.prefix(LEDGER_PRIVILEGE_PREFIX, kvStorage), - // PrefixAppender.prefix(LEDGER_PRIVILEGE_PREFIX, versioningKVStorage), - // readonly); - - // this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), - // previousSetting.getCryptoSetting(), - // PrefixAppender.prefix(LEDGER_PARTICIPANT_PREFIX, kvStorage), - // PrefixAppender.prefix(LEDGER_PARTICIPANT_PREFIX, versioningKVStorage), - // readonly); - String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataSet(metadata.getParticipantsHash(), previousSetting.getCryptoSetting(), - partiPrefix, kvStorage, versioningKVStorage, readonly); - } - - private LedgerMetadata loadAndVerifySettings(HashDigest adminAccountHash) { - // String base58Hash = adminAccountHash.toBase58(); - // String key = encodeMetadataKey(base58Hash); - Bytes key = encodeMetadataKey(adminAccountHash); - byte[] bytes = settingsStorage.get(key); - HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); - if (!hashFunc.verify(adminAccountHash, bytes)) { - LOGGER.error("The hash verification of ledger settings fail! --[HASH=" + key + "]"); - throw new LedgerException("The hash verification of ledger settings fail!"); - } - return deserializeMetadata(bytes); - } - - private Bytes encodeMetadataKey(HashDigest metadataHash) { - // return LEDGER_META_PREFIX + metadataHash; - // return metaPrefix + metadataHash; - return metaPrefix.concat(metadataHash); - } - - /* - * (non-Javadoc) - * - * @see com.jd.blockchain.ledger.core.LedgerAdministration#getMetadata() - */ - @Override - public LedgerMetadata getMetadata() { - return metadata; - } - - /** - * 返回原来的账本配置; - * - *
        - * 此方法总是返回从上一个区块加载的账本配置,即时调用 {@link #setLedgerSetting(LedgerSetting)} 做出了新的更改; - * - * @return - */ - public LedgerSetting getPreviousSetting() { - return previousSetting; - } - - /** - * 返回当前设置的账本配置; - * - * @return - */ - public LedgerSetting getSetting() { - return metadata.getSetting(); - } - - /** - * 更新账本配置; - * - * @param ledgerSetting - */ - public void setLedgerSetting(LedgerSetting ledgerSetting) { - if (readonly) { - throw new IllegalArgumentException("This merkle dataset is readonly!"); - } - metadata.setSetting(ledgerSetting); - } - - @Override - public long getParticipantCount() { - return participants.getParticipantCount(); - } - - // /* - // * (non-Javadoc) - // * - // * @see - // * - // com.jd.blockchain.ledger.core.LedgerAdministration#getParticipant(java.lang. - // * String) - // */ - // @Override - // public ParticipantNode getParticipant(int id) { - // return participants.getParticipant(id); - // } - - @Override - public ParticipantNode[] getParticipants() { - return participants.getParticipants(); - } - - /** - * 加入新的参与方; 如果指定的参与方已经存在,则引发 LedgerException 异常; - * - * @param participant - */ - public void addParticipant(ParticipantNode participant) { - participants.addConsensusParticipant(participant); - } - - /** - * 更新参与方的状态参数; - * - * @param participant - */ - public void updateParticipant(ParticipantNode participant) { - participants.updateConsensusParticipant(participant); - } - - @Override - public boolean isUpdated() { - return updated || participants.isUpdated(); - } - - @Override - public void commit() { - if (!isUpdated()) { - return; - } - participants.commit(); - - metadata.setParticipantsHash(participants.getRootHash()); - - // 基于之前的密码配置来计算元数据的哈希; - byte[] metadataBytes = serializeMetadata(metadata); - HashFunction hashFunc = Crypto - .getHashFunction(previousSetting.getCryptoSetting().getHashAlgorithm()); - HashDigest metadataHash = hashFunc.hash(metadataBytes); - if (adminAccountHash == null || !adminAccountHash.equals(metadataHash)) { - // update modify; - // String base58MetadataHash = metadataHash.toBase58(); - // String metadataKey = encodeMetadataKey(base58MetadataHash); - Bytes metadataKey = encodeMetadataKey(metadataHash); - - boolean nx = settingsStorage.set(metadataKey, metadataBytes, ExPolicy.NOT_EXISTING); - if (!nx) { - // 有可能发生了并发写入冲突,不同的节点都向同一个存储服务器上写入数据; - // throw new LedgerException( - // "Ledger metadata already exist! --[LedgerMetadataHash=" + base58MetadataHash - // + "]"); - // LOGGER.warn("Ledger metadata already exist! --[MetadataHash=" + - // base58MetadataHash + "]"); - } - - adminAccountHash = metadataHash; - } - - updated = false; - } - - private LedgerMetadata deserializeMetadata(byte[] bytes) { - return BinaryProtocol.decode(bytes); - } - - private byte[] serializeMetadata(LedgerMetadataImpl config) { - return BinaryProtocol.encode(config, LedgerMetadata.class); - } - - @Override - public void cancel() { - if (!isUpdated()) { - return; - } - participants.cancel(); - metadata = new LedgerMetadataImpl(origMetadata); - } - - public static class LedgerMetadataImpl implements LedgerMetadata { - - private byte[] seed; - - private LedgerSetting setting; - - private HashDigest participantsHash; - - public LedgerMetadataImpl() { - } - - public LedgerMetadataImpl(LedgerMetadata metadata) { - this.seed = metadata.getSeed(); - this.setting = metadata.getSetting(); - this.participantsHash = metadata.getParticipantsHash(); - } - - @Override - public byte[] getSeed() { - return seed; - } - - @Override - public LedgerSetting getSetting() { - return setting; - } - - @Override - public HashDigest getParticipantsHash() { - return participantsHash; - } - - public void setSeed(byte[] seed) { - this.seed = seed; - } - - public void setSetting(LedgerSetting setting) { - // copy a new instance; - this.setting = new LedgerConfiguration(setting); - } - - public void setParticipantsHash(HashDigest participantsHash) { - this.participantsHash = participantsHash; - } - } - -} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index c4961b12..9c794f82 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -268,6 +268,8 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, * * @return */ + + @Override public LedgerSettings getSettings() { return settings; } @@ -309,6 +311,16 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, participants.addConsensusParticipant(participant); } + + /** + * 更新参与方的状态参数; + * + * @param participant + */ + public void updateParticipant(ParticipantNode participant) { + participants.updateConsensusParticipant(participant); + } + @Override public boolean isUpdated() { return updated || participants.isUpdated() || rolePrivileges.isUpdated() || userRoles.isUpdated(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java index ece04036..434e8c4a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; @@ -6,66 +6,51 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; import com.jd.blockchain.utils.Bytes; -public class ParticipantRegisterOperationHandle implements OperationHandle { - @Override - public BytesValue process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { - ParticipantRegisterOperation participantRegOp = (ParticipantRegisterOperation) op; +public class ParticipantRegisterOperationHandle extends AbstractLedgerOperationHandle { + public ParticipantRegisterOperationHandle() { + super(ParticipantRegisterOperation.class); + } - LedgerAdminAccount adminAccount = dataset.getAdminAccount(); + @Override + protected void doProcess(ParticipantRegisterOperation op, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { - ParticipantInfo participantInfo = participantRegOp.getParticipantInfo(); + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpointPermission(LedgerPermission.REGISTER_PARTICIPANT, MultiIDsPolicy.AT_LEAST_ONE); -// ConsensusProvider provider = ConsensusProviders.getProvider(adminAccount.getSetting().getConsensusProvider()); + ParticipantRegisterOperation participantRegOp = (ParticipantRegisterOperation) op; - ParticipantNode participantNode = new PartNode((int)(adminAccount.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTED); + LedgerAdminDataset adminAccountDataSet = newBlockDataset.getAdminDataset(); -// LedgerAdminAccount.LedgerMetadataImpl metadata = (LedgerAdminAccount.LedgerMetadataImpl) adminAccount.getMetadata(); + ParticipantInfo participantInfo = participantRegOp.getParticipantInfo(); + ParticipantNode participantNode = new PartNode((int)(adminAccountDataSet.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTED); PubKey pubKey = participantNode.getPubKey(); BlockchainIdentityData identityData = new BlockchainIdentityData(pubKey); - //update consensus setting -// Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(metadata.getSetting().getConsensusSetting(), participantInfo); - -// LedgerSetting ledgerSetting = new LedgerConfiguration(adminAccount.getSetting().getConsensusProvider(), -// newConsensusSettings, metadata.getSetting().getCryptoSetting()); - -// metadata.setSetting(ledgerSetting); -// metadata.setViewId(metadata.getViewId() + 1); - - - // //reg participant as user // dataset.getUserAccountSet().register(identityData.getAddress(), pubKey); //add new participant as consensus node - adminAccount.addParticipant(participantNode); - - // Build UserRegisterOperation; + adminAccountDataSet.addParticipant(participantNode); + + // Build UserRegisterOperation; UserRegisterOperation userRegOp = null;// handleContext.handle(userRegOp); - - - return null; - } - - @Override - public boolean support(Class operationType) { - return ParticipantRegisterOperation.class.isAssignableFrom(operationType); } private static class PartNode implements ParticipantNode { private int id; - private String address; + private Bytes address; private String name; @@ -77,7 +62,7 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { this.id = id; this.name = name; this.pubKey = pubKey; - this.address = AddressEncoding.generateAddress(pubKey).toBase58(); + this.address = AddressEncoding.generateAddress(pubKey); this.participantNodeState = participantNodeState; } @@ -87,7 +72,7 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { } @Override - public String getAddress() { + public Bytes getAddress() { return address; } @@ -107,4 +92,5 @@ public class ParticipantRegisterOperationHandle implements OperationHandle { } } + } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java index 0976651f..0c4fe9af 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java @@ -1,4 +1,4 @@ -package com.jd.blockchain.ledger.core.impl.handles; +package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; @@ -6,28 +6,30 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; import com.jd.blockchain.utils.Bytes; -public class ParticipantStateUpdateOperationHandle implements OperationHandle { - @Override - public boolean support(Class operationType) { - return ParticipantStateUpdateOperation.class.isAssignableFrom(operationType); +public class ParticipantStateUpdateOperationHandle extends AbstractLedgerOperationHandle { + public ParticipantStateUpdateOperationHandle() { + super(ParticipantStateUpdateOperation.class); } @Override - public BytesValue process(Operation op, LedgerDataSet newBlockDataset, TransactionRequestContext requestContext, LedgerDataSet previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + protected void doProcess(ParticipantStateUpdateOperation op, LedgerDataset newBlockDataset, + TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, + OperationHandleContext handleContext, LedgerService ledgerService) { - ParticipantStateUpdateOperation stateUpdateOperation = (ParticipantStateUpdateOperation) op; + // 权限校验; + SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); + securityPolicy.checkEndpointPermission(LedgerPermission.REGISTER_PARTICIPANT, MultiIDsPolicy.AT_LEAST_ONE); - LedgerAdminAccount adminAccount = newBlockDataset.getAdminAccount(); + ParticipantStateUpdateOperation stateUpdateOperation = (ParticipantStateUpdateOperation) op; - ConsensusProvider provider = ConsensusProviders.getProvider(adminAccount.getSetting().getConsensusProvider()); + LedgerAdminDataset adminAccountDataSet = newBlockDataset.getAdminDataset(); - LedgerAdminAccount.LedgerMetadataImpl metadata = (LedgerAdminAccount.LedgerMetadataImpl) adminAccount.getMetadata(); + ConsensusProvider provider = ConsensusProviders.getProvider(adminAccountDataSet.getSettings().getConsensusProvider()); - ParticipantNode[] participants = adminAccount.getParticipants(); + ParticipantNode[] participants = adminAccountDataSet.getParticipants(); ParticipantNode participantNode = null; @@ -39,25 +41,24 @@ public class ParticipantStateUpdateOperationHandle implements OperationHandle { } //update consensus setting - ParticipantInfo participantInfo = new ParticipantInfoData("", participantNode.getName(), participantNode.getPubKey(), stateUpdateOperation.getStateUpdateInfo().getNetworkAddress()); + ParticipantInfo participantInfo = new ParticipantInfoData(participantNode.getName(), participantNode.getPubKey(), stateUpdateOperation.getStateUpdateInfo().getNetworkAddress()); - Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(metadata.getSetting().getConsensusSetting(), participantInfo); + Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(adminAccountDataSet.getSettings().getConsensusSetting(), participantInfo); - LedgerSetting ledgerSetting = new LedgerConfiguration(adminAccount.getSetting().getConsensusProvider(), - newConsensusSettings, metadata.getSetting().getCryptoSetting()); + LedgerSettings ledgerSetting = new LedgerConfiguration(adminAccountDataSet.getSettings().getConsensusProvider(), + newConsensusSettings, adminAccountDataSet.getPreviousSetting().getCryptoSetting()); - metadata.setSetting(ledgerSetting); + adminAccountDataSet.setLedgerSetting(ledgerSetting); - adminAccount.updateParticipant(participantNode); + adminAccountDataSet.updateParticipant(participantNode); - return null; } private static class PartNode implements ParticipantNode { private int id; - private String address; + private Bytes address; private String name; @@ -69,7 +70,7 @@ public class ParticipantStateUpdateOperationHandle implements OperationHandle { this.id = id; this.name = name; this.pubKey = pubKey; - this.address = AddressEncoding.generateAddress(pubKey).toBase58(); + this.address = AddressEncoding.generateAddress(pubKey); this.participantNodeState = participantNodeState; } @@ -79,7 +80,7 @@ public class ParticipantStateUpdateOperationHandle implements OperationHandle { } @Override - public String getAddress() { + public Bytes getAddress() { return address; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java deleted file mode 100644 index fc888b29..00000000 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.jd.blockchain.ledger.core.impl; - -import java.util.ArrayList; -import java.util.List; - -import com.jd.blockchain.ledger.core.impl.handles.*; -import org.springframework.stereotype.Component; - -import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.ledger.core.OperationHandle; - -@Component -public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { - - private List opHandles = new ArrayList<>(); - - public DefaultOperationHandleRegisteration() { - initDefaultHandles(); - } - - /** - * 针对不采用bean依赖注入的方式来处理; - */ - private void initDefaultHandles() { - opHandles.add(new DataAccountKVSetOperationHandle()); - opHandles.add(new DataAccountRegisterOperationHandle()); - opHandles.add(new UserRegisterOperationHandle()); - opHandles.add(new ParticipantRegisterOperationHandle()); - opHandles.add(new ContractCodeDeployOperationHandle()); - opHandles.add(new JVMContractEventSendOperationHandle()); - opHandles.add(new ParticipantStateUpdateOperationHandle()); - } - - /** - * 以最高优先级插入一个操作处理器; - * - * @param handle - */ - public void insertAsTopPriority(OperationHandle handle) { - opHandles.remove(handle); - opHandles.add(0, handle); - } - - /* - * (non-Javadoc) - * - * @see - * com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration#getHandle( - * java.lang.Class) - */ - @Override - public OperationHandle getHandle(Class operationType) { - for (OperationHandle handle : opHandles) { - if (handle.support(operationType)) { - return handle; - } - } - throw new LedgerException("Unsupported operation type[" + operationType.getName() + "]!"); - } - -} diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java index 07f6e1d0..c366fdd3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfo.java @@ -18,12 +18,12 @@ import com.jd.blockchain.utils.net.NetworkAddress; public interface ParticipantInfo { /** - * regist or unregist; + * regist or unregist, temporarily not available to users * * @return */ - @DataField(order = 0, primitiveType = PrimitiveType.TEXT) - String getFlag(); +// @DataField(order = 0, primitiveType = PrimitiveType.TEXT) +// String getFlag(); /** * 参与者名称; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java index b41bb079..a5a6bc0a 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java @@ -18,24 +18,14 @@ public class ParticipantInfoData implements ParticipantInfo { private NetworkAddress networkAddress; - private String flag;//代表注册参与方或者删除参与方 +// private String flag;//代表注册参与方或者删除参与方 - public ParticipantInfoData(String flag, String name, PubKey pubKey, NetworkAddress networkAddress) { - this.flag = flag; + public ParticipantInfoData(String name, PubKey pubKey, NetworkAddress networkAddress) { this.name = name; this.pubKey = pubKey; this.networkAddress = networkAddress; } - @Override - public String getFlag() { - return flag; - } - - public void setFlag(String flag) { - this.flag = flag; - } - @Override public String getName() { return name; diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java index 460bdc9b..e4aea5a0 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/converters/ClientResolveUtil.java @@ -10,27 +10,13 @@ package com.jd.blockchain.sdk.converters; import java.lang.reflect.Field; +import com.jd.blockchain.ledger.*; import org.apache.commons.codec.binary.Base64; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.BlockchainIdentityData; -import com.jd.blockchain.ledger.BytesData; -import com.jd.blockchain.ledger.BytesValue; -import com.jd.blockchain.ledger.BytesValueEncoding; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.DataType; -import com.jd.blockchain.ledger.KVDataEntry; -import com.jd.blockchain.ledger.LedgerInitOperation; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.transaction.ContractCodeDeployOpTemplate; import com.jd.blockchain.transaction.ContractEventSendOpTemplate; import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; @@ -214,8 +200,7 @@ public class ClientResolveUtil { JSONObject pubKeyObj = currConsensusParticipant.getJSONObject("pubKey"); String pubKeyBase58 = pubKeyObj.getString("value"); // 生成ParticipantNode对象 - ParticipantCertData participantCertData = new ParticipantCertData(id, addressBase58, name, new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes())); - new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes())); + ParticipantCertData participantCertData = new ParticipantCertData(id, address, name, new PubKey(Bytes.fromBase58(pubKeyBase58).toBytes())); participantNodes[i] = participantCertData; } ledgerInitSettingData.setConsensusParticipants(participantNodes); @@ -309,7 +294,7 @@ public class ClientResolveUtil { this.pubKey = participantNode.getPubKey(); } - public ParticipantCertData(int id, String address, String name, PubKey pubKey) { + public ParticipantCertData(int id, Bytes address, String name, PubKey pubKey) { this.id = id; this.address = address; this.name = name; @@ -332,14 +317,11 @@ public class ClientResolveUtil { return pubKey; } + @Override public int getId() { return id; } - public void setId(int id) { - this.id = id; - } - @Override public ParticipantNodeState getParticipantNodeState() { return participantNodeState; diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java index ec523269..f0bd032e 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java @@ -72,9 +72,9 @@ public class SDK_GateWay_Participant_Regist_Test_ { //existed signer AsymmetricKeypair keyPair = new BlockchainKeypair(pubKey, privKey); - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV, SDKDemo_Constant.PASSWORD); + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV, SDKDemo_Constant.PASSWORD); - PubKey pubKey = KeyGenCommand.decodePubKey(PUB); + PubKey pubKey = KeyGenUtils.decodePubKey(PUB); System.out.println("Address = "+AddressEncoding.generateAddress(pubKey)); @@ -82,7 +82,7 @@ public class SDK_GateWay_Participant_Regist_Test_ { NetworkAddress networkAddress = new NetworkAddress("127.0.0.1", 20000); - ParticipantInfo participantInfo = new ParticipantInfoData("add","Peer4", user.getPubKey(), networkAddress); + ParticipantInfo participantInfo = new ParticipantInfoData("Peer4", user.getPubKey(), networkAddress); // 注册参与方 txTemp.participants().register(participantInfo); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java index ea04ef69..ab2468be 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java @@ -71,9 +71,9 @@ public class SDK_GateWay_Participant_State_Update_Test_ { //existed signer AsymmetricKeypair keyPair = new BlockchainKeypair(pubKey, privKey); - PrivKey privKey = KeyGenCommand.decodePrivKeyWithRawPassword(PRIV, SDKDemo_Constant.PASSWORD); + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV, SDKDemo_Constant.PASSWORD); - PubKey pubKey = KeyGenCommand.decodePubKey(PUB); + PubKey pubKey = KeyGenUtils.decodePubKey(PUB); System.out.println("Address = "+AddressEncoding.generateAddress(pubKey)); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index 663cef6d..1f666572 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -88,7 +88,7 @@ public class Utils { public static ParticipantNode[] loadParticipantNodes() { ParticipantNode[] participantNodes = new ParticipantNode[PUB_KEYS.length]; for (int i = 0; i < PUB_KEYS.length; i++) { - participantNodes[i] = new PartNode(i, KeyGenCommand.decodePubKey(PUB_KEYS[i])); + participantNodes[i] = new PartNode(i, KeyGenUtils.decodePubKey(PUB_KEYS[i]), ParticipantNodeState.CONSENSUSED); } return participantNodes; } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index b4550ee5..f8a7cfed 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -175,7 +175,7 @@ public class IntegrationBase { // 定义交易; TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - ParticipantInfoData participantInfoData = new ParticipantInfoData("add", "peer4", participant.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); + ParticipantInfoData participantInfoData = new ParticipantInfoData("peer4", participant.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); txTpl.participants().register(participantInfoData); @@ -201,7 +201,7 @@ public class IntegrationBase { // 定义交易; TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - ParticipantInfoData participantInfoData = new ParticipantInfoData("add", "peer4", participantKeyPair.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); + ParticipantInfoData participantInfoData = new ParticipantInfoData("peer4", participantKeyPair.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(participantKeyPair.getPubKey(), ParticipantNodeState.CONSENSUSED, participantInfoData.getNetworkAddress()); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index ec352082..20a841a4 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -151,7 +151,7 @@ public class IntegrationTest4Bftsmart { } } - long participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + long participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); @@ -162,28 +162,28 @@ public class IntegrationTest4Bftsmart { participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); } - participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); - BftsmartConsensusSettings consensusSettings = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount().getSetting().getConsensusSetting().toBytes()); + BftsmartConsensusSettings consensusSettings = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminInfo().getSettings().getConsensusSetting().toBytes()); System.out.printf("update participant state before ,old consensus env node num = %d\r\n", consensusSettings.getNodes().length); for (int i = 0; i < participantCount; i++) { - System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); } if (isParticipantStateUpdate) { IntegrationBase.testSDK_UpdateParticipantState(adminKey, new BlockchainKeypair(participantResponse.getKeyPair().getPubKey(), participantResponse.getKeyPair().getPrivKey()), ledgerHash, blockchainService); } - BftsmartConsensusSettings consensusSettingsNew = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getSetting().getConsensusSetting().toBytes()); + BftsmartConsensusSettings consensusSettingsNew = (BftsmartConsensusSettings) ConsensusProviders.getProvider(BFTSMART_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getSettings().getConsensusSetting().toBytes()); System.out.printf("update participant state after ,new consensus env node num = %d\r\n", consensusSettingsNew.getNodes().length); for (int i = 0; i < participantCount; i++) { - System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); } try { diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index dedb4738..c018a855 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -147,7 +147,7 @@ public class IntegrationTest4MQ { integrationBase.testSDK_Contract(adminKey, ledgerHash, blockchainService,ledgerRepository); } - long participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + long participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); @@ -158,30 +158,30 @@ public class IntegrationTest4MQ { participantResponse = IntegrationBase.testSDK_RegisterParticipant(adminKey, ledgerHash, blockchainService); } - participantCount = ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); + participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); - MsgQueueConsensusSettings consensusSettings = (MsgQueueConsensusSettings) ConsensusProviders.getProvider(MQ_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount().getSetting().getConsensusSetting().toBytes()); + MsgQueueConsensusSettings consensusSettings = (MsgQueueConsensusSettings) ConsensusProviders.getProvider(MQ_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminInfo().getSettings().getConsensusSetting().toBytes()); System.out.printf("update participant state before ,old consensus env node num = %d\r\n", consensusSettings.getNodes().length); for (int i = 0; i < participantCount; i++) { - System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); } if (isParticipantStateUpdate) { IntegrationBase.testSDK_UpdateParticipantState(adminKey, new BlockchainKeypair(participantResponse.getKeyPair().getPubKey(), participantResponse.getKeyPair().getPrivKey()), ledgerHash, blockchainService); } - BftsmartConsensusSettings consensusSettingsNew = (BftsmartConsensusSettings) ConsensusProviders.getProvider(MQ_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getSetting().getConsensusSetting().toBytes()); + BftsmartConsensusSettings consensusSettingsNew = (BftsmartConsensusSettings) ConsensusProviders.getProvider(MQ_PROVIDER).getSettingsFactory().getConsensusSettingsEncoder().decode(ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getSettings().getConsensusSetting().toBytes()); System.out.printf("update participant state after ,new consensus env node num = %d\r\n", consensusSettingsNew.getNodes().length); for (int i = 0; i < participantCount; i++) { - System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminAccount(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); + System.out.printf("part%d state = %d\r\n",i, ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipants()[i].getParticipantNodeState().CODE); } IntegrationBase.testConsistencyAmongNodes(ledgers); From 299a5b3b524735dd79d293331d0715782407e08d Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 10 Sep 2019 14:41:59 +0800 Subject: [PATCH 090/124] the second revision of the code merger --- .../ParticipantRegisterOperationHandle.java | 16 +++------- ...ParticipantStateUpdateOperationHandle.java | 4 +-- .../ledger/ParticipantInfoData.java | 1 + .../ledger/ParticipantRegisterOperation.java | 11 +++++-- .../ParticipantStateUpdateOperation.java | 14 +++++++-- .../BlockchainOperationFactory.java | 10 ++++--- .../transaction/ClientOperator.java | 2 +- .../transaction/ParticipantOperator.java | 7 +++++ .../ParticipantRegisterOpTemplate.java | 27 +++++++++++++---- .../ParticipantRegisterOperationBuilder.java | 5 +++- ...rticipantRegisterOperationBuilderImpl.java | 7 +++-- .../ParticipantStateUpdateOpTemplate.java | 30 +++++++++++++++---- ...articipantStateUpdateOperationBuilder.java | 5 +++- ...cipantStateUpdateOperationBuilderImpl.java | 7 +++-- .../SDK_GateWay_Participant_Regist_Test_.java | 4 +-- ...ateWay_Participant_State_Update_Test_.java | 5 ++-- .../jd/blockchain/intgr/IntegrationBase.java | 8 ++--- 17 files changed, 112 insertions(+), 51 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java index 434e8c4a..6f783680 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java @@ -1,11 +1,10 @@ package com.jd.blockchain.ledger.core.handles; -import com.jd.blockchain.consensus.ConsensusProvider; -import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.transaction.UserRegisterOpTemplate; import com.jd.blockchain.utils.Bytes; @@ -27,22 +26,15 @@ public class ParticipantRegisterOperationHandle extends AbstractLedgerOperationH LedgerAdminDataset adminAccountDataSet = newBlockDataset.getAdminDataset(); - ParticipantInfo participantInfo = participantRegOp.getParticipantInfo(); + ParticipantInfo participantInfo = new ParticipantInfoData(participantRegOp.getParticipantName(), participantRegOp.getParticipantIdentity().getPubKey(), participantRegOp.getNetworkAddress()); ParticipantNode participantNode = new PartNode((int)(adminAccountDataSet.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTED); - PubKey pubKey = participantNode.getPubKey(); - - BlockchainIdentityData identityData = new BlockchainIdentityData(pubKey); - -// //reg participant as user -// dataset.getUserAccountSet().register(identityData.getAddress(), pubKey); - //add new participant as consensus node adminAccountDataSet.addParticipant(participantNode); - // Build UserRegisterOperation; - UserRegisterOperation userRegOp = null;// + // Build UserRegisterOperation, reg participant as user + UserRegisterOperation userRegOp = new UserRegisterOpTemplate(participantRegOp.getParticipantIdentity()); handleContext.handle(userRegOp); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java index 0c4fe9af..8969cfea 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java @@ -34,14 +34,14 @@ public class ParticipantStateUpdateOperationHandle extends AbstractLedgerOperati ParticipantNode participantNode = null; for(int i = 0; i < participants.length; i++) { - if (stateUpdateOperation.getStateUpdateInfo().getPubKey().equals(participants[i].getPubKey())) { + if (stateUpdateOperation.getParticipantIdentity().getPubKey().equals(participants[i].getPubKey())) { participantNode = new PartNode(participants[i].getId(), participants[i].getName(), participants[i].getPubKey(), ParticipantNodeState.CONSENSUSED); break; } } //update consensus setting - ParticipantInfo participantInfo = new ParticipantInfoData(participantNode.getName(), participantNode.getPubKey(), stateUpdateOperation.getStateUpdateInfo().getNetworkAddress()); + ParticipantInfo participantInfo = new ParticipantInfoData(participantNode.getName(), participantNode.getPubKey(), stateUpdateOperation.getNetworkAddress()); Bytes newConsensusSettings = provider.getSettingsFactory().getConsensusSettingsBuilder().updateSettings(adminAccountDataSet.getSettings().getConsensusSetting(), participantInfo); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java index a5a6bc0a..27eaa098 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantInfoData.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.net.NetworkAddress; +import org.omg.CORBA.PUBLIC_MEMBER; /** * 即将要注册的参与方的信息 diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java index 2762e924..a8ccf227 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java @@ -2,12 +2,19 @@ package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.utils.net.NetworkAddress; @DataContract(code= DataCodes.TX_OP_PARTICIPANT_REG) public interface ParticipantRegisterOperation extends Operation { - @DataField(order=1, refContract = true) - ParticipantInfo getParticipantInfo(); + @DataField(order = 0, primitiveType=PrimitiveType.TEXT) + String getParticipantName(); + @DataField(order = 1, refContract = true) + BlockchainIdentity getParticipantIdentity(); + + @DataField(order = 2, primitiveType = PrimitiveType.BYTES) + NetworkAddress getNetworkAddress(); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java index 1f656609..46cfea0e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java @@ -2,10 +2,20 @@ package com.jd.blockchain.ledger; import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.utils.net.NetworkAddress; @DataContract(code= DataCodes.TX_OP_PARTICIPANT_STATE_UPDATE) public interface ParticipantStateUpdateOperation extends Operation { - @DataField(order=1, refContract = true) - ParticipantStateUpdateInfo getStateUpdateInfo(); + + @DataField(order = 0, refContract = true) + BlockchainIdentity getParticipantIdentity(); + + @DataField(order = 1, primitiveType = PrimitiveType.BYTES) + NetworkAddress getNetworkAddress(); + + @DataField(order = 2, refEnum = true) + ParticipantNodeState getState(); + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index 317b6e85..c08cc0cb 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -4,8 +4,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.ledger.*; import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.net.NetworkAddress; /** * @author huanghaiquan @@ -288,8 +290,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private class ParticipantRegisterOperationBuilderFilter implements ParticipantRegisterOperationBuilder { @Override - public ParticipantRegisterOperation register(ParticipantInfo participantInfo) { - ParticipantRegisterOperation op = PARTICIPANT_REG_OP_BUILDER.register(participantInfo); + public ParticipantRegisterOperation register(String participantName, BlockchainIdentity participantIdentity, NetworkAddress networkAddress) { + ParticipantRegisterOperation op = PARTICIPANT_REG_OP_BUILDER.register(participantName, participantIdentity, networkAddress); operationList.add(op); return op; } @@ -297,8 +299,8 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe private class ParticipantStateUpdateOperationBuilderFilter implements ParticipantStateUpdateOperationBuilder { @Override - public ParticipantStateUpdateOperation update(ParticipantStateUpdateInfo stateUpdateInfo) { - ParticipantStateUpdateOperation op = PARTICIPANT_STATE_UPDATE_OP_BUILDER.update(stateUpdateInfo); + public ParticipantStateUpdateOperation update(BlockchainIdentity blockchainIdentity, NetworkAddress networkAddress, ParticipantNodeState participantNodeState) { + ParticipantStateUpdateOperation op = PARTICIPANT_STATE_UPDATE_OP_BUILDER.update(blockchainIdentity, networkAddress, participantNodeState); operationList.add(op); return op; } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java index 724e8d8a..cba4569e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ClientOperator.java @@ -7,6 +7,6 @@ package com.jd.blockchain.transaction; * */ public interface ClientOperator - extends SecurityOperator, UserOperator, DataAccountOperator, ContractOperator, EventOperator, ParticipantOperator, ParticipantStateOperator { + extends SecurityOperator, UserOperator, DataAccountOperator, ContractOperator, EventOperator, ParticipantOperator { } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java index 94237cc3..83c1524a 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantOperator.java @@ -8,4 +8,11 @@ public interface ParticipantOperator { * @return */ ParticipantRegisterOperationBuilder participants(); + + /** + * 参与方状态更新操作; + * + * @return + */ + ParticipantStateUpdateOperationBuilder states(); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java index 3eeaea53..2b5c608d 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java @@ -1,8 +1,11 @@ package com.jd.blockchain.transaction; import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.ParticipantInfo; import com.jd.blockchain.ledger.ParticipantRegisterOperation; +import com.jd.blockchain.utils.net.NetworkAddress; public class ParticipantRegisterOpTemplate implements ParticipantRegisterOperation { @@ -10,15 +13,29 @@ public class ParticipantRegisterOpTemplate implements ParticipantRegisterOperati DataContractRegistry.register(ParticipantRegisterOperation.class); } - private ParticipantInfo participantInfo; + private String participantName; + private BlockchainIdentity participantPubKey; + private NetworkAddress networkAddress; - public ParticipantRegisterOpTemplate(ParticipantInfo participantInfo) { + public ParticipantRegisterOpTemplate(String participantName, BlockchainIdentity participantPubKey, NetworkAddress networkAddress) { + this.participantName = participantName; + this.participantPubKey = participantPubKey; + this.networkAddress = networkAddress; - this.participantInfo = participantInfo; } @Override - public ParticipantInfo getParticipantInfo() { - return participantInfo; + public String getParticipantName() { + return participantName; + } + + @Override + public BlockchainIdentity getParticipantIdentity() { + return participantPubKey; + } + + @Override + public NetworkAddress getNetworkAddress() { + return networkAddress; } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java index 3776b691..26f54c5b 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilder.java @@ -1,7 +1,10 @@ package com.jd.blockchain.transaction; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.ParticipantInfo; import com.jd.blockchain.ledger.ParticipantRegisterOperation; +import com.jd.blockchain.utils.net.NetworkAddress; public interface ParticipantRegisterOperationBuilder { @@ -14,7 +17,7 @@ public interface ParticipantRegisterOperationBuilder { * * @return */ - ParticipantRegisterOperation register(ParticipantInfo participantInfo); + ParticipantRegisterOperation register(String participantName, BlockchainIdentity participantPubKey, NetworkAddress networkAddress); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java index 56339bfa..34b126d1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOperationBuilderImpl.java @@ -1,11 +1,14 @@ package com.jd.blockchain.transaction; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.ParticipantInfo; import com.jd.blockchain.ledger.ParticipantRegisterOperation; +import com.jd.blockchain.utils.net.NetworkAddress; public class ParticipantRegisterOperationBuilderImpl implements ParticipantRegisterOperationBuilder { @Override - public ParticipantRegisterOperation register(ParticipantInfo participantNode) { - return new ParticipantRegisterOpTemplate(participantNode); + public ParticipantRegisterOperation register(String participantName, BlockchainIdentity participantPubKey, NetworkAddress networkAddress) { + return new ParticipantRegisterOpTemplate(participantName, participantPubKey, networkAddress); } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java index 9a410a1e..abb3b28d 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java @@ -1,8 +1,10 @@ package com.jd.blockchain.transaction; import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.ParticipantNodeState; import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; +import com.jd.blockchain.utils.net.NetworkAddress; public class ParticipantStateUpdateOpTemplate implements ParticipantStateUpdateOperation { @@ -10,14 +12,30 @@ public class ParticipantStateUpdateOpTemplate implements ParticipantStateUpdateO DataContractRegistry.register(ParticipantStateUpdateOperation.class); } - private ParticipantStateUpdateInfo stateUpdateInfo; + private BlockchainIdentity blockchainIdentity; + private NetworkAddress networkAddress; + private ParticipantNodeState participantNodeState; - public ParticipantStateUpdateOpTemplate(ParticipantStateUpdateInfo stateUpdateInfo) { - this.stateUpdateInfo = stateUpdateInfo; + public ParticipantStateUpdateOpTemplate(BlockchainIdentity blockchainIdentity, NetworkAddress networkAddress, ParticipantNodeState participantNodeState) { + + this.blockchainIdentity = blockchainIdentity; + this.networkAddress = networkAddress; + this.participantNodeState = participantNodeState; + } + + + @Override + public BlockchainIdentity getParticipantIdentity() { + return blockchainIdentity; + } + + @Override + public NetworkAddress getNetworkAddress() { + return networkAddress; } @Override - public ParticipantStateUpdateInfo getStateUpdateInfo() { - return stateUpdateInfo; + public ParticipantNodeState getState() { + return participantNodeState; } } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java index afb66bff..d6c69dc3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilder.java @@ -1,7 +1,10 @@ package com.jd.blockchain.transaction; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.ParticipantNodeState; import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; +import com.jd.blockchain.utils.net.NetworkAddress; public interface ParticipantStateUpdateOperationBuilder { @@ -14,5 +17,5 @@ public interface ParticipantStateUpdateOperationBuilder { * * @return */ - ParticipantStateUpdateOperation update(ParticipantStateUpdateInfo stateUpdateInfo); + ParticipantStateUpdateOperation update(BlockchainIdentity blockchainIdentity, NetworkAddress networkAddress, ParticipantNodeState participantNodeState); } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java index 7c579fd7..9c84e181 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOperationBuilderImpl.java @@ -1,12 +1,15 @@ package com.jd.blockchain.transaction; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.ParticipantNodeState; import com.jd.blockchain.ledger.ParticipantStateUpdateInfo; import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; +import com.jd.blockchain.utils.net.NetworkAddress; public class ParticipantStateUpdateOperationBuilderImpl implements ParticipantStateUpdateOperationBuilder { @Override - public ParticipantStateUpdateOperation update(ParticipantStateUpdateInfo stateUpdateInfo) { - return new ParticipantStateUpdateOpTemplate(stateUpdateInfo); + public ParticipantStateUpdateOperation update(BlockchainIdentity blockchainIdentity, NetworkAddress networkAddress, ParticipantNodeState participantNodeState) { + return new ParticipantStateUpdateOpTemplate(blockchainIdentity, networkAddress, participantNodeState); } } diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java index f0bd032e..77157f0d 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_Regist_Test_.java @@ -82,10 +82,8 @@ public class SDK_GateWay_Participant_Regist_Test_ { NetworkAddress networkAddress = new NetworkAddress("127.0.0.1", 20000); - ParticipantInfo participantInfo = new ParticipantInfoData("Peer4", user.getPubKey(), networkAddress); - // 注册参与方 - txTemp.participants().register(participantInfo); + txTemp.participants().register("Peer4", user.getIdentity(), networkAddress); // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java index ab2468be..9a51657a 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java @@ -77,10 +77,11 @@ public class SDK_GateWay_Participant_State_Update_Test_ { System.out.println("Address = "+AddressEncoding.generateAddress(pubKey)); + BlockchainKeypair user = new BlockchainKeypair(pubKey, privKey); + NetworkAddress networkAddress = new NetworkAddress("127.0.0.1", 20000); - ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(pubKey, ParticipantNodeState.CONSENSUSED, networkAddress); - txTemp.states().update(stateUpdateInfo); + txTemp.states().update(user.getIdentity(),networkAddress, ParticipantNodeState.CONSENSUSED); // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index f8a7cfed..04c67892 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -175,9 +175,7 @@ public class IntegrationBase { // 定义交易; TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - ParticipantInfoData participantInfoData = new ParticipantInfoData("peer4", participant.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); - - txTpl.participants().register(participantInfoData); + txTpl.participants().register("peer4", new BlockchainIdentityData(participant.getPubKey()), new NetworkAddress("127.0.0.1", 20000)); // 签名; PreparedTransaction ptx = txTpl.prepare(); @@ -203,9 +201,7 @@ public class IntegrationBase { ParticipantInfoData participantInfoData = new ParticipantInfoData("peer4", participantKeyPair.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); - ParticipantStateUpdateInfo stateUpdateInfo = new ParticipantStateUpdateInfoData(participantKeyPair.getPubKey(), ParticipantNodeState.CONSENSUSED, participantInfoData.getNetworkAddress()); - - txTpl.states().update(stateUpdateInfo); + txTpl.states().update(new BlockchainIdentityData(participantInfoData.getPubKey()), participantInfoData.getNetworkAddress(), ParticipantNodeState.CONSENSUSED); // 签名; PreparedTransaction ptx = txTpl.prepare(); From 0bcc74e15f5639d31c81e08285ade3df7f76bd71 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Tue, 10 Sep 2019 16:37:54 +0800 Subject: [PATCH 091/124] solve compile problem --- .../com/jd/blockchain/consts/DataCodes.java | 8 +++--- .../ledger/core/ParticipantCertData.java | 4 ++- .../ledger/core/ContractInvokingTest.java | 25 +++---------------- .../ledger/core/LedgerAdminDatasetTest.java | 15 ++--------- .../ledger/core/LedgerInitOperationTest.java | 8 +++--- .../core/LedgerInitSettingSerializeTest.java | 4 ++- .../ledger/core/LedgerManagerTest.java | 5 ++++ .../ledger/core/LedgerMetaDataTest.java | 7 ++---- .../ledger/core/LedgerTestUtils.java | 9 ++----- .../ledger/LedgerInitProperties.java | 4 +++ .../transaction/ConsensusParticipantData.java | 8 ++++-- 11 files changed, 38 insertions(+), 59 deletions(-) diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 26b52692..607c051d 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -58,13 +58,13 @@ public interface DataCodes { public static final int TX_OP_RESULT = 0x370; - public static final int TX_OP_ROLE_CONFIGURE = 0x370; + public static final int TX_OP_ROLE_CONFIGURE = 0x371; - public static final int TX_OP_ROLE_CONFIGURE_ENTRY = 0x371; + public static final int TX_OP_ROLE_CONFIGURE_ENTRY = 0x372; - public static final int TX_OP_USER_ROLES_AUTHORIZE = 0x372; + public static final int TX_OP_USER_ROLES_AUTHORIZE = 0x373; - public static final int TX_OP_USER_ROLE_AUTHORIZE_ENTRY = 0x373; + public static final int TX_OP_USER_ROLE_AUTHORIZE_ENTRY = 0x374; // enum types of permissions; public static final int ENUM_TX_PERMISSION = 0x401; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java index 45078129..c9212bb2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ParticipantCertData.java @@ -30,10 +30,11 @@ public class ParticipantCertData implements ParticipantNode { this.participantNodeState = participantNode.getParticipantNodeState(); } - public ParticipantCertData(Bytes address, String name, PubKey pubKey) { + public ParticipantCertData(Bytes address, String name, PubKey pubKey, ParticipantNodeState participantNodeState) { this.address = address; this.name = name; this.pubKey = pubKey; + this.participantNodeState = participantNodeState; } @Override @@ -51,6 +52,7 @@ public class ParticipantCertData implements ParticipantNode { return pubKey; } + @Override public int getId() { return id; } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index c05c3f1b..77cf7024 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -15,33 +15,13 @@ import static org.mockito.Mockito.when; import java.util.Random; +import com.jd.blockchain.ledger.*; import org.junit.Test; import org.mockito.Mockito; import com.jd.blockchain.binaryproto.BinaryProtocol; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.BytesData; -import com.jd.blockchain.ledger.BytesValue; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.EndpointRequest; -import com.jd.blockchain.ledger.KVDataEntry; -import com.jd.blockchain.ledger.LedgerBlock; -import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.LedgerTransaction; -import com.jd.blockchain.ledger.NodeRequest; -import com.jd.blockchain.ledger.OperationResult; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionContentBody; -import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; @@ -75,6 +55,9 @@ public class ContractInvokingTest { DataContractRegistry.register(TransactionResponse.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(ParticipantNode.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); } private static final String LEDGER_KEY_PREFIX = "LDG://"; diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index e591aa7f..c8ec5e93 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.Random; +import com.jd.blockchain.ledger.*; import org.junit.Test; import com.jd.blockchain.crypto.AddressEncoding; @@ -19,19 +20,6 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.LedgerAdminInfo; -import com.jd.blockchain.ledger.LedgerMetadata_V2; -import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.LedgerSettings; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.ledger.RolePrivilegeSettings; -import com.jd.blockchain.ledger.RolePrivileges; -import com.jd.blockchain.ledger.RolesPolicy; -import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.UserRolesSettings; -import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; @@ -62,6 +50,7 @@ public class LedgerAdminDatasetTest { parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(bckeys[i].getPubKey()); + parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); } ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); initSetting.setConsensusParticipants(parties1); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java index 8c245fa2..e3c36a73 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Random; +import com.jd.blockchain.ledger.*; import org.junit.Before; import org.junit.Test; @@ -17,10 +18,6 @@ import com.jd.blockchain.crypto.CryptoProvider; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.LedgerInitOperation; -import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.ParticipantCertData; import com.jd.blockchain.transaction.ConsensusParticipantData; @@ -80,6 +77,7 @@ public class LedgerInitOperationTest { parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(keys[i].getPubKey()); + parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); } ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); @@ -118,7 +116,7 @@ public class LedgerInitOperationTest { for (int i = 0; i < parties.length; i++) { keys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()), - "Participant[" + i + "]", keys[i].getPubKey()); + "Participant[" + i + "]", keys[i].getPubKey(), ParticipantNodeState.CONSENSUSED); } ParticipantCertData[] parties1 = Arrays.copyOf(parties, 4); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java index 46c96f65..2485cbd0 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Random; +import com.jd.blockchain.ledger.ParticipantNodeState; import org.junit.Before; import org.junit.Test; @@ -80,6 +81,7 @@ public class LedgerInitSettingSerializeTest { parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(keys[i].getPubKey()); + parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); } ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); @@ -122,7 +124,7 @@ public class LedgerInitSettingSerializeTest { for (int i = 0; i < parties.length; i++) { keys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()), - "Participant[" + i + "]", keys[i].getPubKey()); + "Participant[" + i + "]", keys[i].getPubKey(), ParticipantNodeState.CONSENSUSED); } ParticipantCertData[] parties1 = Arrays.copyOf(parties, 4); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java index d814326d..6438ceb9 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java @@ -45,6 +45,7 @@ public class LedgerManagerTest { DataContractRegistry.register(TransactionContent.class); DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(ParticipantNode.class); DataContractRegistry.register(ParticipantRegisterOperation.class); DataContractRegistry.register(ParticipantStateUpdateOperation.class); DataContractRegistry.register(BlockBody.class); @@ -204,6 +205,7 @@ public class LedgerManagerTest { parties[0].setPubKey(kp0.getPubKey()); parties[0].setAddress(AddressEncoding.generateAddress(kp0.getPubKey())); parties[0].setHostAddress(new NetworkAddress("127.0.0.1", 9000)); + parties[0].setParticipantState(ParticipantNodeState.CONSENSUSED); parties[1] = new ConsensusParticipantData(); parties[1].setId(1); @@ -212,6 +214,7 @@ public class LedgerManagerTest { parties[1].setPubKey(kp1.getPubKey()); parties[1].setAddress(AddressEncoding.generateAddress(kp1.getPubKey())); parties[1].setHostAddress(new NetworkAddress("127.0.0.1", 9010)); + parties[1].setParticipantState(ParticipantNodeState.CONSENSUSED); parties[2] = new ConsensusParticipantData(); parties[2].setId(2); @@ -220,6 +223,7 @@ public class LedgerManagerTest { parties[2].setPubKey(kp2.getPubKey()); parties[2].setAddress(AddressEncoding.generateAddress(kp2.getPubKey())); parties[2].setHostAddress(new NetworkAddress("127.0.0.1", 9020)); + parties[2].setParticipantState(ParticipantNodeState.CONSENSUSED); parties[3] = new ConsensusParticipantData(); parties[3].setId(3); @@ -228,6 +232,7 @@ public class LedgerManagerTest { parties[3].setPubKey(kp3.getPubKey()); parties[3].setAddress(AddressEncoding.generateAddress(kp3.getPubKey())); parties[3].setHostAddress(new NetworkAddress("127.0.0.1", 9030)); + parties[3].setParticipantState(ParticipantNodeState.CONSENSUSED); initSetting.setConsensusParticipants(parties); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java index e7cba7ad..6f208323 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java @@ -7,8 +7,7 @@ import static org.junit.Assert.assertTrue; import java.util.Random; -import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.LedgerSettings; +import com.jd.blockchain.ledger.*; import org.junit.Before; import org.junit.Test; @@ -23,8 +22,6 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; @@ -189,7 +186,7 @@ public class LedgerMetaDataTest { // NetworkAddress consensusAddress = new NetworkAddress("192.168.1.1", 9001, // false); Bytes address = AddressEncoding.generateAddress(pubKey); - ParticipantCertData participantCertData = new ParticipantCertData(address, name, pubKey); + ParticipantCertData participantCertData = new ParticipantCertData(address, name, pubKey, ParticipantNodeState.CONSENSUSED); // encode and decode byte[] encodeBytes = BinaryProtocol.encode(participantCertData, ParticipantNode.class); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java index eb6835b5..be201848 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java @@ -11,13 +11,7 @@ import com.jd.blockchain.crypto.SignatureFunction; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.CryptoSetting; -import com.jd.blockchain.ledger.LedgerInitSetting; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.*; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; import com.jd.blockchain.transaction.ConsensusParticipantData; @@ -74,6 +68,7 @@ public class LedgerTestUtils { parties[i].setPubKey(partiKeys[i].getPubKey()); parties[i].setAddress(AddressEncoding.generateAddress(partiKeys[i].getPubKey())); parties[i].setHostAddress(new NetworkAddress("192.168.1." + (10 + i), 9000)); + parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java index ad946049..86900b1c 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java @@ -441,6 +441,7 @@ public class LedgerInitProperties { private NetworkAddress initializerAddress; + @Override public int getId() { return id; } @@ -454,6 +455,7 @@ public class LedgerInitProperties { return address; } + @Override public String getName() { return name; } @@ -470,6 +472,7 @@ public class LedgerInitProperties { // this.pubKeyPath = pubKeyPath; // } + @Override public ParticipantNodeState getParticipantNodeState() { return participantNodeState; } @@ -486,6 +489,7 @@ public class LedgerInitProperties { this.initializerAddress = initializerAddress; } + @Override public PubKey getPubKey() { return pubKey; } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java index 7b5cfd17..e182ced1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ConsensusParticipantData.java @@ -20,7 +20,7 @@ public class ConsensusParticipantData implements ParticipantNode { private ParticipantNodeState participantNodeState; - + @Override public int getId() { return id; } @@ -29,6 +29,7 @@ public class ConsensusParticipantData implements ParticipantNode { this.id = id; } + @Override public String getName() { return name; } @@ -37,7 +38,7 @@ public class ConsensusParticipantData implements ParticipantNode { this.name = name; } - public NetworkAddress getConsensusAddress() { + public NetworkAddress getHostAddress() { return hostAddress; } @@ -45,6 +46,7 @@ public class ConsensusParticipantData implements ParticipantNode { this.hostAddress = hostAddress; } + @Override public PubKey getPubKey() { return pubKey; } @@ -53,6 +55,7 @@ public class ConsensusParticipantData implements ParticipantNode { this.pubKey = pubKey; } + @Override public Bytes getAddress() { return address; } @@ -61,6 +64,7 @@ public class ConsensusParticipantData implements ParticipantNode { this.address = address; } + @Override public ParticipantNodeState getParticipantNodeState() { return participantNodeState; } From e1b4240d5169332c4e7282c554f13e59c682a135 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 10 Sep 2019 17:05:07 +0800 Subject: [PATCH 092/124] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E6=8F=92=E4=BB=B6BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contract/maven/ContractCompileMojo.java | 186 ++++++++++++++---- .../contract/maven/rule/BlackList.java | 3 +- .../maven/rule/DependencyExclude.java | 6 +- .../contract/maven/verify/ResolveEngine.java | 70 +++++-- .../contract/maven/verify/VerifyEngine.java | 16 +- .../src/main/resources/blacks.conf | 12 +- .../{provideds.conf => providers.conf} | 0 .../src/main/resources/whites.conf | 4 +- .../ledger/ContractInvokingTest.java | 13 +- .../resources/contract-JDChain-Contract.jar | Bin 0 -> 96020 bytes .../ledger/data/ContractJarUtilsTest.java | 69 ------- 11 files changed, 243 insertions(+), 136 deletions(-) rename source/contract/contract-maven-plugin/src/main/resources/{provideds.conf => providers.conf} (100%) create mode 100644 source/ledger/ledger-core/src/test/resources/contract-JDChain-Contract.jar delete mode 100644 source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java index 3ddf15a0..a98eeca3 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java @@ -1,12 +1,12 @@ package com.jd.blockchain.contract.maven; -import com.jd.blockchain.contract.ContractJarUtils; import com.jd.blockchain.contract.maven.rule.BlackList; import com.jd.blockchain.contract.maven.rule.WhiteList; import com.jd.blockchain.contract.maven.rule.DependencyExclude; import com.jd.blockchain.contract.maven.verify.ResolveEngine; import com.jd.blockchain.contract.maven.verify.VerifyEngine; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -16,8 +16,14 @@ import org.apache.maven.project.MavenProject; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Set; +import static com.jd.blockchain.contract.ContractJarUtils.BLACK_CONF; +import static com.jd.blockchain.contract.ContractJarUtils.WHITE_CONF; + @Mojo(name = "compile") public class ContractCompileMojo extends SingleAssemblyMojo { @@ -42,46 +48,122 @@ public class ContractCompileMojo extends SingleAssemblyMojo { // 首先对MainClass进行校验,要求必须有MainClass String mainClass = mainClassVerify(); - // 将JDChain本身代码之外的代码移除(不打包进整个Jar) + // 排除所有依赖,只打包当前代码 +// excludeAllArtifactExclude(super.getProject().getDependencyArtifacts()); +// handleArtifactCompile(super.getProject().getDependencyArtifacts()); handleArtifactExclude(super.getProject().getDependencyArtifacts()); // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); // 执行打包命令 + // 该命令生成的是只含有当前项目的实际代码的Jar包,该Jar包仅用于校验MainClass super.execute(); - // 将本次打包好的文件重新命名,以便于后续重新打包需要 - // 把文件改名,然后重新再生成一个文件 - File dstFile; + // 生成解析引擎 + ResolveEngine resolveEngine = new ResolveEngine(getLog(), mainClass); + + // 获取本次生成的Jar文件 + File defaultJarFile; try { - dstFile = rename(getProject(), getFinalName()); - } catch (IOException e) { + defaultJarFile = rename(getProject(), getFinalName()); + // 校验当前MainClass是否满足需求 + resolveEngine.verifyCurrentProjectMainClass(defaultJarFile); + // 校验完成后将该Jar删除 +// FileUtils.forceDelete(mainClassFile); + } catch (Exception e) { getLog().error(e); throw new MojoFailureException(e.getMessage()); } - // 首先校验该类的Jar包中是否包含不符合规范的命名,以及该类的代码中的部分解析 - File finalJarFile = verify(dstFile, mainClass); - - // 将所有的依赖的jar包全部打包进一个包中,以便于进行ASM检查 - handleArtifactCompile(super.getProject().getDependencyArtifacts()); - - // 然后再打包一次,本次打包完成后,其中的代码包含所有的class(JDK自身的除外) - super.execute(); - - // 对代码中的一些规则进行校验,主要是校验其是否包含一些不允许使用的类、包、方法等 - verify(mainClass); - - // 删除中间的一些文件 +// // 将JDChain本身之外的代码打包进Jar包,然后编译 +// handleArtifactExclude(super.getProject().getDependencyArtifacts()); +// +// // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 +// super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); +// +// // 生成Jar包(该Jar包中不包含JDChain内部的代码) +// super.execute(); +// +// File defaultJarFile; +// try { +// defaultJarFile = rename(getProject(), getFinalName()); +// } catch (Exception e) { +// getLog().error(e); +// throw new MojoFailureException(e.getMessage()); +// } + + // 校验该Jar包 + verify(defaultJarFile, mainClass); + + File deployJarFile = resolveEngine.verify(defaultJarFile); + + // 删除中间产生的临时文件 try { - FileUtils.forceDelete(dstFile); - } catch (IOException e) { - throw new MojoFailureException(e.getMessage()); + FileUtils.forceDelete(defaultJarFile); + } catch (Exception e) { + getLog().error(e); } + getLog().info(String.format("JDChain's Contract compile success, path = %s !", deployJarFile.getPath())); + + + +// // 将JDChain本身代码之外的代码移除(不打包进整个Jar) +// handleArtifactExclude(super.getProject().getDependencyArtifacts()); +// +// // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 +// super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); +// +// // 执行打包命令 +// super.execute(); + +// // 将本次打包好的文件重新命名,以便于后续重新打包需要 +// // 把文件改名,然后重新再生成一个文件 +// File dstFile; +// try { +// dstFile = rename(getProject(), getFinalName()); +// } catch (IOException e) { +// getLog().error(e); +// throw new MojoFailureException(e.getMessage()); +// } +// +// // dstFile理论上应该含有 +// +// // 首先校验该类的Jar包中是否包含不符合规范的命名,以及该类的代码中的部分解析 +// +// ResolveEngine resolveEngine = new ResolveEngine(getLog(), mainClass); +// +// // 校验mainClass +// resolveEngine.verifyCurrentProjectMainClass(dstFile); +// +// +// +// File finalJarFile = resolveEngine.verify(); +// +// // 将所有的依赖的jar包全部打包进一个包中,以便于进行ASM检查 +// handleArtifactCompile(super.getProject().getDependencyArtifacts()); +// +// // 然后再打包一次,本次打包完成后,其中的代码包含所有的class(JDK自身的除外) +// super.execute(); +// +// File jarFile = new File(jarPath(getProject(), getFinalName())); +// +// // 校验mainClass +// resolveEngine.verifyCurrentProjectMainClass(jarFile); +// +// // 对代码中的一些规则进行校验,主要是校验其是否包含一些不允许使用的类、包、方法等 +// verify(jarFile, mainClass); +// +// // 删除中间的一些文件 +//// try { +//// FileUtils.forceDelete(dstFile); +//// } catch (IOException e) { +//// throw new MojoFailureException(e.getMessage()); +//// } +// // 若执行到此处没有异常则表明打包成功,打印打包成功消息 - getLog().info(String.format("JDChain's Contract compile success, path = %s !", finalJarFile.getPath())); +// getLog().info(String.format("JDChain's Contract compile success, path = %s !", finalJarFile.getPath())); } private String mainClassVerify() throws MojoFailureException { @@ -107,10 +189,20 @@ public class ContractCompileMojo extends SingleAssemblyMojo { getLog().info(String.format("GroupId[%s] ArtifactId[%s] belongs to DependencyExclude !!!", groupId, artifactId)); // 属于排除的名单之中 artifact.setScope(SCOPE_PROVIDED); + } else { + getLog().info(String.format("GroupId[%s] ArtifactId[%s] not belongs to DependencyExclude !!!", groupId, artifactId)); + // 属于排除的名单之中 + artifact.setScope(SCOPE_COMPILE); } } } + private void excludeAllArtifactExclude(Set artifacts) { + for (Artifact artifact : artifacts) { + artifact.setScope(SCOPE_PROVIDED); + } + } + private void handleArtifactCompile(Set artifacts) { for (Artifact artifact : artifacts) { if (artifact.getScope().equals(SCOPE_PROVIDED)) { @@ -135,17 +227,14 @@ public class ContractCompileMojo extends SingleAssemblyMojo { File.separator + finalName + "-" + JAR_DEPENDENCE + ".jar"; } - private void verify(String mainClass) throws MojoFailureException { + private void verify(File jarFile, String mainClass) throws MojoFailureException { try { - - File jarFile = new File(jarPath(getProject(), getFinalName())); - VerifyEngine verifyEngine = new VerifyEngine(getLog(), jarFile, mainClass, black, white); verifyEngine.verify(); // 校验完成后将该jar包删除 - FileUtils.forceDelete(jarFile); +// FileUtils.forceDelete(jarFile); } catch (Exception e) { getLog().error(e); @@ -153,20 +242,47 @@ public class ContractCompileMojo extends SingleAssemblyMojo { } } - private File verify(File jarFile, String mainClass) throws MojoFailureException { - ResolveEngine resolveEngine = new ResolveEngine(getLog(), jarFile, mainClass); - return resolveEngine.verify(); + private static void init() { + try { + black = AbstractContract.initBlack(loadBlackConf()); + white = AbstractContract.initWhite(loadWhiteConf()); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + private static List loadWhiteConf() { + + return resolveConfig(WHITE_CONF); } - private static void init() { + private static List loadBlackConf() { + return resolveConfig(BLACK_CONF); + } + + private static List resolveConfig(String fileName) { + List configs = new ArrayList<>(); + try { - black = AbstractContract.initBlack(ContractJarUtils.loadBlackConf()); - white = AbstractContract.initWhite(ContractJarUtils.loadWhiteConf()); + List readLines = loadConfig(fileName); + if (!readLines.isEmpty()) { + for (String readLine : readLines) { + String[] lines = readLine.split(","); + configs.addAll(Arrays.asList(lines)); + } + } } catch (Exception e) { throw new IllegalStateException(e); } + + return configs; + } + + public static List loadConfig(String fileName) throws Exception { + + return IOUtils.readLines( + ContractCompileMojo.class.getResourceAsStream("/" + fileName)); } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java index 97044df6..593093aa 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java @@ -52,6 +52,7 @@ public class BlackList { } public boolean isBlack(Class clazz, String methodName) { + // 判断该Class是否属于黑名单 if (isCurrentClassBlack(clazz, methodName)) { return true; @@ -75,7 +76,7 @@ public class BlackList { String packageName = clazz.getPackage().getName(); for (String bp : blackPackages) { - if (packageName.equals(bp) || packageName.startsWith(bp)) { + if ((packageName + ".").equals(bp) || packageName.startsWith(bp)) { return true; } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java index 6c3dca44..c20ce777 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java @@ -1,6 +1,6 @@ package com.jd.blockchain.contract.maven.rule; -import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.maven.ContractCompileMojo; import java.util.ArrayList; import java.util.List; @@ -11,7 +11,7 @@ public class DependencyExclude { private static final String COMMON_ARTIFACTID = "*"; - private static final String CONFIG = "provided.conf"; + private static final String CONFIG = "providers.conf"; private static final Map> DEPENDENCYS = new ConcurrentHashMap<>(); @@ -24,7 +24,7 @@ public class DependencyExclude { } private static void init() throws Exception { - List readLines = ContractJarUtils.loadConfig(CONFIG); + List readLines = ContractCompileMojo.loadConfig(CONFIG); if (!readLines.isEmpty()) { for (String line : readLines) { // groupId/artifactId diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java index 059f7337..97744689 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java @@ -1,7 +1,10 @@ package com.jd.blockchain.contract.maven.verify; +import com.alibaba.fastjson.JSON; +import com.jd.blockchain.contract.Contract; import com.jd.blockchain.contract.ContractJarUtils; import com.jd.blockchain.contract.ContractType; +import com.jd.blockchain.contract.EventProcessingAware; import org.apache.commons.io.FileUtils; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; @@ -21,23 +24,49 @@ public class ResolveEngine { private Log LOGGER; - private File jarFile; +// private File jarFile; private String mainClass; - public ResolveEngine(Log LOGGER, File jarFile, String mainClass) { +// public ResolveEngine(Log LOGGER, File jarFile, String mainClass) { + public ResolveEngine(Log LOGGER, String mainClass) { this.LOGGER = LOGGER; - this.jarFile = jarFile; +// this.jarFile = jarFile; this.mainClass = mainClass; } - public File verify() throws MojoFailureException { + /** + * 校验当前项目中MainClass其是否满足JDChain合约写法 + * + * @param mainClassJarFile + * @throws MojoFailureException + */ + public void verifyCurrentProjectMainClass(File mainClassJarFile) throws MojoFailureException { + // 校验MainClass try { - // 首先校验MainClass - ClassLoader classLoader = verifyMainClass(jarFile); + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass start...", mainClassJarFile.getName())); + // 自定义ClassLoader,必须使用Thread.currentThread().getContextClassLoader() + // 保证其项目内部加载的Jar包无须再加载一次 + URLClassLoader classLoader = new URLClassLoader(new URL[]{mainClassJarFile.toURI().toURL()}, + Thread.currentThread().getContextClassLoader()); + // 从MainClass作为入口进行MainClass代码校验 + Class mClass = classLoader.loadClass(mainClass); + ContractType.resolve(mClass); + + // 校验完成后需要释放,否则无法删除该Jar文件 + classLoader.close(); + + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", mainClassJarFile.getName())); + } catch (Exception e) { + throw new MojoFailureException(e.getMessage()); + } + } + + public File verify(File defaultJarFile) throws MojoFailureException { + try { // 检查jar包中所有的class的命名,要求其包名不能为com.jd.blockchain.* - LinkedList totalClasses = loadAllClass(jarFile); + LinkedList totalClasses = loadAllClass(defaultJarFile); if (!totalClasses.isEmpty()) { @@ -47,9 +76,8 @@ public class ResolveEngine { LOGGER.debug(String.format("Verify Dependency Class[%s] start......", dotClassName)); // 获取其包名 - Class currentClass = classLoader.loadClass(dotClassName); - - String packageName = currentClass.getPackage().getName(); + // 将class转换为包名 + String packageName = class2Package(dotClassName); if (ContractJarUtils.isJDChainPackage(packageName)) { throw new IllegalStateException(String.format("Class[%s]'s package[%s] cannot start with %s !", @@ -61,18 +89,23 @@ public class ResolveEngine { } // 处理完成之后,生成finalName-JDChain-Contract.jar - return compileCustomJar(); + return compileCustomJar(defaultJarFile); } catch (Exception e) { LOGGER.error(e.getMessage()); throw new MojoFailureException(e.getMessage()); } } - private File compileCustomJar() throws IOException { + private String class2Package(String dotClassName) { + + return dotClassName.substring(0, dotClassName.lastIndexOf(".")); + } + + private File compileCustomJar(File defaultJarFile) throws IOException { - String fileParentPath = jarFile.getParentFile().getPath(); + String fileParentPath = defaultJarFile.getParentFile().getPath(); - String jarFileName = jarFile.getName(); + String jarFileName = defaultJarFile.getName(); String fileName = jarFileName.substring(0, jarFileName.lastIndexOf(".")); @@ -80,13 +113,13 @@ public class ResolveEngine { String dstJarPath = fileParentPath + File.separator + fileName + "-temp-" + System.currentTimeMillis() + ".jar"; - File srcJar = jarFile, dstJar = new File(dstJarPath); + File srcJar = defaultJarFile, dstJar = new File(dstJarPath); - LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", jarFile.getPath(), dstJarPath)); + LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", defaultJarFile.getPath(), dstJarPath)); // 首先进行Copy处理 copy(srcJar, dstJar); - LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", jarFile.getPath(), dstJarPath)); + LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", defaultJarFile.getPath(), dstJarPath)); byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); @@ -128,6 +161,9 @@ public class ResolveEngine { } } } + // Jar文件使用完成后需要关闭,否则可能会产生无法删除的问题 + jarFile.close(); + return allClass; } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java index ea3d723c..7fd59c7e 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java @@ -52,7 +52,8 @@ public class VerifyEngine { // 加载所有的jar,然后ASM获取MAP URL jarURL = jarFile.toURI().toURL(); - URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarURL}); + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarURL}, + Thread.currentThread().getContextClassLoader()); // 解析Jar包中所有的Class Map allContractClasses = resolveClasses(jarClasses()); @@ -60,6 +61,8 @@ public class VerifyEngine { // 开始处理MainClass verify(urlClassLoader, allContractClasses); + // 校验完成后需要释放ClassLoader,否则无法删除该Jar包 + urlClassLoader.close(); } public void verify(URLClassLoader urlClassLoader, Map allContractClasses) throws Exception { @@ -91,6 +94,8 @@ public class VerifyEngine { // 将该方法设置为已处理 haveManagedMethods.add(managedKey); String dotClassName = method.getDotClassName(); + + Class dotClass = urlClassLoader.loadClass(dotClassName); if (dotClass == null) { @@ -137,8 +142,13 @@ public class VerifyEngine { } } else { // 非URLClassLoader加载的类,只需要做判断即可 + // 对于系统加载的类,其白名单优先级高于黑名单 // 1、不再需要获取其方法; - // 2、只需要判断黑名单不需要判断白名单 + // 首先判断是否为白名单 + if (white.isWhite(dotClass)) { + return; + } + // 然后判断其是否为黑名单 if (black.isBlack(dotClass, method.getMethodName())) { throw new IllegalStateException(String.format("Class [%s] Method [%s] is Black !!!", dotClassName, method.getMethodName())); } @@ -187,8 +197,6 @@ public class VerifyEngine { continue; } - LOGGER.info(String.format("Resolve Class [%s] ...", className)); - ContractClass contractClass = new ContractClass(className); ClassReader cr = new ClassReader(classContent); cr.accept(new ASMClassVisitor(contractClass), ClassReader.SKIP_DEBUG); diff --git a/source/contract/contract-maven-plugin/src/main/resources/blacks.conf b/source/contract/contract-maven-plugin/src/main/resources/blacks.conf index 533c4850..caa20370 100644 --- a/source/contract/contract-maven-plugin/src/main/resources/blacks.conf +++ b/source/contract/contract-maven-plugin/src/main/resources/blacks.conf @@ -1,5 +1,13 @@ -java.io.* -java.nio.* +java.io.File +java.io.InputStream +java.io.OutputStream +java.io.DataInput +java.io.DataOutput +java.io.Reader +java.io.Writer +java.io.Flushable +java.nio.channels.* +java.nio.file.* java.net.* java.sql.* java.lang.reflect.* diff --git a/source/contract/contract-maven-plugin/src/main/resources/provideds.conf b/source/contract/contract-maven-plugin/src/main/resources/providers.conf similarity index 100% rename from source/contract/contract-maven-plugin/src/main/resources/provideds.conf rename to source/contract/contract-maven-plugin/src/main/resources/providers.conf diff --git a/source/contract/contract-maven-plugin/src/main/resources/whites.conf b/source/contract/contract-maven-plugin/src/main/resources/whites.conf index a495db60..08e7d754 100644 --- a/source/contract/contract-maven-plugin/src/main/resources/whites.conf +++ b/source/contract/contract-maven-plugin/src/main/resources/whites.conf @@ -1 +1,3 @@ -com.jd.blockchain.* \ No newline at end of file +com.jd.blockchain.* +java.nio.charset.Charset +com.alibaba.fastjson.* \ No newline at end of file diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java index 41d28ab2..86ec8c2e 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java @@ -13,9 +13,13 @@ import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.transaction.TxBuilder; import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.io.BytesUtils; +import org.apache.commons.io.IOUtils; import org.junit.Test; import org.mockito.Mockito; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.util.Random; import static org.junit.Assert.*; @@ -44,6 +48,9 @@ public class ContractInvokingTest { // 采用基于内存的 Storage; private MemoryKVStorage storage = new MemoryKVStorage(); + // 用于测试的发布合约文件 + private static final String CONTRACT_JAR = "contract-JDChain-Contract.jar"; + @Test public void test() { // 初始化账本到指定的存储库; @@ -82,7 +89,6 @@ public class ContractInvokingTest { // 构建基于接口调用合约的交易请求,用于测试合约调用; TxBuilder txBuilder = new TxBuilder(ledgerHash); TestContract contractProxy = txBuilder.contract(contractAddress, TestContract.class); - TestContract contractProxy1 = txBuilder.contract(contractAddress, TestContract.class); String asset = "AK"; long issueAmount = new Random().nextLong(); @@ -190,8 +196,7 @@ public class ContractInvokingTest { } private byte[] chainCode() { - byte[] chainCode = new byte[1024]; - new Random().nextBytes(chainCode); - return chainCode; + + return BytesUtils.copyToBytes(this.getClass().getResourceAsStream("/" + CONTRACT_JAR)); } } diff --git a/source/ledger/ledger-core/src/test/resources/contract-JDChain-Contract.jar b/source/ledger/ledger-core/src/test/resources/contract-JDChain-Contract.jar new file mode 100644 index 0000000000000000000000000000000000000000..fd926b150f9c82047415778431d230260b00cc98 GIT binary patch literal 96020 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 literal 0 HcmV?d00001 diff --git a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java b/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java deleted file mode 100644 index 06e2bf44..00000000 --- a/source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractJarUtilsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package test.com.jd.blockchain.ledger.data; - -import org.apache.commons.io.FileUtils; -import org.junit.Test; -import org.springframework.core.io.ClassPathResource; - -import java.io.File; -import java.nio.charset.StandardCharsets; - -import static com.jd.blockchain.contract.ContractJarUtils.*; -import static org.junit.Assert.fail; - -public class ContractJarUtilsTest { - - private String jarName = "complex"; - - @Test - public void test() { - - byte[] chainCode = null; - try { - ClassPathResource classPathResource = new ClassPathResource(jarName + ".jar"); - String classPath = classPathResource.getFile().getParentFile().getPath(); - - // 首先将Jar包转换为指定的格式 - String srcJarPath = classPath + - File.separator + jarName + ".jar"; - - String dstJarPath = classPath + - File.separator + jarName + "-temp-" + System.currentTimeMillis() + ".jar"; - - File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); - - // 首先进行Copy处理 - copy(srcJar, dstJar); - - byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); - - String finalJarPath = classPath + - File.separator + jarName + "-jdchain.jar"; - - File finalJar = new File(finalJarPath); - - copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); - - // 删除临时文件 - FileUtils.forceDelete(dstJar); - - // 读取finalJar中的内容 - chainCode = FileUtils.readFileToByteArray(finalJar); - - FileUtils.forceDelete(finalJar); - } catch (Exception e) { - e.printStackTrace(); - } - try { - verify(chainCode); - System.out.println("Verify Success !!!"); - } catch (Exception e) { - fail("Verify Fail !!"); - } - } - - @Test - public void testSign() { - byte[] test = "zhangsan".getBytes(StandardCharsets.UTF_8); - System.out.println(contractMF(test)); - } -} From 1722db01fd477c03ee0a31f8acfb792ef453ae89 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 10 Sep 2019 18:34:49 +0800 Subject: [PATCH 093/124] Merge remote-tracking branch 'origin/develop' into feature/1.0.0-contract-check2.0-local # Conflicts: # source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java # source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployExeUtil.java # source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractDeployMojo.java # source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java # source/gateway/src/main/java/com/jd/blockchain/gateway/web/TxProcessingController.java # source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractContractEventHandle.java # source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java # source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/JVMContractEventSendOperationHandle.java # source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java # source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractCodeDeployOperationHandle.java # source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java # source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java --- .../blockchain/mocker/MockerNodeContext.java | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 7e916d16..2e406277 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import com.jd.blockchain.ledger.*; import org.mockito.Mockito; import com.jd.blockchain.binaryproto.DataContract; @@ -27,38 +28,6 @@ import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; -import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.ContractCodeDeployOperation; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.ContractInfo; -import com.jd.blockchain.ledger.DataAccountKVSetOperation; -import com.jd.blockchain.ledger.DataAccountRegisterOperation; -import com.jd.blockchain.ledger.EndpointRequest; -import com.jd.blockchain.ledger.KVDataEntry; -import com.jd.blockchain.ledger.KVInfoVO; -import com.jd.blockchain.ledger.LedgerAdminInfo; -import com.jd.blockchain.ledger.LedgerBlock; -import com.jd.blockchain.ledger.LedgerInfo; -import com.jd.blockchain.ledger.LedgerInitProperties; -import com.jd.blockchain.ledger.LedgerMetadata; -import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.LedgerTransaction; -import com.jd.blockchain.ledger.NodeRequest; -import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.OperationResult; -import com.jd.blockchain.ledger.ParticipantNode; -import com.jd.blockchain.ledger.TransactionContent; -import com.jd.blockchain.ledger.TransactionContentBody; -import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.TransactionRequestBuilder; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.TransactionState; -import com.jd.blockchain.ledger.UserInfo; -import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerDataQuery; From 040bbabe6b4324a434bc7605cbe292d099252bdf Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 10 Sep 2019 18:44:37 +0800 Subject: [PATCH 094/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9fastjson=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=B8=BA1.2.60?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/pom.xml b/source/pom.xml index 9dd10642..cca9e658 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -43,6 +43,7 @@ 3.3.0 1.2.2 1.8.8 + 1.2.60 0.5.35 1.0.18 @@ -180,7 +181,7 @@ com.alibaba fastjson - 1.2.58 + ${fastjson.version} From d1e40a91fa26b8229f36d081305684353d330af6 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Tue, 10 Sep 2019 23:49:08 +0800 Subject: [PATCH 095/124] Completed authorization test case and refactored TransactionBatchProcessor; --- .../DefaultOperationHandleRegisteration.java | 16 -- .../ledger/core/LedgerInitializer.java | 112 +++++++- .../blockchain/ledger/core/LedgerManage.java | 3 +- .../blockchain/ledger/core/LedgerManager.java | 9 +- .../blockchain/ledger/core/LedgerQuery.java | 112 ++++++++ .../ledger/core/LedgerQueryService.java | 80 +++--- .../ledger/core/LedgerRepository.java | 106 +------ .../ledger/core/OperationHandle.java | 2 +- .../core/TransactionBatchProcessor.java | 72 +++-- .../ledger/core/TransactionEngineImpl.java | 36 +-- .../ledger/core/UserRolesPrivileges.java | 4 +- .../AbstractLedgerOperationHandle.java | 20 +- ...tractContractEventSendOperationHandle.java | 17 +- .../ContractCodeDeployOperationHandle.java | 7 +- .../DataAccountKVSetOperationHandle.java | 7 +- .../DataAccountRegisterOperationHandle.java | 8 +- .../handles/LedgerInitOperationHandle.java | 5 +- .../RolesConfigureOperationHandle.java | 6 +- .../handles/UserAuthorizeOperationHandle.java | 7 +- .../handles/UserRegisterOperationHandle.java | 6 +- .../ledger/core/ContractInvokingTest.java | 10 +- .../core/TransactionBatchProcessorTest.java | 14 +- .../ledger/LedgerInitProperties.java | 1 - .../blockchain/ledger/LedgerPermission.java | 22 +- .../jd/blockchain/ledger/LedgerPrivilege.java | 16 +- .../jd/blockchain/ledger/PrivilegeBitset.java | 72 +++-- .../ledger/TransactionPermission.java | 8 +- .../ledger/TransactionPrivilege.java | 16 +- .../com/jd/blockchain/ledger/UserRoles.java | 7 +- .../peer/web/LedgerQueryController.java | 66 ++--- .../peer/web/ManagementController.java | 4 +- source/test/pom.xml | 4 +- .../jd/blockchain/intgr/IntegrationTest.java | 30 +- .../intgr/consensus/ConsensusTest.java | 8 +- .../intgr/perf/GlobalPerformanceTest.java | 8 +- .../intgr/perf/LedgerInitializeTest.java | 12 +- .../intgr/perf/LedgerInitializeWebTest.java | 14 +- .../intgr/perf/LedgerPerformanceTest.java | 4 +- .../com/jd/blockchain/intgr/perf/Utils.java | 4 +- .../jd/blockchain/intgr/IntegrationBase.java | 20 +- .../blockchain/intgr/IntegrationBaseTest.java | 16 +- .../jd/blockchain/intgr/IntegrationTest2.java | 16 +- .../intgr/IntegrationTest4Bftsmart.java | 6 +- .../intgr/IntegrationTest4Contract.java | 6 +- .../blockchain/intgr/IntegrationTest4MQ.java | 4 +- .../intgr/IntegrationTestAll4Redis.java | 30 +- .../intgr/IntegrationTestDataAccount.java | 18 +- .../batch/bftsmart/BftsmartLedgerInit.java | 20 +- .../initializer/LedgerInitializeTest.java | 12 +- .../LedgerInitializeWeb4Nodes.java | 12 +- .../LedgerInitializeWeb4SingleStepsTest.java | 6 +- .../ledger/LedgerBlockGeneratingTest.java | 3 +- .../{test-ledger-core => test-ledger}/pom.xml | 7 +- .../MerkleDatasetPerformanceTester.java | 2 +- ...kleDataset_Performance_Result_20180922.txt | 0 .../test/ledger/RolesAuthorizationTest.java | 267 ++++++++++++++++++ .../src/test/resources/bftsmart.config | 144 ++++++++++ .../src/test/resources/keys/parti2.pub | 1 + .../src/test/resources/ledger.init | 165 +++++++++++ .../src/test/resources/logback-test.xml | 6 + .../web/LedgerInitConfiguration.java | 3 +- .../blockchain/mocker/MockerNodeContext.java | 4 +- .../handler/MockerContractExeHandle.java | 7 +- .../mocker/node/NodeWebContext.java | 6 +- .../com/jd/blockchain/utils/Int8Code.java | 8 + 65 files changed, 1216 insertions(+), 528 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java rename source/test/{test-ledger-core => test-ledger}/pom.xml (83%) rename source/test/{test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core => test-ledger/src/main/java/test/perf/com/jd/blockchain/ledger}/MerkleDatasetPerformanceTester.java (99%) rename source/test/{test-ledger-core => test-ledger}/src/main/resources/MerkleDataset_Performance_Result_20180922.txt (100%) create mode 100644 source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java create mode 100644 source/test/test-ledger/src/test/resources/bftsmart.config create mode 100644 source/test/test-ledger/src/test/resources/keys/parti2.pub create mode 100644 source/test/test-ledger/src/test/resources/ledger.init create mode 100644 source/test/test-ledger/src/test/resources/logback-test.xml create mode 100644 source/utils/utils-common/src/main/java/com/jd/blockchain/utils/Int8Code.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java index 6dd34e0a..cc6f5756 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java @@ -18,14 +18,6 @@ import com.jd.blockchain.ledger.core.handles.LedgerInitOperationHandle; import com.jd.blockchain.ledger.core.handles.RolesConfigureOperationHandle; import com.jd.blockchain.ledger.core.handles.UserAuthorizeOperationHandle; import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; -import com.jd.blockchain.transaction.ContractCodeDeployOpTemplate; -import com.jd.blockchain.transaction.ContractEventSendOpTemplate; -import com.jd.blockchain.transaction.DataAccountKVSetOpTemplate; -import com.jd.blockchain.transaction.DataAccountRegisterOpTemplate; -import com.jd.blockchain.transaction.LedgerInitOpTemplate; -import com.jd.blockchain.transaction.RolesConfigureOpTemplate; -import com.jd.blockchain.transaction.UserAuthorizeOpTemplate; -import com.jd.blockchain.transaction.UserRegisterOpTemplate; @Component public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { @@ -121,12 +113,4 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis } return hdl; } - - private static class OpHandleStub { - - private Class operationType; - - private OperationHandle operationHandle; - - } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index ca159135..ddd5a68d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -5,7 +5,9 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.SignatureDigest; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.BlockchainIdentityData; +import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.LedgerInitOperation; @@ -29,12 +31,14 @@ import com.jd.blockchain.transaction.TxRequestBuilder; public class LedgerInitializer { private static final FullPermissionedSecurityManager FULL_PERMISSION_SECURITY_MANAGER = new FullPermissionedSecurityManager(); + + private static final LedgerQuery EMPTY_LEDGER =new EmptyLedgerQuery(); private static final LedgerDataQuery EMPTY_LEDGER_DATA_QUERY = new EmptyLedgerDataset(); private static final OperationHandleRegisteration DEFAULT_OP_HANDLE_REG = new DefaultOperationHandleRegisteration(); - private LedgerService EMPTY_LEDGERS = new LedgerManager(); +// private LedgerService EMPTY_LEDGERS = new LedgerManager(); private LedgerInitSetting initSetting; @@ -75,15 +79,6 @@ public class LedgerInitializer { return initTxContent; } - private static SecurityInitSettings createDefaultSecurityInitSettings() { - // TODO throw new IllegalStateException("Not implemented!"); - return null; - } - -// public static LedgerInitializer create(LedgerInitSetting initSetting) { -// return create(initSetting, createDefaultSecurityInitSettings()); -// } - public static LedgerInitializer create(LedgerInitSetting initSetting, SecurityInitSettings securityInitSettings) { // 生成创世交易; TransactionContent initTxContent = buildGenesisTransaction(initSetting, securityInitSettings); @@ -143,6 +138,10 @@ public class LedgerInitializer { public SignatureDigest signTransaction(PrivKey privKey) { return SignatureUtils.sign(initTxContent, privKey); } + + public DigitalSignature signTransaction(BlockchainKeypair key) { + return SignatureUtils.sign(initTxContent, key); + } /** * 准备创建账本; @@ -204,12 +203,103 @@ public class LedgerInitializer { TransactionRequest txRequest = txReqBuilder.buildRequest(); TransactionBatchProcessor txProcessor = new TransactionBatchProcessor(FULL_PERMISSION_SECURITY_MANAGER, - ledgerEditor, EMPTY_LEDGER_DATA_QUERY, DEFAULT_OP_HANDLE_REG, EMPTY_LEDGERS); + ledgerEditor, EMPTY_LEDGER, DEFAULT_OP_HANDLE_REG); txProcessor.schedule(txRequest); txResultsHandle = txProcessor.prepare(); return txResultsHandle.getBlock(); } + + private static class EmptyLedgerQuery implements LedgerQuery{ + + private EmptyLedgerDataset dataset; + + @Override + public HashDigest getHash() { + return null; + } + + @Override + public long getLatestBlockHeight() { + return 0; + } + + @Override + public HashDigest getLatestBlockHash() { + return null; + } + + @Override + public LedgerBlock getLatestBlock() { + return null; + } + + @Override + public HashDigest getBlockHash(long height) { + return null; + } + + @Override + public LedgerBlock getBlock(long height) { + return null; + } + + @Override + public LedgerAdminInfo getAdminInfo() { + return null; + } + + @Override + public LedgerAdminInfo getAdminInfo(LedgerBlock block) { + return null; + } + + @Override + public LedgerBlock getBlock(HashDigest hash) { + return null; + } + + @Override + public LedgerDataQuery getDataSet(LedgerBlock block) { + return dataset; + } + + @Override + public TransactionSet getTransactionSet(LedgerBlock block) { + return null; + } + + @Override + public UserAccountQuery getUserAccountSet(LedgerBlock block) { + return dataset.getUserAccountSet(); + } + + @Override + public DataAccountQuery getDataAccountSet(LedgerBlock block) { + return dataset.getDataAccountSet(); + } + + @Override + public ContractAccountQuery getContractAccountSet(LedgerBlock block) { + return dataset.getContractAccountset(); + } + + @Override + public LedgerBlock retrieveLatestBlock() { + return null; + } + + @Override + public long retrieveLatestBlockHeight() { + return 0; + } + + @Override + public HashDigest retrieveLatestBlockHash() { + return null; + } + + } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java index 65f8738b..69cafb95 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManage.java @@ -1,7 +1,6 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.storage.service.KVStorageService; /** @@ -14,7 +13,7 @@ public interface LedgerManage extends LedgerService { static final String LEDGER_PREFIX = "LDG://"; - LedgerRepository register(HashDigest ledgerHash, KVStorageService storageService); + LedgerQuery register(HashDigest ledgerHash, KVStorageService storageService); void unregister(HashDigest ledgerHash); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java index 2e66b7ca..715c68f3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerManager.java @@ -22,14 +22,6 @@ import com.jd.blockchain.utils.codec.Base58Utils; */ public class LedgerManager implements LedgerManage { - // @Autowired - // private ExistentialKVStorage exPolicyStorage; - // - // @Autowired - // private VersioningKVStorage versioningStorage; - - // private PrivilegeModelSetting privilegeModel = new PrivilegeModelConfig(); - private Map ledgers = new HashMap<>(); @Override @@ -158,6 +150,7 @@ public class LedgerManager implements LedgerManage { public final LedgerRepository ledgerRepo; + @SuppressWarnings("unused") public final KVStorageService storageService; public LedgerRepositoryContext(LedgerRepository ledgerRepo, KVStorageService storageService) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java new file mode 100644 index 00000000..0bed1953 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java @@ -0,0 +1,112 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerBlock; + +public interface LedgerQuery { + + /** + * 账本哈希,这是账本的唯一标识; + * + * @return + */ + HashDigest getHash(); + + /** + * 最新区块高度; + * + * @return + */ + long getLatestBlockHeight(); + + /** + * 最新区块哈希; + * + * @return + */ + HashDigest getLatestBlockHash(); + + /** + * 最新区块; + * + * @return + */ + LedgerBlock getLatestBlock(); + + /** + * 指定高度的区块哈希; + * + * @param height + * @return + */ + HashDigest getBlockHash(long height); + + /** + * 指定高度的区块; + * + * @param height + * @return + */ + LedgerBlock getBlock(long height); + + LedgerAdminInfo getAdminInfo(); + + LedgerAdminInfo getAdminInfo(LedgerBlock block); + + LedgerBlock getBlock(HashDigest hash); + + LedgerDataQuery getDataSet(LedgerBlock block); + + TransactionSet getTransactionSet(LedgerBlock block); + + UserAccountQuery getUserAccountSet(LedgerBlock block); + + DataAccountQuery getDataAccountSet(LedgerBlock block); + + ContractAccountQuery getContractAccountSet(LedgerBlock block); + + default LedgerDataQuery getDataSet() { + return getDataSet(getLatestBlock()); + } + + default TransactionSet getTransactionSet() { + return getTransactionSet(getLatestBlock()); + } + + default UserAccountQuery getUserAccountSet() { + return getUserAccountSet(getLatestBlock()); + } + + default DataAccountQuery getDataAccountSet() { + return getDataAccountSet(getLatestBlock()); + } + + default ContractAccountQuery getContractAccountset() { + return getContractAccountSet(getLatestBlock()); + } + + /** + * 重新检索最新区块,同时更新缓存; + * + * @return + */ + LedgerBlock retrieveLatestBlock(); + + /** + * 重新检索最新区块,同时更新缓存; + * + * @return + */ + long retrieveLatestBlockHeight(); + + /** + * 重新检索最新区块哈希,同时更新缓存; + * + * @return + */ + HashDigest retrieveLatestBlockHash(); + + + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java index 0b85c8c6..6c1bed81 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java @@ -14,6 +14,7 @@ import com.jd.blockchain.ledger.KVDataVO; import com.jd.blockchain.ledger.KVInfoVO; import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerInfo; import com.jd.blockchain.ledger.LedgerMetadata; import com.jd.blockchain.ledger.LedgerTransaction; @@ -27,21 +28,30 @@ import com.jd.blockchain.utils.QueryUtil; public class LedgerQueryService implements BlockchainQueryService { private static final KVDataEntry[] EMPTY_ENTRIES = new KVDataEntry[0]; + + private HashDigest[] ledgerHashs; - private LedgerService ledgerService; + private LedgerQuery ledger; - public LedgerQueryService(LedgerService ledgerService) { - this.ledgerService = ledgerService; + public LedgerQueryService(LedgerQuery ledger) { + this.ledger = ledger; + this.ledgerHashs = new HashDigest[] {ledger.getHash()}; + } + + private void checkLedgerHash(HashDigest ledgerHash) { + if (!ledgerHashs[0].equals(ledgerHash)) { + throw new LedgerException("Unsupport cross chain query!"); + } } @Override public HashDigest[] getLedgerHashs() { - return ledgerService.getLedgerHashs(); + return ledgerHashs; } @Override public LedgerInfo getLedger(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerInfo ledgerInfo = new LedgerInfo(); ledgerInfo.setHash(ledger.getHash()); ledgerInfo.setLatestBlockHash(ledger.getLatestBlockHash()); @@ -51,7 +61,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); LedgerAdminInfo administration = ledger.getAdminInfo(block); return administration; @@ -69,19 +79,19 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public LedgerBlock getBlock(HashDigest ledgerHash, long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); return ledger.getBlock(height); } @Override public LedgerBlock getBlock(HashDigest ledgerHash, HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); return ledger.getBlock(blockHash); } @Override public long getTransactionCount(HashDigest ledgerHash, long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); TransactionSet txset = ledger.getTransactionSet(block); return txset.getTotalCount(); @@ -89,7 +99,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getTransactionCount(HashDigest ledgerHash, HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); TransactionSet txset = ledger.getTransactionSet(block); return txset.getTotalCount(); @@ -97,7 +107,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getTransactionTotalCount(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); TransactionSet txset = ledger.getTransactionSet(block); return txset.getTotalCount(); @@ -105,7 +115,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getDataAccountCount(HashDigest ledgerHash, long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); @@ -113,7 +123,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getDataAccountCount(HashDigest ledgerHash, HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); @@ -121,7 +131,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getDataAccountTotalCount(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); @@ -129,7 +139,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getUserCount(HashDigest ledgerHash, long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); @@ -137,7 +147,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getUserCount(HashDigest ledgerHash, HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); @@ -145,7 +155,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getUserTotalCount(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); @@ -153,7 +163,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getContractCount(HashDigest ledgerHash, long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); @@ -161,7 +171,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getContractCount(HashDigest ledgerHash, HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); @@ -169,7 +179,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getContractTotalCount(HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); @@ -177,7 +187,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public LedgerTransaction[] getTransactions(HashDigest ledgerHash, long height, int fromIndex, int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(height); TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); int lastHeightTxTotalNums = 0; @@ -206,7 +216,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public LedgerTransaction[] getTransactions(HashDigest ledgerHash, HashDigest blockHash, int fromIndex, int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(blockHash); long height = ledgerBlock.getHeight(); TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); @@ -236,7 +246,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public LedgerTransaction getTransactionByContentHash(HashDigest ledgerHash, HashDigest contentHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); TransactionSet txset = ledger.getTransactionSet(block); return txset.get(contentHash); @@ -244,7 +254,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public TransactionState getTransactionStateByContentHash(HashDigest ledgerHash, HashDigest contentHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); TransactionSet txset = ledger.getTransactionSet(block); return txset.getTxState(contentHash); @@ -252,7 +262,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public UserInfo getUser(HashDigest ledgerHash, String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getUser(address); @@ -261,7 +271,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public AccountHeader getDataAccount(HashDigest ledgerHash, String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -272,7 +282,7 @@ public class LedgerQueryService implements BlockchainQueryService { if (keys == null || keys.length == 0) { return EMPTY_ENTRIES; } - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -320,7 +330,7 @@ public class LedgerQueryService implements BlockchainQueryService { throw new ContractException("keys.length!=versions.length!"); } - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -350,8 +360,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public KVDataEntry[] getDataEntries(HashDigest ledgerHash, String address, int fromIndex, int count) { - - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -362,8 +371,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public long getDataEntriesTotalCount(HashDigest ledgerHash, String address) { - - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -373,7 +381,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public ContractInfo getContract(HashDigest ledgerHash, String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getContract(Bytes.fromBase58(address)); @@ -381,7 +389,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public AccountHeader[] getUsers(HashDigest ledgerHash, int fromIndex, int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotalCount()); @@ -390,7 +398,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public AccountHeader[] getDataAccounts(HashDigest ledgerHash, int fromIndex, int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotalCount()); @@ -399,7 +407,7 @@ public class LedgerQueryService implements BlockchainQueryService { @Override public AccountHeader[] getContractAccounts(HashDigest ledgerHash, int fromIndex, int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotalCount()); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java index a8790ab0..e98ff376 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepository.java @@ -2,112 +2,8 @@ package com.jd.blockchain.ledger.core; import java.io.Closeable; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.LedgerAdminInfo; -import com.jd.blockchain.ledger.LedgerBlock; +public interface LedgerRepository extends Closeable, LedgerQuery { -public interface LedgerRepository extends Closeable { - - /** - * 账本哈希,这是账本的唯一标识; - * - * @return - */ - HashDigest getHash(); - - /** - * 最新区块高度; - * - * @return - */ - long getLatestBlockHeight(); - - /** - * 最新区块哈希; - * - * @return - */ - HashDigest getLatestBlockHash(); - - /** - * 最新区块; - * - * @return - */ - LedgerBlock getLatestBlock(); - - /** - * 指定高度的区块哈希; - * - * @param height - * @return - */ - HashDigest getBlockHash(long height); - - /** - * 指定高度的区块; - * - * @param height - * @return - */ - LedgerBlock getBlock(long height); - - LedgerAdminInfo getAdminInfo(); - - LedgerAdminInfo getAdminInfo(LedgerBlock block); - - LedgerBlock getBlock(HashDigest hash); - - LedgerDataQuery getDataSet(LedgerBlock block); - - TransactionSet getTransactionSet(LedgerBlock block); - - UserAccountQuery getUserAccountSet(LedgerBlock block); - - DataAccountQuery getDataAccountSet(LedgerBlock block); - - ContractAccountQuery getContractAccountSet(LedgerBlock block); - - default LedgerDataQuery getDataSet() { - return getDataSet(getLatestBlock()); - } - - default TransactionSet getTransactionSet() { - return getTransactionSet(getLatestBlock()); - } - - default UserAccountQuery getUserAccountSet() { - return getUserAccountSet(getLatestBlock()); - } - - default DataAccountQuery getDataAccountSet() { - return getDataAccountSet(getLatestBlock()); - } - - default ContractAccountQuery getContractAccountSet() { - return getContractAccountSet(getLatestBlock()); - } - - /** - * 重新检索最新区块,同时更新缓存; - * - * @return - */ - LedgerBlock retrieveLatestBlock(); - - /** - * 重新检索最新区块,同时更新缓存; - * - * @return - */ - long retrieveLatestBlockHeight(); - - /** - * 重新检索最新区块哈希,同时更新缓存; - * - * @return - */ - HashDigest retrieveLatestBlockHash(); /** * 创建新区块的编辑器; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java index 763ea51a..ac3dbbec 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/OperationHandle.java @@ -27,6 +27,6 @@ public interface OperationHandle { * @return */ BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); + LedgerQuery ledger, OperationHandleContext handleContext); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index cab2a4e2..a6982585 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -42,13 +42,11 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { private LedgerSecurityManager securityManager; - private LedgerService ledgerService; - private LedgerEditor newBlockEditor; - private LedgerDataQuery ledgerQueryer; + private LedgerQuery ledger; - private OperationHandleRegisteration opHandles; + private OperationHandleRegisteration handlesRegisteration; // 新创建的交易; private LedgerBlock block; @@ -59,18 +57,53 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { private TransactionBatchResult batchResult; + public HashDigest getLedgerHash() { + return ledger.getHash(); + } + /** * @param newBlockEditor 新区块的数据编辑器; * @param ledgerQueryer 账本查询器,只包含新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; * @param opHandles 操作处理对象注册表; */ public TransactionBatchProcessor(LedgerSecurityManager securityManager, LedgerEditor newBlockEditor, - LedgerDataQuery ledgerQueryer, OperationHandleRegisteration opHandles, LedgerService ledgerService) { + LedgerQuery ledger, OperationHandleRegisteration opHandles) { this.securityManager = securityManager; this.newBlockEditor = newBlockEditor; - this.ledgerQueryer = ledgerQueryer; - this.opHandles = opHandles; - this.ledgerService = ledgerService; + this.ledger = ledger; + this.handlesRegisteration = opHandles; + } + + public TransactionBatchProcessor(LedgerRepository ledgerRepo, OperationHandleRegisteration handlesRegisteration) { + this.ledger = ledgerRepo; + this.handlesRegisteration = handlesRegisteration; + + LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); + LedgerDataQuery ledgerDataQuery = ledgerRepo.getDataSet(ledgerBlock); + LedgerAdminDataQuery previousAdminDataset = ledgerDataQuery.getAdminDataset(); + this.securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getAdminInfo().getRolePrivileges(), + previousAdminDataset.getAdminInfo().getUserRoles(), previousAdminDataset.getParticipantDataset(), + ledgerDataQuery.getUserAccountSet()); + + this.newBlockEditor = ledgerRepo.createNextBlock(); + + } + + public static TransactionBatchProcess create(LedgerRepository ledgerRepo, + OperationHandleRegisteration handlesRegisteration) { + LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); + LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); + LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); + + LedgerAdminDataQuery previousAdminDataset = previousBlockDataset.getAdminDataset(); + LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl( + previousAdminDataset.getAdminInfo().getRolePrivileges(), + previousAdminDataset.getAdminInfo().getUserRoles(), previousAdminDataset.getParticipantDataset(), + previousBlockDataset.getUserAccountSet()); + + TransactionBatchProcessor processor = new TransactionBatchProcessor(securityManager, newBlockEditor, ledgerRepo, + handlesRegisteration); + return processor; } /* @@ -228,16 +261,15 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { public void handle(Operation operation) { // assert; Instance of operation are one of User related operations or // DataAccount related operations; - OperationHandle hdl = opHandles.getHandle(operation.getClass()); - hdl.process(operation, dataset, request, ledgerQueryer, this, ledgerService); + OperationHandle hdl = handlesRegisteration.getHandle(operation.getClass()); + hdl.process(operation, dataset, request, ledger, this); } }; OperationHandle opHandle; int opIndex = 0; for (Operation op : ops) { - opHandle = opHandles.getHandle(op.getClass()); - BytesValue opResult = opHandle.process(op, dataset, request, ledgerQueryer, handleContext, - ledgerService); + opHandle = handlesRegisteration.getHandle(op.getClass()); + BytesValue opResult = opHandle.process(op, dataset, request, ledger, handleContext); if (opResult != null) { operationResults.add(new OperationResultData(opIndex, opResult)); } @@ -335,8 +367,8 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { if (batchResult != null) { throw new IllegalStateException("Batch result has already been prepared or canceled!"); } - block = newBlockEditor.prepare(); - batchResult = new TransactionBatchResultHandleImpl(); + this.block = newBlockEditor.prepare(); + this.batchResult = new TransactionBatchResultHandleImpl(); return (TransactionBatchResultHandle) batchResult; } @@ -361,10 +393,12 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { @Override public long blockHeight() { - if (block != null) { - return block.getHeight(); - } - return 0; +// if (block != null) { +// return block.getHeight(); +// } +// return 0; + + return ledger.getLatestBlockHeight(); } private void commitSuccess() { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index 928997fd..416a269d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -6,7 +6,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.factory.annotation.Autowired; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.service.TransactionBatchProcess; import com.jd.blockchain.service.TransactionEngine; @@ -38,17 +37,8 @@ public class TransactionEngineImpl implements TransactionEngine { LedgerRepository ledgerRepo = ledgerService.getLedger(ledgerHash); - LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); - LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); - - LedgerAdminDataQuery previousAdminDataset = previousBlockDataset.getAdminDataset(); - LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl( - previousAdminDataset.getAdminInfo().getRolePrivileges(), - previousAdminDataset.getAdminInfo().getUserRoles(), previousAdminDataset.getParticipantDataset(), - previousBlockDataset.getUserAccountSet()); - batch = new InnerTransactionBatchProcessor(ledgerHash, securityManager, newBlockEditor, previousBlockDataset, - opHdlRegs, ledgerService, ledgerBlock.getHeight()); + batch = new InnerTransactionBatchProcessor(ledgerRepo, + opHdlRegs); batchs.put(ledgerHash, batch); return batch; } @@ -66,22 +56,16 @@ public class TransactionEngineImpl implements TransactionEngine { private HashDigest ledgerHash; - private long blockHeight; - /** * 创建交易批处理器; * - * @param ledgerHash 账本哈希; - * @param newBlockEditor 新区块的数据编辑器; - * @param previousBlockDataset 新区块的前一个区块的数据集;即未提交新区块之前的经过共识的账本最新数据集; - * @param opHandles 操作处理对象注册表; + * @param ledgerRepo 账本; + * @param handlesRegisteration 操作处理对象注册表; + * @param blockHeight */ - public InnerTransactionBatchProcessor(HashDigest ledgerHash, LedgerSecurityManager securityManager, - LedgerEditor newBlockEditor, LedgerDataQuery previousBlockDataset, - OperationHandleRegisteration opHandles, LedgerService ledgerService, long blockHeight) { - super(securityManager, newBlockEditor, previousBlockDataset, opHandles, ledgerService); - this.ledgerHash = ledgerHash; - this.blockHeight = blockHeight; + public InnerTransactionBatchProcessor(LedgerRepository ledgerRepo, + OperationHandleRegisteration handlesRegisteration) { + super(ledgerRepo, handlesRegisteration); } @Override @@ -96,9 +80,5 @@ public class TransactionEngineImpl implements TransactionEngine { finishBatch(ledgerHash); } - @Override - public long blockHeight() { - return this.blockHeight; - } } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java index 4a626c70..dee7fb48 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRolesPrivileges.java @@ -21,9 +21,9 @@ class UserRolesPrivileges { private Bytes userAddress; - private PrivilegeBitset ledgerPrivileges; + private LedgerPrivilege ledgerPrivileges; - private PrivilegeBitset transactionPrivileges; + private TransactionPrivilege transactionPrivileges; public UserRolesPrivileges(Bytes userAddress, RolesPolicy policy, Collection privilegesList) { this.userAddress = userAddress; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java index 36a2a0ae..2b259d4a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbstractLedgerOperationHandle.java @@ -4,9 +4,8 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionPermission; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; @@ -37,7 +36,7 @@ public abstract class AbstractLedgerOperationHandle impleme // public final boolean support(Class operationType) { // return SUPPORTED_OPERATION_TYPE.isAssignableFrom(operationType); // } - + @Override public Class getOperationType() { return SUPPORTED_OPERATION_TYPE; @@ -45,8 +44,7 @@ public abstract class AbstractLedgerOperationHandle impleme @Override public final BytesValue process(Operation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension requestContext, LedgerQuery ledger, OperationHandleContext handleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); securityPolicy.checkEndpointPermission(TransactionPermission.DIRECT_OPERATION, MultiIDsPolicy.AT_LEAST_ONE); @@ -54,20 +52,12 @@ public abstract class AbstractLedgerOperationHandle impleme // 操作账本; @SuppressWarnings("unchecked") T concretedOp = (T) op; - doProcess(concretedOp, newBlockDataset, requestContext, previousBlockDataset, handleContext, ledgerService); + doProcess(concretedOp, newBlockDataset, requestContext, ledger, handleContext); // 账本操作没有返回值; return null; } - /** - * @param op - * @param newBlockDataset - * @param requestContext - * @param previousBlockDataset - * @param handleContext - * @param ledgerService - */ protected abstract void doProcess(T op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService); + LedgerQuery ledger, OperationHandleContext handleContext); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java index af1d95b4..eeb3ceb0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java @@ -11,10 +11,9 @@ import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.core.ContractAccount; import com.jd.blockchain.ledger.core.ContractAccountQuery; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.LedgerQueryService; -import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; @@ -24,7 +23,7 @@ import com.jd.blockchain.ledger.core.TransactionRequestExtension; @Service public abstract class AbtractContractEventSendOperationHandle implements OperationHandle { - + @Override public Class getOperationType() { return ContractEventSendOperation.class; @@ -32,7 +31,7 @@ public abstract class AbtractContractEventSendOperationHandle implements Operati @Override public BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + LedgerQuery ledger, OperationHandleContext opHandleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); securityPolicy.checkEndpointPermission(TransactionPermission.CONTRACT_OPERATION, MultiIDsPolicy.AT_LEAST_ONE); @@ -40,23 +39,21 @@ public abstract class AbtractContractEventSendOperationHandle implements Operati // 操作账本; ContractEventSendOperation contractOP = (ContractEventSendOperation) op; - return doProcess(requestContext, contractOP, newBlockDataset, previousBlockDataset, opHandleContext, - ledgerService); + return doProcess(requestContext, contractOP, newBlockDataset, ledger, opHandleContext); } private BytesValue doProcess(TransactionRequestExtension request, ContractEventSendOperation contractOP, - LedgerDataset newBlockDataset, LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, - LedgerService ledgerService) { + LedgerDataset newBlockDataset, LedgerQuery ledger, OperationHandleContext opHandleContext) { // 先从账本校验合约的有效性; // 注意:必须在前一个区块的数据集中进行校验,因为那是经过共识的数据;从当前新区块链数据集校验则会带来攻击风险:未经共识的合约得到执行; - ContractAccountQuery contractSet = previousBlockDataset.getContractAccountset(); + ContractAccountQuery contractSet = ledger.getContractAccountset(); if (!contractSet.contains(contractOP.getContractAddress())) { throw new LedgerException(String.format("Contract was not registered! --[ContractAddress=%s]", contractOP.getContractAddress())); } // 创建合约的账本上下文实例; - LedgerQueryService queryService = new LedgerQueryService(ledgerService); + LedgerQueryService queryService = new LedgerQueryService(ledger); ContractLedgerContext ledgerContext = new ContractLedgerContext(queryService, opHandleContext); // 先检查合约引擎是否已经加载合约;如果未加载,再从账本中读取合约代码并装载到引擎中执行; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java index e054a1e2..cbec5e33 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java @@ -2,9 +2,8 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -18,8 +17,8 @@ public class ContractCodeDeployOperationHandle extends AbstractLedgerOperationHa @Override protected void doProcess(ContractCodeDeployOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension requestContext, LedgerQuery ledger, + OperationHandleContext handleContext) { // TODO: 校验合约代码的正确性; // TODO: 请求者应该提供合约账户的公钥签名,以确保注册人对注册的地址和公钥具有合法的使用权; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index 6661a6d5..01cc065d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -6,9 +6,8 @@ import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; import com.jd.blockchain.ledger.DataVersionConflictException; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.core.DataAccount; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -23,8 +22,8 @@ public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHand @Override protected void doProcess(DataAccountKVSetOperation kvWriteOp, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension requestContext, LedgerQuery ledger, + OperationHandleContext handleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); securityPolicy.checkEndpointPermission(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIDsPolicy.AT_LEAST_ONE); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java index 0c031d5d..e5c596c1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountRegisterOperationHandle.java @@ -3,9 +3,8 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.DataAccountRegisterOperation; import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -16,11 +15,10 @@ public class DataAccountRegisterOperationHandle extends AbstractLedgerOperationH public DataAccountRegisterOperationHandle() { super(DataAccountRegisterOperation.class); } - + @Override protected void doProcess(DataAccountRegisterOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension requestContext, LedgerQuery ledger, OperationHandleContext handleContext) { // TODO: 请求者应该提供数据账户的公钥签名,以更好地确保注册人对该地址和公钥具有合法使用权; // 权限校验; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java index 39862be0..ff22f777 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/LedgerInitOperationHandle.java @@ -3,9 +3,8 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.LedgerInitOperation; import com.jd.blockchain.ledger.Operation; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestExtension; @@ -19,7 +18,7 @@ public class LedgerInitOperationHandle implements OperationHandle { @Override public BytesValue process(Operation op, LedgerDataset newBlockDataset, TransactionRequestExtension requestContext, - LedgerDataQuery previousBlockDataset, OperationHandleContext handleContext, LedgerService ledgerService) { + LedgerQuery ledger,OperationHandleContext handleContext) { // 对初始化操作不需要做任何处理; return null; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java index 3ae02b4e..896ae229 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/RolesConfigureOperationHandle.java @@ -5,9 +5,8 @@ import com.jd.blockchain.ledger.RolePrivilegeSettings; import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.RolesConfigureOperation; import com.jd.blockchain.ledger.RolesConfigureOperation.RolePrivilegeEntry; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -21,8 +20,7 @@ public class RolesConfigureOperationHandle extends AbstractLedgerOperationHandle @Override protected void doProcess(RolesConfigureOperation operation, LedgerDataset newBlockDataset, - TransactionRequestExtension request, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension request, LedgerQuery ledger, OperationHandleContext handleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); securityPolicy.checkEndpointPermission(LedgerPermission.CONFIGURE_ROLES, MultiIDsPolicy.AT_LEAST_ONE); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java index b9d3614d..156fd92b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java @@ -10,9 +10,8 @@ import com.jd.blockchain.ledger.UserAuthorizeOperation; import com.jd.blockchain.ledger.UserAuthorizeOperation.UserRolesEntry; import com.jd.blockchain.ledger.UserRoles; import com.jd.blockchain.ledger.UserRolesSettings; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -27,8 +26,8 @@ public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle< @Override protected void doProcess(UserAuthorizeOperation operation, LedgerDataset newBlockDataset, - TransactionRequestExtension request, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension request, LedgerQuery ledger, + OperationHandleContext handleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); securityPolicy.checkEndpointPermission(LedgerPermission.CONFIGURE_ROLES, MultiIDsPolicy.AT_LEAST_ONE); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java index 3b8f043d..829d56ad 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserRegisterOperationHandle.java @@ -3,9 +3,8 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.ledger.BlockchainIdentity; import com.jd.blockchain.ledger.LedgerPermission; import com.jd.blockchain.ledger.UserRegisterOperation; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.SecurityContext; @@ -20,8 +19,7 @@ public class UserRegisterOperationHandle extends AbstractLedgerOperationHandle @@ -44,17 +45,17 @@ public enum LedgerPermission { * * 如果不具备此项权限,则无法注册用户; */ - REGISTER_USER((byte) 0x11), + REGISTER_USER((byte) 0x06), /** * 注册数据账户;
        */ - REGISTER_DATA_ACCOUNT((byte) 0x12), + REGISTER_DATA_ACCOUNT((byte) 0x07), /** * 注册合约;
        */ - REGISTER_CONTRACT((byte) 0x13), + REGISTER_CONTRACT((byte) 0x08), /** * 升级合约 @@ -64,12 +65,12 @@ public enum LedgerPermission { /** * 设置用户属性;
        */ - SET_USER_ATTRIBUTES((byte) 0x15), + SET_USER_ATTRIBUTES((byte) 0x09), /** * 写入数据账户;
        */ - WRITE_DATA_ACCOUNT((byte) 0x16), + WRITE_DATA_ACCOUNT((byte) 0x0A), /** * 参与方核准交易;
        @@ -78,14 +79,14 @@ public enum LedgerPermission { *

        * 只对交易请求的节点签名列表{@link TransactionRequest#getNodeSignatures()}的用户产生影响; */ - APPROVE_TX((byte) 0x0C), + APPROVE_TX((byte) 0x0B), /** * 参与方共识交易;
        * * 如果不具备此项权限,则无法作为共识节点接入并对交易进行共识; */ - CONSENSUS_TX((byte) 0x0D); + CONSENSUS_TX((byte) 0x0C); @EnumField(type = PrimitiveType.INT8) public final byte CODE; @@ -94,4 +95,9 @@ public enum LedgerPermission { this.CODE = code; } + @Override + public byte getCode() { + return CODE; + } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java index dad59a41..01261cc2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerPrivilege.java @@ -8,22 +8,16 @@ package com.jd.blockchain.ledger; */ public class LedgerPrivilege extends PrivilegeBitset { - private static final CodeIndexer CODE_INDEXER = new LedgerPermissionCodeIndexer(); - public LedgerPrivilege() { - super(CODE_INDEXER); } public LedgerPrivilege(byte[] codeBytes) { - super(codeBytes, CODE_INDEXER); + super(codeBytes); } - private static class LedgerPermissionCodeIndexer implements CodeIndexer { - - @Override - public int getCodeIndex(LedgerPermission permission) { - return permission.CODE & 0xFF; - } - + @Override + public LedgerPrivilege clone() { + return (LedgerPrivilege) super.clone(); } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java index 1282530d..3b038fbb 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/PrivilegeBitset.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger; import java.util.BitSet; +import com.jd.blockchain.utils.Int8Code; import com.jd.blockchain.utils.io.BytesSerializable; /** @@ -10,43 +11,46 @@ import com.jd.blockchain.utils.io.BytesSerializable; * @author huanghaiquan * */ -public class PrivilegeBitset> implements Privilege, BytesSerializable { - // 加入前缀位,可避免序列化时输出空的字节数组; - private static final boolean[] PREFIX = { false, false, false, true, false, false, false, true }; - private static final int OFFSET = PREFIX.length; - private static final int MAX_SIZE = 256 - PREFIX.length; +public abstract class PrivilegeBitset & Int8Code> implements Privilege, BytesSerializable, Cloneable { + // 加入1个字节的前缀位 0xF1,可避免序列化时输出空的字节数组; + private static final byte PREFIX = (byte) 0xF1; + private static final byte[] PREFIX_BYTES = { PREFIX }; + private static final int OFFSET = 8; + private static final int MAX_SIZE = 32; - private BitSet permissionBits; + // 前缀中置为 1 的位数,值 0xF1 有 5 个比特位为 1; + private static final int PREFIX_CARDINALITY = 5; - private CodeIndexer codeIndexer; + private BitSet permissionBits; - public PrivilegeBitset(CodeIndexer codeIndexer) { - this.permissionBits = new BitSet(); - this.codeIndexer = codeIndexer; + public PrivilegeBitset() { // 设置前缀; - for (int i = 0; i < PREFIX.length; i++) { - permissionBits.set(i, PREFIX[i]); - } + this.permissionBits = BitSet.valueOf(PREFIX_BYTES); } - public PrivilegeBitset(byte[] codeBytes, CodeIndexer codeIndexer) { + /** + * @param codeBytes 权限的字节位; + * @param codeIndexer + */ + public PrivilegeBitset(byte[] codeBytes) { + // 检查长度; + if (codeBytes.length == 0) { + throw new IllegalArgumentException("Empty code bytes!"); + } if (codeBytes.length > MAX_SIZE) { throw new IllegalArgumentException( "The size of code bytes specified to PrivilegeBitset exceed the max size[" + MAX_SIZE + "]!"); } - this.permissionBits = BitSet.valueOf(codeBytes); - this.codeIndexer = codeIndexer; // 校验前缀; - for (int i = 0; i < PREFIX.length; i++) { - if (permissionBits.get(i) != PREFIX[i]) { - throw new IllegalArgumentException("The code bytes is not match the privilege prefix code!"); - } + if (codeBytes[0] != PREFIX) { + throw new IllegalArgumentException("The code bytes is not match the privilege prefix code!"); } + + this.permissionBits = BitSet.valueOf(codeBytes); } - private PrivilegeBitset(BitSet bits, CodeIndexer codeIndexer) { + protected PrivilegeBitset(BitSet bits) { this.permissionBits = bits; - this.codeIndexer = codeIndexer; } public boolean isEnable(E permission) { @@ -134,15 +138,29 @@ public class PrivilegeBitset> implements Privilege, BytesSe return this; } - public PrivilegeBitset clone() { - return new PrivilegeBitset((BitSet) permissionBits.clone(), codeIndexer); + @Override + public Privilege clone() { + try { + BitSet bitSet = (BitSet) permissionBits.clone(); + @SuppressWarnings("unchecked") + PrivilegeBitset privilege = (PrivilegeBitset) super.clone(); + privilege.permissionBits = bitSet; + return privilege; + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + protected BitSet cloneBitSet() { + return (BitSet) permissionBits.clone(); } private int index(E permission) { - return OFFSET + codeIndexer.getCodeIndex(permission); + return OFFSET + permission.getCode(); } - static interface CodeIndexer> { - int getCodeIndex(E permission); + public int getPermissionCount() { + return permissionBits.cardinality() - PREFIX_CARDINALITY; } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java index b197820e..e251fac4 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPermission.java @@ -4,6 +4,7 @@ import com.jd.blockchain.binaryproto.EnumContract; import com.jd.blockchain.binaryproto.EnumField; import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.utils.Int8Code; /** * TxPermission 交易权限表示一个用户可以发起的交易类型; @@ -12,7 +13,7 @@ import com.jd.blockchain.consts.DataCodes; * */ @EnumContract(code = DataCodes.ENUM_TX_PERMISSION) -public enum TransactionPermission { +public enum TransactionPermission implements Int8Code { /** * 交易中包含指令操作; @@ -31,4 +32,9 @@ public enum TransactionPermission { this.CODE = code; } + @Override + public byte getCode() { + return CODE; + } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java index 755a75a7..dddd76d6 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionPrivilege.java @@ -2,22 +2,16 @@ package com.jd.blockchain.ledger; public class TransactionPrivilege extends PrivilegeBitset { - private static final CodeIndexer CODE_INDEXER = new TransactionPermissionCodeIndexer(); - public TransactionPrivilege() { - super(CODE_INDEXER); } public TransactionPrivilege(byte[] codeBytes) { - super(codeBytes, CODE_INDEXER); + super(codeBytes); } - private static class TransactionPermissionCodeIndexer implements CodeIndexer { - - @Override - public int getCodeIndex(TransactionPermission permission) { - return permission.CODE & 0xFF; - } - + @Override + public TransactionPrivilege clone() { + return (TransactionPrivilege) super.clone(); } + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java index d73d67a2..2cae56d1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java @@ -1,6 +1,7 @@ package com.jd.blockchain.ledger; import java.util.Collection; +import java.util.Collections; import java.util.Set; import java.util.TreeSet; @@ -49,7 +50,7 @@ public class UserRoles implements RoleSet { public RolesPolicy getPolicy() { return policy; } - + public void setPolicy(RolesPolicy policy) { this.policy = policy; } @@ -62,6 +63,10 @@ public class UserRoles implements RoleSet { public String[] getRoles() { return roles.toArray(new String[roles.size()]); } + + public Set getRoleSet(){ + return Collections.unmodifiableSet(roles); + } public long getVersion() { return version; diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java index 992cf1a0..6dfd85d1 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java @@ -17,7 +17,7 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.core.ContractAccountQuery; import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DataAccountQuery; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.ParticipantCertData; import com.jd.blockchain.ledger.core.TransactionSet; @@ -42,7 +42,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}") @Override public LedgerInfo getLedger(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); // TODO: 需要配置返回值的 spring MsgQueueMessageDispatcher // ,对返回对象仅仅序列化声明的返回值类型的属性,而不是整个对象本身; LedgerInfo ledgerInfo = new LedgerInfo(); @@ -55,7 +55,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants") @Override public ParticipantNode[] getConsensusParticipants(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerAdminInfo ledgerAdministration = ledger.getAdminInfo(); long participantCount = ledgerAdministration.getParticipantCount(); if (participantCount <= 0) { @@ -76,7 +76,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/admininfo") @Override public LedgerAdminInfo getLedgerAdminInfo(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerAdminInfo ledgerAdministration = ledger.getAdminInfo(); return ledgerAdministration; } @@ -84,7 +84,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/metadata") @Override public LedgerMetadata getLedgerMetadata(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerAdminInfo ledgerAdministration = ledger.getAdminInfo(); LedgerMetadata ledgerMetadata = ledgerAdministration.getMetadata(); return ledgerMetadata; @@ -94,7 +94,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public LedgerBlock getBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHeight") long blockHeight) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); // TODO: 需要配置返回值的 spring MsgQueueMessageDispatcher // ,对返回对象仅仅序列化声明的返回值类型的属性,而不是整个对象本身; return ledger.getBlock(blockHeight); @@ -104,7 +104,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public LedgerBlock getBlock(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); // TODO: 需要配置返回值的 spring MsgQueueMessageDispatcher // ,对返回对象仅仅序列化声明的返回值类型的属性,而不是整个对象本身; return ledger.getBlock(blockHash); @@ -114,7 +114,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHeight") long blockHeight) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHeight); TransactionSet txSet = ledger.getTransactionSet(block); return txSet.getTotalCount(); @@ -124,7 +124,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getTransactionCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); TransactionSet txSet = ledger.getTransactionSet(block); return txSet.getTotalCount(); @@ -133,7 +133,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/txs/count") @Override public long getTransactionTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); TransactionSet txSet = ledger.getTransactionSet(block); return txSet.getTotalCount(); @@ -143,7 +143,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHeight") long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); @@ -153,7 +153,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getDataAccountCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); @@ -162,7 +162,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/count") @Override public long getDataAccountTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getTotalCount(); @@ -172,7 +172,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHeight") long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); @@ -182,7 +182,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getUserCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); @@ -191,7 +191,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/count") @Override public long getUserTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getTotalCount(); @@ -201,7 +201,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHeight") long height) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); @@ -211,7 +211,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public long getContractCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "blockHash") HashDigest blockHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); @@ -220,7 +220,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/count") @Override public long getContractTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getTotalCount(); @@ -233,7 +233,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(blockHeight); TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); int lastHeightTxTotalNums = 0; @@ -266,7 +266,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHash") HashDigest blockHash, @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(blockHash); long height = ledgerBlock.getHeight(); TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); @@ -298,7 +298,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public LedgerTransaction getTransactionByContentHash(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "contentHash") HashDigest contentHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); TransactionSet txset = ledger.getTransactionSet(block); return txset.get(contentHash); @@ -308,7 +308,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public TransactionState getTransactionStateByContentHash(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "contentHash") HashDigest contentHash) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); TransactionSet txset = ledger.getTransactionSet(block); return txset.getTxState(contentHash); @@ -318,7 +318,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public UserInfo getUser(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "address") String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); return userAccountSet.getUser(address); @@ -328,7 +328,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public AccountHeader getDataAccount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "address") String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); return dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -342,7 +342,7 @@ public class LedgerQueryController implements BlockchainQueryService { if (keys == null || keys.length == 0) { return null; } - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -391,7 +391,7 @@ public class LedgerQueryController implements BlockchainQueryService { throw new ContractException("keys.length!=versions.length!"); } - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -426,7 +426,7 @@ public class LedgerQueryController implements BlockchainQueryService { @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -440,7 +440,7 @@ public class LedgerQueryController implements BlockchainQueryService { public long getDataEntriesTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "address") String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); @@ -452,7 +452,7 @@ public class LedgerQueryController implements BlockchainQueryService { @Override public ContractInfo getContract(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @PathVariable(name = "address") String address) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); return contractAccountSet.getContract(Bytes.fromBase58(address)); @@ -471,7 +471,7 @@ public class LedgerQueryController implements BlockchainQueryService { public AccountHeader[] getUsers(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotalCount()); @@ -491,7 +491,7 @@ public class LedgerQueryController implements BlockchainQueryService { public AccountHeader[] getDataAccounts(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotalCount()); @@ -503,7 +503,7 @@ public class LedgerQueryController implements BlockchainQueryService { public AccountHeader[] getContractAccounts(@PathVariable(name = "ledgerHash") HashDigest ledgerHash, @RequestParam(name = "fromIndex", required = false, defaultValue = "0") int fromIndex, @RequestParam(name = "count", required = false, defaultValue = "-1") int count) { - LedgerRepository ledger = ledgerService.getLedger(ledgerHash); + LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotalCount()); diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index 9ad0a94b..5d8b0433 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -49,7 +49,7 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerAdminDataQuery; import com.jd.blockchain.ledger.core.LedgerManage; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.peer.ConsensusRealm; import com.jd.blockchain.peer.LedgerBindingConfigAware; import com.jd.blockchain.peer.PeerManage; @@ -224,7 +224,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag LedgerBindingConfig.BindingConfig bindingConfig = config.getLedger(ledgerHash); DbConnection dbConnNew = connFactory.connect(bindingConfig.getDbConnection().getUri(), bindingConfig.getDbConnection().getPassword()); - LedgerRepository ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); + LedgerQuery ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); // load provider; LedgerAdminInfo ledgerAdminAccount = ledgerRepository.getAdminInfo(); diff --git a/source/test/pom.xml b/source/test/pom.xml index bf16a95f..14bf30e9 100644 --- a/source/test/pom.xml +++ b/source/test/pom.xml @@ -13,9 +13,9 @@ test-consensus-client test-consensus-node - test-ledger-core + test-ledger test-integration - + diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java index f00b50dd..4cb241e6 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java @@ -38,7 +38,7 @@ import com.jd.blockchain.ledger.UserInfo; import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.KVStorageService; @@ -150,16 +150,16 @@ public class IntegrationTest { private void testConsistencyAmongNodes(IntegratedContext context) { int[] ids = context.getNodeIds(); Node[] nodes = new Node[ids.length]; - LedgerRepository[] ledgers = new LedgerRepository[ids.length]; + LedgerQuery[] ledgers = new LedgerQuery[ids.length]; for (int i = 0; i < nodes.length; i++) { nodes[i] = context.getNode(ids[i]); HashDigest ledgerHash = nodes[i].getLedgerManager().getLedgerHashs()[0]; ledgers[i] = nodes[i].getLedgerManager().getLedger(ledgerHash); } - LedgerRepository ledger0 = ledgers[0]; + LedgerQuery ledger0 = ledgers[0]; LedgerBlock latestBlock0 = ledger0.retrieveLatestBlock(); for (int i = 1; i < ledgers.length; i++) { - LedgerRepository otherLedger = ledgers[i]; + LedgerQuery otherLedger = ledgers[i]; LedgerBlock otherLatestBlock = otherLedger.retrieveLatestBlock(); } } @@ -212,7 +212,7 @@ public class IntegrationTest { TransactionResponse txResp = prepTx.commit(); Node node0 = context.getNode(0); - LedgerRepository ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); + LedgerQuery ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); ledgerOfNode0.retrieveLatestBlock(); // 更新内存 // 先验证应答 @@ -250,7 +250,7 @@ public class IntegrationTest { KVStorageService storageService = node0.getStorageDB().connect(memDbConnString).getStorageService(); - LedgerRepository ledgerOfNode0 = ledgerManager.register(ledgerHash, storageService); + LedgerQuery ledgerOfNode0 = ledgerManager.register(ledgerHash, storageService); return user; } @@ -282,7 +282,7 @@ public class IntegrationTest { KVStorageService storageService = node0.getStorageDB().connect(memDbConnString).getStorageService(); - LedgerRepository ledgerOfNode0 = ledgerManager.register(ledgerHash, storageService); + LedgerQuery ledgerOfNode0 = ledgerManager.register(ledgerHash, storageService); long latestBlockHeight = ledgerOfNode0.retrieveLatestBlockHeight(); return dataAccount; @@ -301,7 +301,7 @@ public class IntegrationTest { KVStorageService storageService = node0.getStorageDB().connect(memDbConnString).getStorageService(); - LedgerRepository ledgerOfNode0 = ledgerManager.register(ledgerHash, storageService); + LedgerQuery ledgerOfNode0 = ledgerManager.register(ledgerHash, storageService); // getLedgerHashs HashDigest[] ledgerHashs = blockchainService.getLedgerHashs(); @@ -494,10 +494,10 @@ public class IntegrationTest { HashDigest ledgerHash2 = callback2.waitReturn(); HashDigest ledgerHash3 = callback3.waitReturn(); - LedgerRepository ledger0 = nodeCtx0.registLedger(ledgerHash0); - LedgerRepository ledger1 = nodeCtx1.registLedger(ledgerHash1); - LedgerRepository ledger2 = nodeCtx2.registLedger(ledgerHash2); - LedgerRepository ledger3 = nodeCtx3.registLedger(ledgerHash3); + LedgerQuery ledger0 = nodeCtx0.registLedger(ledgerHash0); + LedgerQuery ledger1 = nodeCtx1.registLedger(ledgerHash1); + LedgerQuery ledger2 = nodeCtx2.registLedger(ledgerHash2); + LedgerQuery ledger3 = nodeCtx3.registLedger(ledgerHash3); IntegratedContext context = new IntegratedContext(); @@ -577,7 +577,7 @@ public class IntegrationTest { txResp.getContentHash(); Node node0 = context.getNode(0); - LedgerRepository ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); + LedgerQuery ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); LedgerBlock block = ledgerOfNode0.getBlock(txResp.getBlockHeight()); byte[] contractCodeInDb = ledgerOfNode0.getContractAccountSet(block).getContract(contractDeployKey.getAddress()) .getChainCode(); @@ -615,7 +615,7 @@ public class IntegrationTest { LedgerInfo latestLedgerInfo = blockchainService.getLedger(ledgerHash); Node node0 = context.getNode(0); - LedgerRepository ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); + LedgerQuery ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); LedgerBlock backgroundLedgerBlock = ledgerOfNode0.retrieveLatestBlock(); // 验证合约中的赋值,外部可以获得; @@ -657,7 +657,7 @@ public class IntegrationTest { // 验证结果; Node node0 = context.getNode(0); - LedgerRepository ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); + LedgerQuery ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); LedgerBlock block = ledgerOfNode0.getBlock(txResp.getBlockHeight()); BytesValue val1InDb = ledgerOfNode0.getDataAccountSet(block).getDataAccount(contractDataKey.getAddress()) .getBytes("A"); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java index 7d9f8019..7c959bc3 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/consensus/ConsensusTest.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; @@ -118,7 +118,7 @@ public class ConsensusTest { BlockchainKeypair[] keys = generateKeys(batchSize); HashDigest ledgerHash = node0.getLedgerManager().getLedgerHashs()[0]; - LedgerRepository ledger = node0.getLedgerManager().getLedger(ledgerHash); + LedgerQuery ledger = node0.getLedgerManager().getLedger(ledgerHash); PreparedTransaction[] ptxs = prepareTransactions_RegisterDataAcount(keys, node0.getPartiKeyPair(), ledgerHash, blockchainService); @@ -339,7 +339,7 @@ public class ConsensusTest { this.serverAddress = serverAddress; } - public LedgerRepository registLedger(HashDigest ledgerHash) { + public LedgerQuery registLedger(HashDigest ledgerHash) { // LedgerManage ledgerManager = ctx.getBean(LedgerManage.class); // // DbConnectionFactory dbConnFactory = ctx.getBean(DbConnectionFactory.class); @@ -347,7 +347,7 @@ public class ConsensusTest { // dbConnConfig.getPassword()); DbConnection conn = db.connect(dbConnConfig.getUri(), dbConnConfig.getPassword()); - LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); + LedgerQuery ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); return ledgerRepo; } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java index 47ad09d8..7f23a276 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/GlobalPerformanceTest.java @@ -32,7 +32,7 @@ import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; @@ -116,7 +116,7 @@ public class GlobalPerformanceTest { BlockchainKeypair[] keys = generateKeys(batchSize); HashDigest ledgerHash = node0.getLedgerManager().getLedgerHashs()[0]; - LedgerRepository ledger = node0.getLedgerManager().getLedger(ledgerHash); + LedgerQuery ledger = node0.getLedgerManager().getLedger(ledgerHash); PreparedTransaction[] ptxs = prepareTransactions_RegisterDataAcount(keys, node0.getPartiKeyPair(), ledgerHash, blockchainService); @@ -338,7 +338,7 @@ public class GlobalPerformanceTest { this.serverAddress = serverAddress; } - public LedgerRepository registLedger(HashDigest ledgerHash) { + public LedgerQuery registLedger(HashDigest ledgerHash) { // LedgerManage ledgerManager = ctx.getBean(LedgerManage.class); // // DbConnectionFactory dbConnFactory = ctx.getBean(DbConnectionFactory.class); @@ -347,7 +347,7 @@ public class GlobalPerformanceTest { DbConnection conn = db.connect(dbConnConfig.getUri(), dbConnConfig.getPassword()); conns.add(conn); - LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); + LedgerQuery ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); return ledgerRepo; } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index e94fcfaa..33a9f9f4 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -25,7 +25,7 @@ import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; @@ -106,10 +106,10 @@ public class LedgerInitializeTest { HashDigest ledgerHash2 = callback2.waitReturn(); HashDigest ledgerHash3 = callback3.waitReturn(); - LedgerRepository ledger0 = node0.registLedger(ledgerHash0, memoryConnString[0]); - LedgerRepository ledger1 = node1.registLedger(ledgerHash1, memoryConnString[1]); - LedgerRepository ledger2 = node2.registLedger(ledgerHash2, memoryConnString[2]); - LedgerRepository ledger3 = node3.registLedger(ledgerHash3, memoryConnString[3]); + LedgerQuery ledger0 = node0.registLedger(ledgerHash0, memoryConnString[0]); + LedgerQuery ledger1 = node1.registLedger(ledgerHash1, memoryConnString[1]); + LedgerQuery ledger2 = node2.registLedger(ledgerHash2, memoryConnString[2]); + LedgerQuery ledger3 = node3.registLedger(ledgerHash3, memoryConnString[3]); LedgerBlock genesisBlock = ledger0.getLatestBlock(); @@ -221,7 +221,7 @@ public class LedgerInitializeTest { return invoker.start(); } - public LedgerRepository registLedger(HashDigest ledgerHash, String connString) { + public LedgerQuery registLedger(HashDigest ledgerHash, String connString) { return ledgerManager.register(ledgerHash, memoryDBConnFactory.connect(connString).getStorageService()); } } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index f5db2162..f1ff6770 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.storage.service.DbConnection; @@ -294,10 +294,10 @@ public class LedgerInitializeWebTest { HashDigest ledgerHash2 = callback2.waitReturn(); HashDigest ledgerHash3 = callback3.waitReturn(); - LedgerRepository ledger0 = node0.registLedger(ledgerHash0); - LedgerRepository ledger1 = node1.registLedger(ledgerHash1); - LedgerRepository ledger2 = node2.registLedger(ledgerHash2); - LedgerRepository ledger3 = node3.registLedger(ledgerHash3); + LedgerQuery ledger0 = node0.registLedger(ledgerHash0); + LedgerQuery ledger1 = node1.registLedger(ledgerHash1); + LedgerQuery ledger2 = node2.registLedger(ledgerHash2); + LedgerQuery ledger3 = node3.registLedger(ledgerHash3); LedgerBlock genesisBlock = ledger0.getLatestBlock(); @@ -390,7 +390,7 @@ public class LedgerInitializeWebTest { this.serverAddress = serverAddress; } - public LedgerRepository registLedger(HashDigest ledgerHash) { + public LedgerQuery registLedger(HashDigest ledgerHash) { // LedgerManage ledgerManager = ctx.getBean(LedgerManage.class); // // DbConnectionFactory dbConnFactory = ctx.getBean(DbConnectionFactory.class); @@ -400,7 +400,7 @@ public class LedgerInitializeWebTest { // DbConnection conn = db.connect(dbConnConfig.getUri(), // dbConnConfig.getPassword()); DbConnection conn = db.connect(dbConnConfig.getUri()); - LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); + LedgerQuery ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); return ledgerRepo; } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index e8769ee4..90bf9f0f 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -294,7 +294,7 @@ public class LedgerPerformanceTest { LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); LedgerEditor newEditor = ledger.createNextBlock(); TransactionBatchProcessor txProc = new TransactionBatchProcessor(DEFAULT_SECURITY_MANAGER, newEditor, - previousDataSet, opHandler, ledgerManager); + ledger, opHandler); // 准备请求 int totalCount = batchSize * batchCount; @@ -332,7 +332,7 @@ public class LedgerPerformanceTest { LedgerEditor newEditor = ledger.createNextBlock(); TransactionBatchProcessor txProc = new TransactionBatchProcessor(DEFAULT_SECURITY_MANAGER, newEditor, - previousDataSet, opHandler, ledgerManager); + ledger, opHandler); testTxExec(txList, i * batchSize, batchSize, txProc); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index cd779963..34c056da 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.core.LedgerConfiguration; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.storage.service.DbConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerInitProcess; @@ -187,7 +187,7 @@ public class Utils { return invoker.start(); } - public LedgerRepository registLedger(HashDigest ledgerHash, DBConnectionConfig dbConnConf) { + public LedgerQuery registLedger(HashDigest ledgerHash, DBConnectionConfig dbConnConf) { return ledgerManager.register(ledgerHash, dbConnFactory.connect(dbConnConf.getUri()).getStorageService()); } } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index b5b24f92..ab73b3b4 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -35,7 +35,7 @@ import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.AsymmetricKeypair; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.DbConnectionFactory; @@ -166,7 +166,7 @@ public class IntegrationBase { return kvResponse; } - public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerRepository ledgerRepository, + public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerQuery ledgerRepository, KeyPairType keyPairType) { TransactionResponse txResp = keyPairResponse.txResp; HashDigest transactionHash = keyPairResponse.txHash; @@ -191,7 +191,7 @@ public class IntegrationBase { System.out.printf("validKeyPair end %s \r\n", index); } - public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerRepository ledgerRepository, + public static void validKeyPair(IntegrationBase.KeyPairResponse keyPairResponse, LedgerQuery ledgerRepository, KeyPairType keyPairType, CountDownLatch countDownLatch) { TransactionResponse txResp = keyPairResponse.txResp; @@ -215,7 +215,7 @@ public class IntegrationBase { countDownLatch.countDown(); } - public static void validKvWrite(IntegrationBase.KvResponse kvResponse, LedgerRepository ledgerRepository, + public static void validKvWrite(IntegrationBase.KvResponse kvResponse, LedgerQuery ledgerRepository, BlockchainService blockchainService) { // 先验证应答 TransactionResponse txResp = kvResponse.getTxResp(); @@ -240,10 +240,10 @@ public class IntegrationBase { } } - public static LedgerRepository[] buildLedgers(LedgerBindingConfig[] bindingConfigs, + public static LedgerQuery[] buildLedgers(LedgerBindingConfig[] bindingConfigs, DbConnectionFactory[] dbConnectionFactories) { int[] ids = { 0, 1, 2, 3 }; - LedgerRepository[] ledgers = new LedgerRepository[ids.length]; + LedgerQuery[] ledgers = new LedgerQuery[ids.length]; LedgerManager[] ledgerManagers = new LedgerManager[ids.length]; for (int i = 0; i < ids.length; i++) { ledgerManagers[i] = new LedgerManager(); @@ -255,11 +255,11 @@ public class IntegrationBase { return ledgers; } - public static void testConsistencyAmongNodes(LedgerRepository[] ledgers) { - LedgerRepository ledger0 = ledgers[0]; + public static void testConsistencyAmongNodes(LedgerQuery[] ledgers) { + LedgerQuery ledger0 = ledgers[0]; LedgerBlock latestBlock0 = ledger0.retrieveLatestBlock(); for (int i = 1; i < ledgers.length; i++) { - LedgerRepository otherLedger = ledgers[i]; + LedgerQuery otherLedger = ledgers[i]; LedgerBlock otherLatestBlock = otherLedger.retrieveLatestBlock(); assertEquals(ledger0.getHash(), otherLedger.getHash()); assertEquals(ledger0.getLatestBlockHeight(), otherLedger.getLatestBlockHeight()); @@ -447,7 +447,7 @@ public class IntegrationBase { static HashDigest txContentHash; public static LedgerBlock testSDK_Contract(AsymmetricKeypair adminKey, HashDigest ledgerHash, - BlockchainService blockchainService, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, LedgerQuery ledgerRepository) { KeyPairResponse keyPairResponse = testSDK_RegisterDataAccount(adminKey,ledgerHash,blockchainService); System.out.println("adminKey=" + AddressEncoding.generateAddress(adminKey.getPubKey())); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java index 46df4b5a..d28a2b45 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBaseTest.java @@ -19,7 +19,7 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.gateway.GatewayConfigProperties.KeyPairConfig; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitProperties; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.tools.initializer.DBConnectionConfig; import com.jd.blockchain.tools.initializer.LedgerBindingConfig; import com.jd.blockchain.tools.initializer.Prompter; @@ -92,16 +92,16 @@ public class IntegrationBaseTest { public void testConsistencyAmongNodes(IntegratedContext context) { int[] ids = context.getNodeIds(); Node[] nodes = new Node[ids.length]; - LedgerRepository[] ledgers = new LedgerRepository[ids.length]; + LedgerQuery[] ledgers = new LedgerQuery[ids.length]; for (int i = 0; i < nodes.length; i++) { nodes[i] = context.getNode(ids[i]); HashDigest ledgerHash = nodes[i].getLedgerManager().getLedgerHashs()[0]; ledgers[i] = nodes[i].getLedgerManager().getLedger(ledgerHash); } - LedgerRepository ledger0 = ledgers[0]; + LedgerQuery ledger0 = ledgers[0]; LedgerBlock latestBlock0 = ledger0.retrieveLatestBlock(); for (int i = 1; i < ledgers.length; i++) { - LedgerRepository otherLedger = ledgers[i]; + LedgerQuery otherLedger = ledgers[i]; LedgerBlock otherLatestBlock = otherLedger.retrieveLatestBlock(); assertEquals(ledger0.getHash(), otherLedger.getHash()); assertEquals(ledger0.getLatestBlockHeight(), otherLedger.getLatestBlockHeight()); @@ -187,10 +187,10 @@ public class IntegrationBaseTest { assertEquals(ledgerHash0, ledgerHash2); assertEquals(ledgerHash0, ledgerHash3); - LedgerRepository ledger0 = nodeCtx0.registLedger(ledgerHash0); - LedgerRepository ledger1 = nodeCtx1.registLedger(ledgerHash1); - LedgerRepository ledger2 = nodeCtx2.registLedger(ledgerHash2); - LedgerRepository ledger3 = nodeCtx3.registLedger(ledgerHash3); + LedgerQuery ledger0 = nodeCtx0.registLedger(ledgerHash0); + LedgerQuery ledger1 = nodeCtx1.registLedger(ledgerHash1); + LedgerQuery ledger2 = nodeCtx2.registLedger(ledgerHash2); + LedgerQuery ledger3 = nodeCtx3.registLedger(ledgerHash3); assertNotNull(ledger0); assertNotNull(ledger1); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java index cf2774b1..683ed91f 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest2.java @@ -29,7 +29,7 @@ import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.PreparedTransaction; import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; @@ -117,16 +117,16 @@ public class IntegrationTest2 { private void testConsistencyAmongNodes(IntegratedContext context) { int[] ids = context.getNodeIds(); Node[] nodes = new Node[ids.length]; - LedgerRepository[] ledgers = new LedgerRepository[ids.length]; + LedgerQuery[] ledgers = new LedgerQuery[ids.length]; for (int i = 0; i < nodes.length; i++) { nodes[i] = context.getNode(ids[i]); HashDigest ledgerHash = nodes[i].getLedgerManager().getLedgerHashs()[0]; ledgers[i] = nodes[i].getLedgerManager().getLedger(ledgerHash); } - LedgerRepository ledger0 = ledgers[0]; + LedgerQuery ledger0 = ledgers[0]; LedgerBlock latestBlock0 = ledger0.retrieveLatestBlock(); for (int i = 1; i < ledgers.length; i++) { - LedgerRepository otherLedger = ledgers[i]; + LedgerQuery otherLedger = ledgers[i]; LedgerBlock otherLatestBlock = otherLedger.retrieveLatestBlock(); assertEquals(ledger0.getHash(), otherLedger.getHash()); assertEquals(ledger0.getLatestBlockHeight(), otherLedger.getLatestBlockHeight()); @@ -225,10 +225,10 @@ public class IntegrationTest2 { assertEquals(ledgerHash0, ledgerHash2); assertEquals(ledgerHash0, ledgerHash3); - LedgerRepository ledger0 = nodeCtx0.registLedger(ledgerHash0); - LedgerRepository ledger1 = nodeCtx1.registLedger(ledgerHash1); - LedgerRepository ledger2 = nodeCtx2.registLedger(ledgerHash2); - LedgerRepository ledger3 = nodeCtx3.registLedger(ledgerHash3); + LedgerQuery ledger0 = nodeCtx0.registLedger(ledgerHash0); + LedgerQuery ledger1 = nodeCtx1.registLedger(ledgerHash1); + LedgerQuery ledger2 = nodeCtx2.registLedger(ledgerHash2); + LedgerQuery ledger3 = nodeCtx3.registLedger(ledgerHash3); assertNotNull(ledger0); assertNotNull(ledger1); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index 1838495d..c290638d 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -7,7 +7,7 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnectionFactory; @@ -79,7 +79,7 @@ public class IntegrationTest4Bftsmart { gwStarting.waitReturn(); // 执行测试用例之前,校验每个节点的一致性; - LedgerRepository[] ledgers = buildLedgers(new LedgerBindingConfig[]{ + LedgerQuery[] ledgers = buildLedgers(new LedgerBindingConfig[]{ peerNodes[0].getLedgerBindingConfig(), peerNodes[1].getLedgerBindingConfig(), peerNodes[2].getLedgerBindingConfig(), @@ -93,7 +93,7 @@ public class IntegrationTest4Bftsmart { IntegrationBase.testConsistencyAmongNodes(ledgers); - LedgerRepository ledgerRepository = ledgers[0]; + LedgerQuery ledgerRepository = ledgers[0]; GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java index dc2c3ea1..098b7385 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Contract.java @@ -7,7 +7,7 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnectionFactory; @@ -60,7 +60,7 @@ public class IntegrationTest4Contract { gwStarting.waitReturn(); // 执行测试用例之前,校验每个节点的一致性; - LedgerRepository[] ledgers = buildLedgers(new LedgerBindingConfig[]{ + LedgerQuery[] ledgers = buildLedgers(new LedgerBindingConfig[]{ peerNodes[0].getLedgerBindingConfig(), peerNodes[1].getLedgerBindingConfig(), peerNodes[2].getLedgerBindingConfig(), @@ -74,7 +74,7 @@ public class IntegrationTest4Contract { IntegrationBase.testConsistencyAmongNodes(ledgers); - LedgerRepository ledgerRepository = ledgers[0]; + LedgerQuery ledgerRepository = ledgers[0]; GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index f401941c..7fd111bd 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -89,7 +89,7 @@ public class IntegrationTest4MQ { gwStarting.waitReturn(); // 执行测试用例之前,校验每个节点的一致性; - LedgerRepository[] ledgers = buildLedgers(new LedgerBindingConfig[]{ + LedgerQuery[] ledgers = buildLedgers(new LedgerBindingConfig[]{ peerNodes[0].getLedgerBindingConfig(), peerNodes[1].getLedgerBindingConfig(), peerNodes[2].getLedgerBindingConfig(), @@ -103,7 +103,7 @@ public class IntegrationTest4MQ { IntegrationBase.testConsistencyAmongNodes(ledgers); - LedgerRepository ledgerRepository = ledgers[0]; + LedgerQuery ledgerRepository = ledgers[0]; GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java index 43df7bde..51be6d8f 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java @@ -7,7 +7,7 @@ import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerManage; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; @@ -109,7 +109,7 @@ public class IntegrationTestAll4Redis { gwStarting0.waitReturn(); // 执行测试用例之前,校验每个节点的一致性; - LedgerRepository[] ledgers = buildLedgers( + LedgerQuery[] ledgers = buildLedgers( new LedgerBindingConfig[] { bindingConfig0, bindingConfig1, bindingConfig2, bindingConfig3 }, new DbConnectionFactory[] { dbConnectionFactory0, dbConnectionFactory1, dbConnectionFactory2, dbConnectionFactory3 }); @@ -144,10 +144,10 @@ public class IntegrationTestAll4Redis { } } - private LedgerRepository[] buildLedgers(LedgerBindingConfig[] bindingConfigs, + private LedgerQuery[] buildLedgers(LedgerBindingConfig[] bindingConfigs, DbConnectionFactory[] dbConnectionFactories) { int[] ids = { 0, 1, 2, 3 }; - LedgerRepository[] ledgers = new LedgerRepository[ids.length]; + LedgerQuery[] ledgers = new LedgerQuery[ids.length]; LedgerManager[] ledgerManagers = new LedgerManager[ids.length]; for (int i = 0; i < ids.length; i++) { ledgerManagers[i] = new LedgerManager(); @@ -160,11 +160,11 @@ public class IntegrationTestAll4Redis { return ledgers; } - private void testConsistencyAmongNodes(LedgerRepository[] ledgers) { - LedgerRepository ledger0 = ledgers[0]; + private void testConsistencyAmongNodes(LedgerQuery[] ledgers) { + LedgerQuery ledger0 = ledgers[0]; LedgerBlock latestBlock0 = ledger0.retrieveLatestBlock(); for (int i = 1; i < ledgers.length; i++) { - LedgerRepository otherLedger = ledgers[i]; + LedgerQuery otherLedger = ledgers[i]; LedgerBlock otherLatestBlock = otherLedger.retrieveLatestBlock(); assertEquals(ledger0.getHash(), otherLedger.getHash()); assertEquals(ledger0.getLatestBlockHeight(), otherLedger.getLatestBlockHeight()); @@ -183,7 +183,7 @@ public class IntegrationTestAll4Redis { // 测试一个区块包含多个交易的写入情况,并验证写入结果; private void testWriteBatchTransactions(GatewayTestRunner gateway, AsymmetricKeypair adminKey, - LedgerRepository ledgerRepository) { + LedgerQuery ledgerRepository) { // 连接网关; GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); BlockchainService blockchainService = gwsrvFact.getBlockchainService(); @@ -233,7 +233,7 @@ public class IntegrationTestAll4Redis { return; } - private void testSDK(GatewayTestRunner gateway, AsymmetricKeypair adminKey, LedgerRepository ledgerRepository) { + private void testSDK(GatewayTestRunner gateway, AsymmetricKeypair adminKey, LedgerQuery ledgerRepository) { // 连接网关; GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(gateway.getServiceAddress()); BlockchainService bcsrv = gwsrvFact.getBlockchainService(); @@ -248,7 +248,7 @@ public class IntegrationTestAll4Redis { } private void testSDK_InsertData(AsymmetricKeypair adminKey, HashDigest ledgerHash, - BlockchainService blockchainService, Bytes dataAccountAddress, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, Bytes dataAccountAddress, LedgerQuery ledgerRepository) { // 在本地定义注册账号的 TX; TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); @@ -292,7 +292,7 @@ public class IntegrationTestAll4Redis { } private BlockchainKeypair testSDK_RegisterDataAccount(AsymmetricKeypair adminKey, HashDigest ledgerHash, - BlockchainService blockchainService, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, LedgerQuery ledgerRepository) { // 注册数据账户,并验证最终写入; BlockchainKeypair dataAccount = BlockchainKeyGenerator.getInstance().generate(); @@ -327,7 +327,7 @@ public class IntegrationTestAll4Redis { } private BlockchainKeypair testSDK_RegisterUser(AsymmetricKeypair adminKey, HashDigest ledgerHash, - BlockchainService blockchainService, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, LedgerQuery ledgerRepository) { // 注册用户,并验证最终写入; BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); @@ -367,7 +367,7 @@ public class IntegrationTestAll4Redis { } private LedgerBlock testSDK_Contract(AsymmetricKeypair adminKey, HashDigest ledgerHash, - BlockchainService blockchainService, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, LedgerQuery ledgerRepository) { System.out.println("adminKey=" + AddressEncoding.generateAddress(adminKey.getPubKey())); BlockchainKeypair userKey = BlockchainKeyGenerator.getInstance().generate(); System.out.println("userKey=" + userKey.getAddress()); @@ -418,7 +418,7 @@ public class IntegrationTestAll4Redis { } private void testContractExe(AsymmetricKeypair adminKey, HashDigest ledgerHash, BlockchainKeypair userKey, - BlockchainService blockchainService, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, LedgerQuery ledgerRepository) { LedgerInfo ledgerInfo = blockchainService.getLedger(ledgerHash); LedgerBlock previousBlock = blockchainService.getBlock(ledgerHash, ledgerInfo.getLatestBlockHeight() - 1); @@ -463,7 +463,7 @@ public class IntegrationTestAll4Redis { } private void prepareContractData(AsymmetricKeypair adminKey, HashDigest ledgerHash, - BlockchainService blockchainService, LedgerRepository ledgerRepository) { + BlockchainService blockchainService, LedgerQuery ledgerRepository) { // 定义交易; TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java index e11891a9..c6361d89 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestDataAccount.java @@ -31,7 +31,7 @@ import com.jd.blockchain.ledger.TransactionResponse; import com.jd.blockchain.ledger.TransactionTemplate; import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; import com.jd.blockchain.storage.service.DbConnection; @@ -185,7 +185,7 @@ public class IntegrationTestDataAccount { DbConnection memoryBasedDb = context.getNode(0).getStorageDB() .connect(LedgerInitConsensusConfig.memConnectionStrings[0]); - LedgerRepository ledgerRepository = ledgerManager.register(ledgerHashs[0], memoryBasedDb.getStorageService()); + LedgerQuery ledgerRepository = ledgerManager.register(ledgerHashs[0], memoryBasedDb.getStorageService()); DataAccountQuery dataAccountSet = ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()); @@ -227,16 +227,16 @@ public class IntegrationTestDataAccount { public void testConsistencyAmongNodes(IntegratedContext context) { int[] ids = context.getNodeIds(); Node[] nodes = new Node[ids.length]; - LedgerRepository[] ledgers = new LedgerRepository[ids.length]; + LedgerQuery[] ledgers = new LedgerQuery[ids.length]; for (int i = 0; i < nodes.length; i++) { nodes[i] = context.getNode(ids[i]); HashDigest ledgerHash = nodes[i].getLedgerManager().getLedgerHashs()[0]; ledgers[i] = nodes[i].getLedgerManager().getLedger(ledgerHash); } - LedgerRepository ledger0 = ledgers[0]; + LedgerQuery ledger0 = ledgers[0]; LedgerBlock latestBlock0 = ledger0.retrieveLatestBlock(); for (int i = 1; i < ledgers.length; i++) { - LedgerRepository otherLedger = ledgers[i]; + LedgerQuery otherLedger = ledgers[i]; LedgerBlock otherLatestBlock = otherLedger.retrieveLatestBlock(); assertEquals(ledger0.getHash(), otherLedger.getHash()); assertEquals(ledger0.getLatestBlockHeight(), otherLedger.getLatestBlockHeight()); @@ -322,10 +322,10 @@ public class IntegrationTestDataAccount { assertEquals(ledgerHash0, ledgerHash2); assertEquals(ledgerHash0, ledgerHash3); - LedgerRepository ledger0 = nodeCtx0.registLedger(ledgerHash0); - LedgerRepository ledger1 = nodeCtx1.registLedger(ledgerHash1); - LedgerRepository ledger2 = nodeCtx2.registLedger(ledgerHash2); - LedgerRepository ledger3 = nodeCtx3.registLedger(ledgerHash3); + LedgerQuery ledger0 = nodeCtx0.registLedger(ledgerHash0); + LedgerQuery ledger1 = nodeCtx1.registLedger(ledgerHash1); + LedgerQuery ledger2 = nodeCtx2.registLedger(ledgerHash2); + LedgerQuery ledger3 = nodeCtx3.registLedger(ledgerHash3); assertNotNull(ledger0); assertNotNull(ledger1); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java index 4d45e4a0..f7ddad5c 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/batch/bftsmart/BftsmartLedgerInit.java @@ -15,7 +15,7 @@ import com.jd.blockchain.crypto.PrivKey; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.gateway.GatewayConfigProperties; import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.peer.PeerServerBooter; import com.jd.blockchain.sdk.BlockchainService; import com.jd.blockchain.sdk.client.GatewayServiceFactory; @@ -93,7 +93,7 @@ public class BftsmartLedgerInit { localConf4NodesLoad(); PeerTestRunner[] peerNodes = startNodes(4); // 检查账本一致性 - LedgerRepository[] ledgers = checkNodes(peerNodes); + LedgerQuery[] ledgers = checkNodes(peerNodes); txRequestTest(peerNodes, ledgers); } @@ -110,7 +110,7 @@ public class BftsmartLedgerInit { localConf8NodesLoad(); PeerTestRunner[] peerNodes = startNodes(8); // 检查账本一致性 - LedgerRepository[] ledgers = checkNodes(peerNodes); + LedgerQuery[] ledgers = checkNodes(peerNodes); txRequestTest(peerNodes, ledgers); } @@ -127,7 +127,7 @@ public class BftsmartLedgerInit { localConf16NodesLoad(); PeerTestRunner[] peerNodes = startNodes(16); // 检查账本一致性 - LedgerRepository[] ledgers = checkNodes(peerNodes); + LedgerQuery[] ledgers = checkNodes(peerNodes); txRequestTest(peerNodes, ledgers); } @@ -145,7 +145,7 @@ public class BftsmartLedgerInit { // ledgerInitPools.shutdown(); PeerTestRunner[] peerNodes = startNodes(32); // 检查账本一致性 - LedgerRepository[] ledgers = checkNodes(peerNodes); + LedgerQuery[] ledgers = checkNodes(peerNodes); txRequestTest(peerNodes, ledgers); } @@ -162,16 +162,16 @@ public class BftsmartLedgerInit { localConf64NodesLoad(); PeerTestRunner[] peerNodes = startNodes(64); // 检查账本一致性 - LedgerRepository[] ledgers = checkNodes(peerNodes); + LedgerQuery[] ledgers = checkNodes(peerNodes); txRequestTest(peerNodes, ledgers); } - public void txRequestTest(PeerTestRunner[] peerNodes, LedgerRepository[] ledgers) { + public void txRequestTest(PeerTestRunner[] peerNodes, LedgerQuery[] ledgers) { // 测试K-V GatewayTestRunner gateway = initGateWay(peerNodes[0]); - LedgerRepository ledgerRepository = ledgers[0]; + LedgerQuery ledgerRepository = ledgers[0]; HashDigest ledgerHash = ledgerRepository.getHash(); @@ -249,7 +249,7 @@ public class BftsmartLedgerInit { return gateway; } - public LedgerRepository[] checkNodes(PeerTestRunner[] peerNodes) { + public LedgerQuery[] checkNodes(PeerTestRunner[] peerNodes) { int size = peerNodes.length; LedgerBindingConfig[] ledgerBindingConfigs = new LedgerBindingConfig[size]; DbConnectionFactory[] connectionFactories = new DbConnectionFactory[size]; @@ -259,7 +259,7 @@ public class BftsmartLedgerInit { } // 执行测试用例之前,校验每个节点的一致性; - LedgerRepository[] ledgers = buildLedgers(ledgerBindingConfigs, connectionFactories); + LedgerQuery[] ledgers = buildLedgers(ledgerBindingConfigs, connectionFactories); IntegrationBase.testConsistencyAmongNodes(ledgers); return ledgers; } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index e667b51a..cf30df83 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -34,7 +34,7 @@ import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.storage.service.utils.MemoryDBConnFactory; @@ -133,10 +133,10 @@ public class LedgerInitializeTest { assertEquals(ledgerHash0, ledgerHash2); assertEquals(ledgerHash0, ledgerHash3); - LedgerRepository ledger0 = node0.registLedger(ledgerHash0, dbConnections[0]); - LedgerRepository ledger1 = node1.registLedger(ledgerHash1, dbConnections[1]); - LedgerRepository ledger2 = node2.registLedger(ledgerHash2, dbConnections[2]); - LedgerRepository ledger3 = node3.registLedger(ledgerHash3, dbConnections[3]); + LedgerQuery ledger0 = node0.registLedger(ledgerHash0, dbConnections[0]); + LedgerQuery ledger1 = node1.registLedger(ledgerHash1, dbConnections[1]); + LedgerQuery ledger2 = node2.registLedger(ledgerHash2, dbConnections[2]); + LedgerQuery ledger3 = node3.registLedger(ledgerHash3, dbConnections[3]); assertNotNull(ledger0); assertNotNull(ledger1); @@ -277,7 +277,7 @@ public class LedgerInitializeTest { return invoker.start(); } - public LedgerRepository registLedger(HashDigest ledgerHash, String memConn) { + public LedgerQuery registLedger(HashDigest ledgerHash, String memConn) { return ledgerManager.register(ledgerHash, storageDb.connect(memConn).getStorageService()); } } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index 6909c155..3c95c8e6 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -124,10 +124,10 @@ public class LedgerInitializeWeb4Nodes { assertEquals(ledgerHash0, ledgerHash2); assertEquals(ledgerHash0, ledgerHash3); - LedgerRepository ledger0 = node0.registLedger(ledgerHash0); - LedgerRepository ledger1 = node1.registLedger(ledgerHash1); - LedgerRepository ledger2 = node2.registLedger(ledgerHash2); - LedgerRepository ledger3 = node3.registLedger(ledgerHash3); + LedgerQuery ledger0 = node0.registLedger(ledgerHash0); + LedgerQuery ledger1 = node1.registLedger(ledgerHash1); + LedgerQuery ledger2 = node2.registLedger(ledgerHash2); + LedgerQuery ledger3 = node3.registLedger(ledgerHash3); assertNotNull(ledger0); assertNotNull(ledger1); @@ -225,9 +225,9 @@ public class LedgerInitializeWeb4Nodes { this.serverAddress = serverAddress; } - public LedgerRepository registLedger(HashDigest ledgerHash) { + public LedgerQuery registLedger(HashDigest ledgerHash) { DbConnection conn = db.connect(dbConnConfig.getUri()); - LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); + LedgerQuery ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); return ledgerRepo; } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java index f627a8d2..e478c70f 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4SingleStepsTest.java @@ -30,7 +30,7 @@ import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; import com.jd.blockchain.tools.initializer.DBConnectionConfig; @@ -326,7 +326,7 @@ public class LedgerInitializeWeb4SingleStepsTest { this.serverAddress = serverAddress; } - public LedgerRepository registLedger(HashDigest ledgerHash) { + public LedgerQuery registLedger(HashDigest ledgerHash) { // LedgerManage ledgerManager = ctx.getBean(LedgerManage.class); // // DbConnectionFactory dbConnFactory = ctx.getBean(DbConnectionFactory.class); @@ -336,7 +336,7 @@ public class LedgerInitializeWeb4SingleStepsTest { // DbConnection conn = db.connect(dbConnConfig.getUri(), // dbConnConfig.getPassword()); DbConnection conn = db.connect(dbConnConfig.getUri()); - LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); + LedgerQuery ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); return ledgerRepo; } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index 0e1b2755..5cea4d68 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -83,8 +83,7 @@ public class LedgerBlockGeneratingTest { LedgerEditor newEditor = ledger.createNextBlock(); TransactionBatchProcessor txProc = new TransactionBatchProcessor( - LedgerPerformanceTest.DEFAULT_SECURITY_MANAGER, newEditor, previousDataSet, opHandler, - ledgerManager); + LedgerPerformanceTest.DEFAULT_SECURITY_MANAGER, newEditor, ledger, opHandler); testTxExec(txList, i * batchSize, batchSize, txProc); diff --git a/source/test/test-ledger-core/pom.xml b/source/test/test-ledger/pom.xml similarity index 83% rename from source/test/test-ledger-core/pom.xml rename to source/test/test-ledger/pom.xml index 4d831fdc..3e32fb6d 100644 --- a/source/test/test-ledger-core/pom.xml +++ b/source/test/test-ledger/pom.xml @@ -7,9 +7,14 @@ test 1.1.0-SNAPSHOT - test-ledger-core + test-ledger + + com.jd.blockchain + tools-initializer + ${project.version} + com.jd.blockchain ledger-core diff --git a/source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java b/source/test/test-ledger/src/main/java/test/perf/com/jd/blockchain/ledger/MerkleDatasetPerformanceTester.java similarity index 99% rename from source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java rename to source/test/test-ledger/src/main/java/test/perf/com/jd/blockchain/ledger/MerkleDatasetPerformanceTester.java index bd614126..7b5faffb 100644 --- a/source/test/test-ledger-core/src/main/java/test/perf/com/jd/blockchain/ledger/core/MerkleDatasetPerformanceTester.java +++ b/source/test/test-ledger/src/main/java/test/perf/com/jd/blockchain/ledger/MerkleDatasetPerformanceTester.java @@ -1,4 +1,4 @@ -package test.perf.com.jd.blockchain.ledger.core; +package test.perf.com.jd.blockchain.ledger; import java.io.IOException; import java.util.Random; diff --git a/source/test/test-ledger-core/src/main/resources/MerkleDataset_Performance_Result_20180922.txt b/source/test/test-ledger/src/main/resources/MerkleDataset_Performance_Result_20180922.txt similarity index 100% rename from source/test/test-ledger-core/src/main/resources/MerkleDataset_Performance_Result_20180922.txt rename to source/test/test-ledger/src/main/resources/MerkleDataset_Performance_Result_20180922.txt diff --git a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java new file mode 100644 index 00000000..2cd06f96 --- /dev/null +++ b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java @@ -0,0 +1,267 @@ +package test.com.jd.blockchain.test.ledger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.KeyGenUtils; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.DataAccountRegisterOperation; +import com.jd.blockchain.ledger.DigitalSignature; +import com.jd.blockchain.ledger.LedgerBlock; +import com.jd.blockchain.ledger.LedgerInitOperation; +import com.jd.blockchain.ledger.LedgerInitProperties; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.RolesConfigureOperation; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionBuilder; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.TransactionRequest; +import com.jd.blockchain.ledger.TransactionRequestBuilder; +import com.jd.blockchain.ledger.UserAuthorizeOperation; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; +import com.jd.blockchain.ledger.core.LedgerInitializer; +import com.jd.blockchain.ledger.core.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerQuery; +import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.OperationHandleRegisteration; +import com.jd.blockchain.ledger.core.TransactionBatchProcessor; +import com.jd.blockchain.service.TransactionBatchResult; +import com.jd.blockchain.service.TransactionBatchResultHandle; +import com.jd.blockchain.storage.service.KVStorageService; +import com.jd.blockchain.storage.service.utils.MemoryKVStorage; +import com.jd.blockchain.tools.initializer.web.LedgerInitConfiguration; +import com.jd.blockchain.transaction.TxBuilder; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.io.RuntimeIOException; + +public class RolesAuthorizationTest { + + private static final OperationHandleRegisteration HANDLE_REG = new DefaultOperationHandleRegisteration(); + + public static final String PASSWORD = "abc"; + + public static final String[] PUB_KEYS = { "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", + "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", + "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", + "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk" }; + + public static final String[] PRIV_KEYS = { + "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", + "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", + "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", + "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns" }; + + public static final BlockchainKeypair[] KEYS; + + private static final BlockchainKeypair ADMIN_USER; + private static final BlockchainKeypair MANAGER_USER; + private static final BlockchainKeypair DEFAULT_USER; + private static final BlockchainKeypair GUEST_USER; + + // 预置的数据账户; + private static final BlockchainIdentity DATA_ACCOUNT_ID = BlockchainKeyGenerator.getInstance().generate() + .getIdentity(); + + static { + KEYS = new BlockchainKeypair[PRIV_KEYS.length]; + for (int i = 0; i < PRIV_KEYS.length; i++) { + PrivKey privKey = KeyGenUtils.decodePrivKeyWithRawPassword(PRIV_KEYS[i], PASSWORD); + PubKey pubKey = KeyGenUtils.decodePubKey(PUB_KEYS[i]); + KEYS[i] = new BlockchainKeypair(pubKey, privKey); + } + ADMIN_USER = KEYS[0]; + MANAGER_USER = KEYS[1]; + DEFAULT_USER = KEYS[2]; + GUEST_USER = KEYS[3]; + + // ---------- + DataContractRegistry.register(LedgerInitOperation.class); + DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(RolesConfigureOperation.class); + DataContractRegistry.register(UserAuthorizeOperation.class); + } + + @Test + public void test() { + MemoryKVStorage storage = new MemoryKVStorage(); + LedgerBlock genesisBlock = initLedger(storage); + + LedgerManager ledgerManager = new LedgerManager(); + LedgerRepository ledger = ledgerManager.register(genesisBlock.getHash(), storage); + + // 验证角色和用户的权限配置; + assertUserRolesPermissions(ledger); + + // 预置数据; + TransactionRequest tx = buildRequest(ledger.getHash(), ADMIN_USER, ADMIN_USER, new TransactionDefiner() { + @Override + public void define(TransactionBuilder txBuilder) { + txBuilder.dataAccounts().register(DATA_ACCOUNT_ID); + } + }); + + TransactionBatchResult procResult = executeTransactions(ledger, tx); + assertEquals(1, procResult.getBlock().getHeight()); + + } + + private TransactionBatchResult executeTransactions(LedgerRepository ledger, TransactionRequest... transactions) { + TransactionBatchProcessor txProcessor = new TransactionBatchProcessor(ledger, HANDLE_REG); + + for (TransactionRequest request : transactions) { + txProcessor.schedule(request); + } + + TransactionBatchResultHandle procResult = txProcessor.prepare(); + procResult.commit(); + + return procResult; + } + + private TransactionRequest buildRequest(HashDigest ledgerHash, BlockchainKeypair endpoint, BlockchainKeypair node, + TransactionDefiner definer) { + TransactionBuilder txBuilder = new TxBuilder(ledgerHash); + definer.define(txBuilder); + TransactionRequestBuilder reqBuilder = txBuilder.prepareRequest(); + reqBuilder.signAsEndpoint(endpoint); + if (node != null) { + reqBuilder.signAsNode(node); + } + return reqBuilder.buildRequest(); + } + + private void assertUserRolesPermissions(LedgerQuery ledger) { + // 验证角色-权限; + assertRolePermissions(ledger, "DEFAULT", + new LedgerPermission[] { LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT }, + new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }); + + assertRolePermissions(ledger, "ADMIN", + new LedgerPermission[] { LedgerPermission.CONFIGURE_ROLES, LedgerPermission.AUTHORIZE_USER_ROLES, + LedgerPermission.SET_CONSENSUS, LedgerPermission.SET_CRYPTO, + LedgerPermission.REGISTER_PARTICIPANT, LedgerPermission.REGISTER_USER }, + new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION }); + + assertRolePermissions(ledger, "MANAGER", + new LedgerPermission[] { LedgerPermission.CONFIGURE_ROLES, LedgerPermission.AUTHORIZE_USER_ROLES, + LedgerPermission.REGISTER_USER, LedgerPermission.REGISTER_DATA_ACCOUNT, + LedgerPermission.REGISTER_CONTRACT, LedgerPermission.UPGRADE_CONTRACT, + LedgerPermission.SET_USER_ATTRIBUTES, LedgerPermission.WRITE_DATA_ACCOUNT, + LedgerPermission.APPROVE_TX }, + new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, + TransactionPermission.CONTRACT_OPERATION }); + + assertRolePermissions(ledger, "GUEST", new LedgerPermission[] {}, + new TransactionPermission[] { TransactionPermission.CONTRACT_OPERATION }); + + // 验证用户-角色; + assertUserRoles(ledger, ADMIN_USER, RolesPolicy.UNION, "ADMIN", "MANAGER"); + assertUserRoles(ledger, MANAGER_USER, RolesPolicy.UNION, "MANAGER"); + assertUserRoles(ledger, DEFAULT_USER, RolesPolicy.UNION); + assertUserRoles(ledger, DEFAULT_USER, RolesPolicy.UNION); + } + + private void assertUserRoles(LedgerQuery ledger, BlockchainKeypair userKey, RolesPolicy policy, String... roles) { + assertUserRoles(ledger, userKey.getAddress(), policy, roles); + } + + private void assertUserRoles(LedgerQuery ledger, Bytes address, RolesPolicy policy, String[] roles) { + if (roles == null) { + roles = new String[0]; + } + UserRoles userRoles = ledger.getAdminInfo().getUserRoles().getUserRoles(address); + assertNotNull(userRoles); + assertEquals(policy, userRoles.getPolicy()); + + Set expectedRoles = new HashSet(Arrays.asList(roles)); + Set actualRoles = userRoles.getRoleSet(); + assertEquals(expectedRoles.size(), actualRoles.size()); + for (String r : actualRoles) { + assertTrue(expectedRoles.contains(r)); + } + } + + private void assertRolePermissions(LedgerQuery ledger, String roleName, LedgerPermission[] ledgerPermissions, + TransactionPermission[] txPermissions) { + RolePrivilegeSettings roles = ledger.getAdminInfo().getRolePrivileges(); + assertTrue(roles.contains(roleName)); + RolePrivileges privileges = roles.getRolePrivilege(roleName); + assertEquals(ledgerPermissions.length, privileges.getLedgerPrivilege().getPermissionCount()); + assertEquals(txPermissions.length, privileges.getTransactionPrivilege().getPermissionCount()); + + Set expectedLedgerPermissions = new HashSet( + Arrays.asList(ledgerPermissions)); + for (LedgerPermission p : LedgerPermission.values()) { + if (expectedLedgerPermissions.contains(p)) { + assertTrue(privileges.getLedgerPrivilege().isEnable(p)); + } else { + assertFalse(privileges.getLedgerPrivilege().isEnable(p)); + } + } + + Set expectedTxPermissions = new HashSet( + Arrays.asList(txPermissions)); + for (TransactionPermission p : TransactionPermission.values()) { + if (expectedTxPermissions.contains(p)) { + assertTrue(privileges.getTransactionPrivilege().isEnable(p)); + } else { + assertFalse(privileges.getTransactionPrivilege().isEnable(p)); + } + } + } + + private LedgerBlock initLedger(KVStorageService storage) { + LedgerInitProperties initProps = loadInitProperties(); + LedgerInitConfiguration initConfig = LedgerInitConfiguration.create(initProps); + LedgerInitializer initializer = LedgerInitializer.create(initConfig.getLedgerSettings(), + initConfig.getSecuritySettings()); + + DigitalSignature sign0 = initializer.signTransaction(KEYS[0]); + DigitalSignature sign1 = initializer.signTransaction(KEYS[1]); + DigitalSignature sign2 = initializer.signTransaction(KEYS[2]); + DigitalSignature sign3 = initializer.signTransaction(KEYS[3]); + + LedgerBlock genesisBlock = initializer.prepareLedger(storage, sign0, sign1, sign2, sign3); + initializer.commit(); + return genesisBlock; + } + + private LedgerInitProperties loadInitProperties() { + try { + ClassPathResource ledgerInitSettingResource = new ClassPathResource("ledger.init"); + InputStream in = ledgerInitSettingResource.getInputStream(); + return LedgerInitProperties.resolve(in); + } catch (IOException e) { + throw new RuntimeIOException(e.getMessage(), e); + } + } + + private static interface TransactionDefiner { + + void define(TransactionBuilder txBuilder); + + } +} diff --git a/source/test/test-ledger/src/test/resources/bftsmart.config b/source/test/test-ledger/src/test/resources/bftsmart.config new file mode 100644 index 00000000..df69caf5 --- /dev/null +++ b/source/test/test-ledger/src/test/resources/bftsmart.config @@ -0,0 +1,144 @@ +# Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags +# +# Licensed 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. + +############################################ +####### Communication Configurations ####### +############################################ + +#HMAC algorithm used to authenticate messages between processes (HmacMD5 is the default value) +#This parameter is not currently being used being used +#system.authentication.hmacAlgorithm = HmacSHA1 + +#Specify if the communication system should use a thread to send data (true or false) +system.communication.useSenderThread = true + +#Force all processes to use the same public/private keys pair and secret key. This is useful when deploying experiments +#and benchmarks, but must not be used in production systems. +system.communication.defaultkeys = true + +############################################ +### Replication Algorithm Configurations ### +############################################ + +#Timeout to asking for a client request +system.totalordermulticast.timeout = 2000 + + +#Maximum batch size (in number of messages) +system.totalordermulticast.maxbatchsize = 400 + +#Number of nonces (for non-determinism actions) generated +system.totalordermulticast.nonces = 10 + +#if verification of leader-generated timestamps are increasing +#it can only be used on systems in which the network clocks +#are synchronized +system.totalordermulticast.verifyTimestamps = false + +#Quantity of messages that can be stored in the receive queue of the communication system +system.communication.inQueueSize = 500000 + +# Quantity of messages that can be stored in the send queue of each replica +system.communication.outQueueSize = 500000 + +#Set to 1 if SMaRt should use signatures, set to 0 if otherwise +system.communication.useSignatures = 0 + +#Set to 1 if SMaRt should use MAC's, set to 0 if otherwise +system.communication.useMACs = 1 + +#Set to 1 if SMaRt should use the standard output to display debug messages, set to 0 if otherwise +system.debug = 0 + +#Print information about the replica when it is shutdown +system.shutdownhook = true + +############################################ +###### State Transfer Configurations ####### +############################################ + +#Activate the state transfer protocol ('true' to activate, 'false' to de-activate) +system.totalordermulticast.state_transfer = true + +#Maximum ahead-of-time message not discarded +system.totalordermulticast.highMark = 10000 + +#Maximum ahead-of-time message not discarded when the replica is still on EID 0 (after which the state transfer is triggered) +system.totalordermulticast.revival_highMark = 10 + +#Number of ahead-of-time messages necessary to trigger the state transfer after a request timeout occurs +system.totalordermulticast.timeout_highMark = 200 + +############################################ +###### Log and Checkpoint Configurations ### +############################################ + +system.totalordermulticast.log = true +system.totalordermulticast.log_parallel = false +system.totalordermulticast.log_to_disk = false +system.totalordermulticast.sync_log = false + +#Period at which BFT-SMaRt requests the state to the application (for the state transfer state protocol) +system.totalordermulticast.checkpoint_period = 1000 +system.totalordermulticast.global_checkpoint_period = 120000 + +system.totalordermulticast.checkpoint_to_disk = false +system.totalordermulticast.sync_ckp = false + + +############################################ +###### Reconfiguration Configurations ###### +############################################ + +#The ID of the trust third party (TTP) +system.ttp.id = 7002 + +#This sets if the system will function in Byzantine or crash-only mode. Set to "true" to support Byzantine faults +system.bft = true + +#Custom View Storage; +#view.storage.handler=bftsmart.reconfiguration.views.DefaultViewStorage + + +#Number of servers in the group +system.servers.num = 4 + +#Maximum number of faulty replicas +system.servers.f = 1 + +#Replicas ID for the initial view, separated by a comma. +# The number of replicas in this parameter should be equal to that specified in 'system.servers.num' +system.initial.view = 0,1,2,3 + +#Configuration of all node servers; +#PubKey of node server with specified ID, with base58 encoding. +system.server.0.pubkey= +system.server.0.network.host=127.0.0.1 +system.server.0.network.port=8900 +system.server.0.network.secure=false + +system.server.1.pubkey= +system.server.1.network.host=127.0.0.1 +system.server.1.network.port=8910 +system.server.1.network.secure=false + +system.server.2.pubkey= +system.server.2.network.host=127.0.0.1 +system.server.2.network.port=8920 +system.server.2.network.secure=false + +system.server.3.pubkey= +system.server.3.network.host=127.0.0.1 +system.server.3.network.port=8920 +system.server.3.network.secure=false diff --git a/source/test/test-ledger/src/test/resources/keys/parti2.pub b/source/test/test-ledger/src/test/resources/keys/parti2.pub new file mode 100644 index 00000000..dde44b8e --- /dev/null +++ b/source/test/test-ledger/src/test/resources/keys/parti2.pub @@ -0,0 +1 @@ +3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x \ No newline at end of file diff --git a/source/test/test-ledger/src/test/resources/ledger.init b/source/test/test-ledger/src/test/resources/ledger.init new file mode 100644 index 00000000..a503c96a --- /dev/null +++ b/source/test/test-ledger/src/test/resources/ledger.init @@ -0,0 +1,165 @@ + +#账本的种子;一段16进制字符,最长可以包含64个字符;可以用字符“-”分隔,以便更容易读取; +ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe + +#账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; +ledger.name=test + +#声明账本的创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 +created-time=2019-08-01 14:26:58.069+0800 + + +#----------------------------------------------- +# 初始的角色名称列表;可选项; +# 角色名称不区分大小写,最长不超过20个字符;多个角色名称之间用半角的逗点“,”分隔; +# 系统会预置一个默认角色“DEFAULT”,所有未指定角色的用户都以赋予该角色的权限;若初始化时未配置默认角色的权限,则为默认角色分配所有权限; +# +# 注:如果声明了角色,但未声明角色对应的权限清单,这会忽略该角色的初始化; +# +security.roles=DEFAULT, ADMIN, MANAGER, GUEST + +# 赋予角色的账本权限清单;可选项; +# 可选的权限如下; +# AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, +# REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, +# SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +# APPROVE_TX, CONSENSUS_TX +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT + +# 赋予角色的交易权限清单;可选项; +# 可选的权限如下; +# DIRECT_OPERATION, CONTRACT_OPERATION +# 多项权限之间用逗点“,”分隔; +# +security.role.DEFAULT.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 其它角色的配置示例; +# 系统管理员角色:只能操作全局性的参数配置和用户注册,只能执行直接操作指令; +security.role.ADMIN.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER +security.role.ADMIN.tx-privileges=DIRECT_OPERATION + +# 业务主管角色:只能够执行账本数据相关的操作,包括注册用户、注册数据账户、注册合约、升级合约、写入数据等;能够执行直接操作指令和调用合约; +security.role.MANAGER.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, REGISTER_USER, REGISTER_DATA_ACCOUNT, \ +REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, APPROVE_TX +security.role.MANAGER.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 访客角色:不具备任何的账本权限,只有数据读取的操作;也只能够通过调用合约来读取数据; +security.role.GUEST.ledger-privileges= +security.role.GUEST.tx-privileges=CONTRACT_OPERATION + + + +#----------------------------------------------- +#共识服务提供者;必须; +consensus.service-provider=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider + +#共识服务的参数配置;必须; +consensus.conf=classpath:bftsmart.config + +#密码服务提供者列表,以英文逗点“,”分隔;必须; +crypto.service-providers=com.jd.blockchain.crypto.service.classic.ClassicCryptoService, \ +com.jd.blockchain.crypto.service.sm.SMCryptoService + +#从存储中加载账本数据时,是否校验哈希;可选; +crypto.verify-hash=true + +#哈希算法; +crypto.hash-algorithm=SHA256 + + +#参与方的个数,后续以 cons_parti.id 分别标识每一个参与方的配置; +cons_parti.count=4 + +#第0个参与方的名称; +cons_parti.0.name=jd.com +#第0个参与方的公钥文件路径; +cons_parti.0.pubkey-path=keys/jd-com.pub +#第0个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.0.pubkey=3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9 +#第0个参与方的角色清单;可选项; +cons_parti.0.roles=ADMIN, MANAGER +#第0个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.0.roles-policy=UNION +#第0个参与方的共识服务的主机地址; +cons_parti.0.consensus.host=127.0.0.1 +#第0个参与方的共识服务的端口; +cons_parti.0.consensus.port=8900 +#第0个参与方的共识服务是否开启安全连接; +cons_parti.0.consensus.secure=true +#第0个参与方的账本初始服务的主机; +cons_parti.0.initializer.host=127.0.0.1 +#第0个参与方的账本初始服务的端口; +cons_parti.0.initializer.port=8800 +#第0个参与方的账本初始服务是否开启安全连接; +cons_parti.0.initializer.secure=true + +#第1个参与方的名称; +cons_parti.1.name=at.com +#第1个参与方的公钥文件路径; +cons_parti.1.pubkey-path=keys/at-com.pub +#第1个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.1.pubkey=3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX +#第1个参与方的角色清单;可选项; +cons_parti.1.roles=MANAGER +#第1个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.1.roles-policy=UNION +#第1个参与方的共识服务的主机地址; +cons_parti.1.consensus.host=127.0.0.1 +#第1个参与方的共识服务的端口; +cons_parti.1.consensus.port=8910 +#第1个参与方的共识服务是否开启安全连接; +cons_parti.1.consensus.secure=false +#第1个参与方的账本初始服务的主机; +cons_parti.1.initializer.host=127.0.0.1 +#第1个参与方的账本初始服务的端口; +cons_parti.1.initializer.port=8810 +#第1个参与方的账本初始服务是否开启安全连接; +cons_parti.1.initializer.secure=false + +#第2个参与方的名称; +cons_parti.2.name=bt.com +#第2个参与方的公钥文件路径; +cons_parti.2.pubkey-path=classpath:keys/parti2.pub +#第2个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.2.pubkey= +#第2个参与方的角色清单;可选项; +cons_parti.2.roles= +#第2个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.2.roles-policy= +#第2个参与方的共识服务的主机地址; +cons_parti.2.consensus.host=127.0.0.1 +#第2个参与方的共识服务的端口; +cons_parti.2.consensus.port=8920 +#第2个参与方的共识服务是否开启安全连接; +cons_parti.2.consensus.secure=false +#第2个参与方的账本初始服务的主机; +cons_parti.2.initializer.host=127.0.0.1 +#第2个参与方的账本初始服务的端口; +cons_parti.2.initializer.port=8820 +#第2个参与方的账本初始服务是否开启安全连接; +cons_parti.2.initializer.secure=true + +#第3个参与方的名称; +cons_parti.3.name=xt.com +#第3个参与方的公钥文件路径; +cons_parti.3.pubkey-path=keys/xt-com.pub +#第3个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; +cons_parti.3.pubkey=3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk +#第3个参与方的角色清单;可选项; +cons_parti.3.roles=GUEST +#第3个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +cons_parti.3.roles-policy=INTERSECT +#第3个参与方的共识服务的主机地址; +cons_parti.3.consensus.host=127.0.0.1 +#第3个参与方的共识服务的端口; +cons_parti.3.consensus.port=8930 +#第3个参与方的共识服务是否开启安全连接; +cons_parti.3.consensus.secure=false +#第3个参与方的账本初始服务的主机; +cons_parti.3.initializer.host=127.0.0.1 +#第3个参与方的账本初始服务的端口; +cons_parti.3.initializer.port=8830 +#第3个参与方的账本初始服务是否开启安全连接; +cons_parti.3.initializer.secure=false diff --git a/source/test/test-ledger/src/test/resources/logback-test.xml b/source/test/test-ledger/src/test/resources/logback-test.xml new file mode 100644 index 00000000..29013782 --- /dev/null +++ b/source/test/test-ledger/src/test/resources/logback-test.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java index 6dc9b98b..d453cc26 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitConfiguration.java @@ -21,7 +21,6 @@ import com.jd.blockchain.ledger.LedgerInitProperties; import com.jd.blockchain.ledger.LedgerInitProperties.CryptoProperties; import com.jd.blockchain.ledger.LedgerInitProperties.ParticipantProperties; import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.SecurityInitData; import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.core.CryptoConfig; @@ -184,6 +183,8 @@ public class LedgerInitConfiguration { partiProps.getId(), partiProps.getName())); } } + //去掉对默认角色的授权; + securityInitData.addUserAuthencation(partiProps.getAddress(), roles, partiProps.getRolesPolicy()); } diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 367a08c7..4d259523 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -194,7 +194,7 @@ public class MockerNodeContext implements BlockchainQueryService { ledgerRepository = registerLedger(ledgerHash, dbConnectionConfig); - queryService = new LedgerQueryService(ledgerManager); + queryService = new LedgerQueryService(ledgerRepository); contractExeHandle.initLedger(ledgerManager, ledgerHash); @@ -470,7 +470,7 @@ public class MockerNodeContext implements BlockchainQueryService { LedgerBlock latestBlock = ledgerRepository.getLatestBlock(); LedgerDataQuery previousDataSet = ledgerRepository.getDataSet(latestBlock); TransactionBatchProcessor txProc = new TransactionBatchProcessor(getSecurityManager(), newEditor, - previousDataSet, opHandler, ledgerManager); + ledgerRepository, opHandler); TransactionResponse txResp = txProc.schedule(txRequest); TransactionBatchResultHandle handle = txProc.prepare(); handle.commit(); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java index d278de13..dee8fe18 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/handler/MockerContractExeHandle.java @@ -16,11 +16,10 @@ import com.jd.blockchain.ledger.BytesValueList; import com.jd.blockchain.ledger.ContractEventSendOperation; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.TransactionRequest; -import com.jd.blockchain.ledger.core.LedgerDataQuery; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerManager; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.LedgerQueryService; -import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.OperationHandle; import com.jd.blockchain.ledger.core.OperationHandleContext; import com.jd.blockchain.ledger.core.TransactionRequestExtension; @@ -37,7 +36,7 @@ public class MockerContractExeHandle implements OperationHandle { @Override public BytesValue process(Operation op, LedgerDataset dataset, TransactionRequestExtension request, - LedgerDataQuery previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { + LedgerQuery ledger, OperationHandleContext opHandleContext) { ContractEventSendOperation contractOP = (ContractEventSendOperation) op; HashDigest txHash = request.getTransactionContent().getHash(); @@ -46,7 +45,7 @@ public class MockerContractExeHandle implements OperationHandle { Object result = null; if (executorProxy != null) { - LedgerQueryService queryService = new LedgerQueryService(ledgerManager); + LedgerQueryService queryService = new LedgerQueryService(ledger); ContractLedgerContext ledgerContext = new ContractLedgerContext(queryService, opHandleContext); MockerContractEventContext contractEventContext = new MockerContractEventContext(ledgerHash, diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java index 30cb4866..24a6ee53 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/node/NodeWebContext.java @@ -7,7 +7,7 @@ import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.core.LedgerInitDecision; import com.jd.blockchain.ledger.core.LedgerInitProposal; import com.jd.blockchain.ledger.core.LedgerManager; -import com.jd.blockchain.ledger.core.LedgerRepository; +import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.mocker.config.LedgerInitWebConfiguration; import com.jd.blockchain.storage.service.DbConnection; import com.jd.blockchain.storage.service.impl.composite.CompositeConnectionFactory; @@ -61,9 +61,9 @@ public class NodeWebContext { this.serverAddress = serverAddress; } - public LedgerRepository registLedger(HashDigest ledgerHash) { + public LedgerQuery registLedger(HashDigest ledgerHash) { DbConnection conn = db.connect(dbConnConfig.getUri()); - LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); + LedgerQuery ledgerRepo = ledgerManager.register(ledgerHash, conn.getStorageService()); return ledgerRepo; } diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/Int8Code.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/Int8Code.java new file mode 100644 index 00000000..339c9c07 --- /dev/null +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/Int8Code.java @@ -0,0 +1,8 @@ +package com.jd.blockchain.utils; + +public interface Int8Code { + + byte getCode(); + + +} From 9e0a07df22d3c018c21221ab5288655979e2e127 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 11 Sep 2019 09:03:30 +0800 Subject: [PATCH 096/124] =?UTF-8?q?=E5=B0=86=E6=80=A7=E8=83=BD=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/tools/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tools/pom.xml b/source/tools/pom.xml index 72142be9..ce56cf0f 100644 --- a/source/tools/pom.xml +++ b/source/tools/pom.xml @@ -14,7 +14,7 @@ tools-keygen-booter tools-initializer tools-initializer-booter - tools-capability + From 24f2f74cc221b708f7e538a029f57802da73c4bb Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 11 Sep 2019 10:08:18 +0800 Subject: [PATCH 097/124] Fixed compilation errors; --- .../DefaultOperationHandleRegisteration.java | 11 ++++++++- .../ParticipantRegisterOperationHandle.java | 23 ++++++++++++++---- ...ParticipantStateUpdateOperationHandle.java | 24 +++++++++++++++---- .../ledger/core/LedgerAdminDatasetTest.java | 2 +- .../ledger/core/LedgerInitOperationTest.java | 4 ++-- .../core/LedgerInitSettingSerializeTest.java | 4 ++-- .../ledger/core/LedgerManagerTest.java | 8 +++---- .../ledger/core/LedgerMetaDataTest.java | 2 +- .../ledger/core/LedgerTestUtils.java | 2 +- .../ledger/LedgerInitProperties.java | 2 +- .../ledger/ParticipantNodeState.java | 6 ++--- ...ateWay_Participant_State_Update_Test_.java | 2 +- .../com/jd/blockchain/intgr/perf/Utils.java | 2 +- .../jd/blockchain/intgr/IntegrationBase.java | 2 +- .../blockchain/mocker/MockerNodeContext.java | 3 ++- 15 files changed, 67 insertions(+), 30 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java index 3c001e3f..ae8ad40b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DefaultOperationHandleRegisteration.java @@ -6,11 +6,20 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.jd.blockchain.ledger.core.handles.*; import org.springframework.stereotype.Component; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.Operation; +import com.jd.blockchain.ledger.core.handles.ContractCodeDeployOperationHandle; +import com.jd.blockchain.ledger.core.handles.DataAccountKVSetOperationHandle; +import com.jd.blockchain.ledger.core.handles.DataAccountRegisterOperationHandle; +import com.jd.blockchain.ledger.core.handles.JVMContractEventSendOperationHandle; +import com.jd.blockchain.ledger.core.handles.LedgerInitOperationHandle; +import com.jd.blockchain.ledger.core.handles.ParticipantRegisterOperationHandle; +import com.jd.blockchain.ledger.core.handles.ParticipantStateUpdateOperationHandle; +import com.jd.blockchain.ledger.core.handles.RolesConfigureOperationHandle; +import com.jd.blockchain.ledger.core.handles.UserAuthorizeOperationHandle; +import com.jd.blockchain.ledger.core.handles.UserRegisterOperationHandle; @Component public class DefaultOperationHandleRegisteration implements OperationHandleRegisteration { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java index 6f783680..e3e8f6e1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java @@ -2,8 +2,21 @@ package com.jd.blockchain.ledger.core.handles; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.ParticipantInfo; +import com.jd.blockchain.ledger.ParticipantInfoData; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; +import com.jd.blockchain.ledger.ParticipantRegisterOperation; +import com.jd.blockchain.ledger.UserRegisterOperation; +import com.jd.blockchain.ledger.core.LedgerAdminDataset; +import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerQuery; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; +import com.jd.blockchain.ledger.core.OperationHandleContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; import com.jd.blockchain.transaction.UserRegisterOpTemplate; import com.jd.blockchain.utils.Bytes; @@ -15,8 +28,8 @@ public class ParticipantRegisterOperationHandle extends AbstractLedgerOperationH @Override protected void doProcess(ParticipantRegisterOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension requestContext, LedgerQuery previousBlockDataset, + OperationHandleContext handleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); @@ -28,7 +41,7 @@ public class ParticipantRegisterOperationHandle extends AbstractLedgerOperationH ParticipantInfo participantInfo = new ParticipantInfoData(participantRegOp.getParticipantName(), participantRegOp.getParticipantIdentity().getPubKey(), participantRegOp.getNetworkAddress()); - ParticipantNode participantNode = new PartNode((int)(adminAccountDataSet.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTED); + ParticipantNode participantNode = new PartNode((int)(adminAccountDataSet.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTERED); //add new participant as consensus node adminAccountDataSet.addParticipant(participantNode); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java index 8969cfea..799ac36c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java @@ -4,8 +4,22 @@ import com.jd.blockchain.consensus.ConsensusProvider; import com.jd.blockchain.consensus.ConsensusProviders; import com.jd.blockchain.crypto.AddressEncoding; import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.*; -import com.jd.blockchain.ledger.core.*; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSettings; +import com.jd.blockchain.ledger.ParticipantInfo; +import com.jd.blockchain.ledger.ParticipantInfoData; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; +import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; +import com.jd.blockchain.ledger.core.LedgerAdminDataset; +import com.jd.blockchain.ledger.core.LedgerConfiguration; +import com.jd.blockchain.ledger.core.LedgerDataset; +import com.jd.blockchain.ledger.core.LedgerQuery; +import com.jd.blockchain.ledger.core.MultiIDsPolicy; +import com.jd.blockchain.ledger.core.OperationHandleContext; +import com.jd.blockchain.ledger.core.SecurityContext; +import com.jd.blockchain.ledger.core.SecurityPolicy; +import com.jd.blockchain.ledger.core.TransactionRequestExtension; import com.jd.blockchain.utils.Bytes; @@ -16,8 +30,8 @@ public class ParticipantStateUpdateOperationHandle extends AbstractLedgerOperati @Override protected void doProcess(ParticipantStateUpdateOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { + TransactionRequestExtension requestContext, LedgerQuery previousBlockDataset, + OperationHandleContext handleContext) { // 权限校验; SecurityPolicy securityPolicy = SecurityContext.getContextUsersPolicy(); @@ -35,7 +49,7 @@ public class ParticipantStateUpdateOperationHandle extends AbstractLedgerOperati for(int i = 0; i < participants.length; i++) { if (stateUpdateOperation.getParticipantIdentity().getPubKey().equals(participants[i].getPubKey())) { - participantNode = new PartNode(participants[i].getId(), participants[i].getName(), participants[i].getPubKey(), ParticipantNodeState.CONSENSUSED); + participantNode = new PartNode(participants[i].getId(), participants[i].getName(), participants[i].getPubKey(), ParticipantNodeState.ACTIVED); break; } } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index c8ec5e93..08c3ec89 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -50,7 +50,7 @@ public class LedgerAdminDatasetTest { parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(bckeys[i].getPubKey()); - parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[i].setParticipantState(ParticipantNodeState.ACTIVED); } ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); initSetting.setConsensusParticipants(parties1); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java index e3c36a73..e916cc66 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitOperationTest.java @@ -77,7 +77,7 @@ public class LedgerInitOperationTest { parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(keys[i].getPubKey()); - parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[i].setParticipantState(ParticipantNodeState.ACTIVED); } ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); @@ -116,7 +116,7 @@ public class LedgerInitOperationTest { for (int i = 0; i < parties.length; i++) { keys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()), - "Participant[" + i + "]", keys[i].getPubKey(), ParticipantNodeState.CONSENSUSED); + "Participant[" + i + "]", keys[i].getPubKey(), ParticipantNodeState.ACTIVED); } ParticipantCertData[] parties1 = Arrays.copyOf(parties, 4); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java index 2485cbd0..3884186e 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerInitSettingSerializeTest.java @@ -81,7 +81,7 @@ public class LedgerInitSettingSerializeTest { parties[i].setHostAddress(new NetworkAddress("192.168.10." + (10 + i), 10010 + 10 * i)); parties[i].setName("Participant[" + i + "]"); parties[i].setPubKey(keys[i].getPubKey()); - parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[i].setParticipantState(ParticipantNodeState.ACTIVED); } ConsensusParticipantData[] parties1 = Arrays.copyOf(parties, 4); @@ -124,7 +124,7 @@ public class LedgerInitSettingSerializeTest { for (int i = 0; i < parties.length; i++) { keys[i] = BlockchainKeyGenerator.getInstance().generate(); parties[i] = new ParticipantCertData(AddressEncoding.generateAddress(keys[i].getPubKey()), - "Participant[" + i + "]", keys[i].getPubKey(), ParticipantNodeState.CONSENSUSED); + "Participant[" + i + "]", keys[i].getPubKey(), ParticipantNodeState.ACTIVED); } ParticipantCertData[] parties1 = Arrays.copyOf(parties, 4); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java index 6438ceb9..225bd16d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerManagerTest.java @@ -205,7 +205,7 @@ public class LedgerManagerTest { parties[0].setPubKey(kp0.getPubKey()); parties[0].setAddress(AddressEncoding.generateAddress(kp0.getPubKey())); parties[0].setHostAddress(new NetworkAddress("127.0.0.1", 9000)); - parties[0].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[0].setParticipantState(ParticipantNodeState.ACTIVED); parties[1] = new ConsensusParticipantData(); parties[1].setId(1); @@ -214,7 +214,7 @@ public class LedgerManagerTest { parties[1].setPubKey(kp1.getPubKey()); parties[1].setAddress(AddressEncoding.generateAddress(kp1.getPubKey())); parties[1].setHostAddress(new NetworkAddress("127.0.0.1", 9010)); - parties[1].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[1].setParticipantState(ParticipantNodeState.ACTIVED); parties[2] = new ConsensusParticipantData(); parties[2].setId(2); @@ -223,7 +223,7 @@ public class LedgerManagerTest { parties[2].setPubKey(kp2.getPubKey()); parties[2].setAddress(AddressEncoding.generateAddress(kp2.getPubKey())); parties[2].setHostAddress(new NetworkAddress("127.0.0.1", 9020)); - parties[2].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[2].setParticipantState(ParticipantNodeState.ACTIVED); parties[3] = new ConsensusParticipantData(); parties[3].setId(3); @@ -232,7 +232,7 @@ public class LedgerManagerTest { parties[3].setPubKey(kp3.getPubKey()); parties[3].setAddress(AddressEncoding.generateAddress(kp3.getPubKey())); parties[3].setHostAddress(new NetworkAddress("127.0.0.1", 9030)); - parties[3].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[3].setParticipantState(ParticipantNodeState.ACTIVED); initSetting.setConsensusParticipants(parties); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java index 6f208323..51254613 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerMetaDataTest.java @@ -186,7 +186,7 @@ public class LedgerMetaDataTest { // NetworkAddress consensusAddress = new NetworkAddress("192.168.1.1", 9001, // false); Bytes address = AddressEncoding.generateAddress(pubKey); - ParticipantCertData participantCertData = new ParticipantCertData(address, name, pubKey, ParticipantNodeState.CONSENSUSED); + ParticipantCertData participantCertData = new ParticipantCertData(address, name, pubKey, ParticipantNodeState.ACTIVED); // encode and decode byte[] encodeBytes = BinaryProtocol.encode(participantCertData, ParticipantNode.class); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java index be201848..d3595755 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerTestUtils.java @@ -68,7 +68,7 @@ public class LedgerTestUtils { parties[i].setPubKey(partiKeys[i].getPubKey()); parties[i].setAddress(AddressEncoding.generateAddress(partiKeys[i].getPubKey())); parties[i].setHostAddress(new NetworkAddress("192.168.1." + (10 + i), 9000)); - parties[i].setParticipantState(ParticipantNodeState.CONSENSUSED); + parties[i].setParticipantState(ParticipantNodeState.ACTIVED); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java index 86900b1c..120f6730 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerInitProperties.java @@ -326,7 +326,7 @@ public class LedgerInitProperties { .parseBoolean(PropertiesUtils.getRequiredProperty(props, initializerSecureKey)); NetworkAddress initializerAddress = new NetworkAddress(initializerHost, initializerPort, initializerSecure); parti.setInitializerAddress(initializerAddress); - parti.setParticipantNodeState(ParticipantNodeState.CONSENSUSED); + parti.setParticipantNodeState(ParticipantNodeState.ACTIVED); initProps.addConsensusParticipant(parti); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java index 375f196b..f7b97fd1 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantNodeState.java @@ -18,12 +18,12 @@ public enum ParticipantNodeState { /** * 已注册; */ - REGISTED((byte) 0), + REGISTERED((byte) 0), /** - * 已共识; + * 已激活; */ - CONSENSUSED((byte) 1); + ACTIVED((byte) 1); @EnumField(type= PrimitiveType.INT8) public final byte CODE; diff --git a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java index 9a51657a..4fb229ce 100644 --- a/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java +++ b/source/sdk/sdk-samples/src/test/java/test/com/jd/blockchain/sdk/test/SDK_GateWay_Participant_State_Update_Test_.java @@ -81,7 +81,7 @@ public class SDK_GateWay_Participant_State_Update_Test_ { NetworkAddress networkAddress = new NetworkAddress("127.0.0.1", 20000); - txTemp.states().update(user.getIdentity(),networkAddress, ParticipantNodeState.CONSENSUSED); + txTemp.states().update(user.getIdentity(),networkAddress, ParticipantNodeState.ACTIVED); // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java index c03eb7cc..d1e9409c 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/Utils.java @@ -88,7 +88,7 @@ public class Utils { public static ParticipantNode[] loadParticipantNodes() { ParticipantNode[] participantNodes = new ParticipantNode[PUB_KEYS.length]; for (int i = 0; i < PUB_KEYS.length; i++) { - participantNodes[i] = new PartNode(i, KeyGenUtils.decodePubKey(PUB_KEYS[i]), ParticipantNodeState.CONSENSUSED); + participantNodes[i] = new PartNode(i, KeyGenUtils.decodePubKey(PUB_KEYS[i]), ParticipantNodeState.ACTIVED); } return participantNodes; } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index b38b190f..c20d8e04 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -201,7 +201,7 @@ public class IntegrationBase { ParticipantInfoData participantInfoData = new ParticipantInfoData("peer4", participantKeyPair.getPubKey(), new NetworkAddress("127.0.0.1", 20000)); - txTpl.states().update(new BlockchainIdentityData(participantInfoData.getPubKey()), participantInfoData.getNetworkAddress(), ParticipantNodeState.CONSENSUSED); + txTpl.states().update(new BlockchainIdentityData(participantInfoData.getPubKey()), participantInfoData.getNetworkAddress(), ParticipantNodeState.ACTIVED); // 签名; PreparedTransaction ptx = txTpl.prepare(); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index 42b61ebe..72989010 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -12,7 +12,6 @@ import java.util.Properties; import org.mockito.Mockito; -import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.consensus.ClientIdentification; import com.jd.blockchain.consensus.ClientIdentifications; @@ -50,6 +49,8 @@ import com.jd.blockchain.ledger.NodeRequest; import com.jd.blockchain.ledger.Operation; import com.jd.blockchain.ledger.OperationResult; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantRegisterOperation; +import com.jd.blockchain.ledger.ParticipantStateUpdateOperation; import com.jd.blockchain.ledger.TransactionContent; import com.jd.blockchain.ledger.TransactionContentBody; import com.jd.blockchain.ledger.TransactionPermission; From 31071068c01433059ed3dd259d6dfc5edc82e43b Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 11 Sep 2019 10:29:51 +0800 Subject: [PATCH 098/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/base/pom.xml | 7 +++++++ source/gateway/pom.xml | 15 +++++++++------ source/peer/pom.xml | 10 ++++++++-- source/pom.xml | 4 ++-- source/test/test-consensus-client/pom.xml | 12 ++++++++++++ source/test/test-consensus-node/pom.xml | 12 ++++++++++++ source/tools/tools-initializer/pom.xml | 8 ++++---- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/source/base/pom.xml b/source/base/pom.xml index 94592186..0e0b7299 100644 --- a/source/base/pom.xml +++ b/source/base/pom.xml @@ -8,4 +8,11 @@ 1.1.0-SNAPSHOT base + + + + org.springframework.boot + spring-boot-starter-log4j2 + + \ No newline at end of file diff --git a/source/gateway/pom.xml b/source/gateway/pom.xml index 652d0ef0..2df3c7af 100644 --- a/source/gateway/pom.xml +++ b/source/gateway/pom.xml @@ -77,19 +77,22 @@ commons-io - - org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + - - - + + org.springframework.boot spring-boot-starter-security diff --git a/source/peer/pom.xml b/source/peer/pom.xml index a7127e68..f428d188 100644 --- a/source/peer/pom.xml +++ b/source/peer/pom.xml @@ -54,12 +54,18 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + - + org.springframework.boot diff --git a/source/pom.xml b/source/pom.xml index cca9e658..77e8149b 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -78,10 +78,10 @@ - + junit junit diff --git a/source/test/test-consensus-client/pom.xml b/source/test/test-consensus-client/pom.xml index f3cab64c..1fbfa8aa 100644 --- a/source/test/test-consensus-client/pom.xml +++ b/source/test/test-consensus-client/pom.xml @@ -13,7 +13,19 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + com.jd.blockchain consensus-bftsmart diff --git a/source/test/test-consensus-node/pom.xml b/source/test/test-consensus-node/pom.xml index 28a5776b..671ecd50 100644 --- a/source/test/test-consensus-node/pom.xml +++ b/source/test/test-consensus-node/pom.xml @@ -12,7 +12,19 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + com.jd.blockchain peer diff --git a/source/tools/tools-initializer/pom.xml b/source/tools/tools-initializer/pom.xml index b4f51d28..f4cfe07d 100644 --- a/source/tools/tools-initializer/pom.xml +++ b/source/tools/tools-initializer/pom.xml @@ -72,18 +72,18 @@ org.springframework.boot spring-boot-starter-web - + - + org.springframework.boot From e285cac39b353e94ee128da8e948b0fc01ce3007 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 11 Sep 2019 13:46:28 +0800 Subject: [PATCH 099/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9B=86=E6=88=90?= =?UTF-8?q?=E6=B5=8B=E8=AF=95BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/binary-proto/pom.xml | 7 +++++++ .../gateway/web/GatewayWebServerConfigurer.java | 2 +- .../transaction/RolesConfigureOpTemplate.java | 1 + .../transaction/UserAuthorizeOpTemplate.java | 1 + .../blockchain/peer/web/ManagementController.java | 2 +- .../initializer/LedgerInitializeWeb4Nodes.java | 15 +++++++++++++++ .../src/test/resources/logback-test.xml | 6 ------ 7 files changed, 26 insertions(+), 8 deletions(-) delete mode 100644 source/test/test-integration/src/test/resources/logback-test.xml diff --git a/source/binary-proto/pom.xml b/source/binary-proto/pom.xml index b0c94608..ace2ff38 100644 --- a/source/binary-proto/pom.xml +++ b/source/binary-proto/pom.xml @@ -15,9 +15,16 @@ utils-common ${project.version} + + + org.springframework.boot + spring-boot-starter-log4j2 + + junit junit + test \ No newline at end of file diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java index fba7295a..83268ad8 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java @@ -35,7 +35,7 @@ public class GatewayWebServerConfigurer implements WebMvcConfigurer { JSONSerializeUtils.disableCircularReferenceDetect(); JSONSerializeUtils.configStringSerializer(ByteArray.class); DataContractRegistry.register(BftsmartNodeSettings.class); - DataContractRegistry.register(LedgerAdminInfo.class); +// DataContractRegistry.register(LedgerAdminInfo.class); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java index 5ff5fea0..ab8d19dc 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java @@ -19,6 +19,7 @@ public class RolesConfigureOpTemplate implements RolesConfigurer, RolesConfigure static { DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(RolesConfigureOperation.class); } private Map rolesMap = Collections diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java index a8f44f87..ed851763 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java @@ -18,6 +18,7 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe static { DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(UserAuthorizeOperation.class); } private Set userAuthMap = Collections diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index c65ab837..b254da02 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -111,7 +111,7 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(BftsmartConsensusSettings.class); DataContractRegistry.register(BftsmartNodeSettings.class); - DataContractRegistry.register(LedgerAdminDataQuery.class); +// DataContractRegistry.register(LedgerAdminDataQuery.class); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index 6909c155..a48e7ca2 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -43,6 +43,21 @@ public class LedgerInitializeWeb4Nodes { public static final String[] PRIV_KEYS = IntegrationBase.PRIV_KEYS; + static { + try { + // 首先获取当前Resource路径 + ClassPathResource ledgerInitSettingResource = new ClassPathResource(""); + String path = ledgerInitSettingResource.getURL().getPath(); + System.out.println("-----" + path + "-----"); + // 将参数注册进去 + System.setProperty("peer.log", path); + System.setProperty("init.log", path); + System.setProperty("gateway.log", path); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Test public void testMQInitByMemWith4Nodes() { testInitWith4Nodes(LedgerInitConsensusConfig.mqConfig, LedgerInitConsensusConfig.memConnectionStrings); diff --git a/source/test/test-integration/src/test/resources/logback-test.xml b/source/test/test-integration/src/test/resources/logback-test.xml deleted file mode 100644 index 29013782..00000000 --- a/source/test/test-integration/src/test/resources/logback-test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 48e0f09f741598364ca59e42a8906220aa6a1487 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 11 Sep 2019 14:45:49 +0800 Subject: [PATCH 100/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E9=98=BB=E5=A1=9E=EF=BC=8C=E5=A4=84=E7=90=86=E5=86=B3=E5=AE=9A?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E4=B8=BANULL=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContractCodeDeployOperationHandle.java | 13 ++----- .../web/LedgerInitializeWebController.java | 39 +++++++++++++------ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java index 3f64e4b8..b4e1cac3 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ContractCodeDeployOperationHandle.java @@ -1,15 +1,9 @@ package com.jd.blockchain.ledger.core.handles; +import com.jd.blockchain.contract.ContractJarUtils; import com.jd.blockchain.ledger.ContractCodeDeployOperation; import com.jd.blockchain.ledger.LedgerPermission; -import com.jd.blockchain.ledger.core.LedgerDataQuery; -import com.jd.blockchain.ledger.core.LedgerDataset; -import com.jd.blockchain.ledger.core.LedgerService; -import com.jd.blockchain.ledger.core.MultiIDsPolicy; -import com.jd.blockchain.ledger.core.OperationHandleContext; -import com.jd.blockchain.ledger.core.SecurityContext; -import com.jd.blockchain.ledger.core.SecurityPolicy; -import com.jd.blockchain.ledger.core.TransactionRequestExtension; +import com.jd.blockchain.ledger.core.*; public class ContractCodeDeployOperationHandle extends AbstractLedgerOperationHandle { @@ -19,11 +13,10 @@ public class ContractCodeDeployOperationHandle extends AbstractLedgerOperationHa @Override protected void doProcess(ContractCodeDeployOperation op, LedgerDataset newBlockDataset, - TransactionRequestExtension requestContext, LedgerDataQuery previousBlockDataset, - OperationHandleContext handleContext, LedgerService ledgerService) { TransactionRequestExtension requestContext, LedgerQuery ledger, OperationHandleContext handleContext) { + // TODO: 请求者应该提供合约账户的公钥签名,以确保注册人对注册的地址和公钥具有合法的使用权; // 权限校验; diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java index 87ffc108..0d4216ea 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/web/LedgerInitializeWebController.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; @@ -60,6 +61,12 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI private static final String DEFAULT_SIGN_ALGORITHM = "ED25519"; + /** + * 决定值列表并发处理,必须等待Local释放 + * + */ + private final CountDownLatch decisionsCountDownLatch = new CountDownLatch(1); + private final SignatureFunction SIGN_FUNC; private volatile LedgerInitConfiguration ledgerInitConfig; @@ -190,18 +197,25 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI } public LedgerInitDecision makeLocalDecision(PrivKey privKey) { - // 生成账本; - initializer.prepareLedger(dbConn.getStorageService(), getNodesSignatures()); - - // 生成签名决定; - this.localDecision = makeDecision(currentId, initializer.getLedgerHash(), privKey); - this.decisions = new DecisionResultHandle[ledgerInitConfig.getParticipantCount()]; - for (int i = 0; i < decisions.length; i++) { - // 参与者的 id 是依次递增的; - this.decisions[i] = new DecisionResultHandle(i); + + try { + // 生成账本; + initializer.prepareLedger(dbConn.getStorageService(), getNodesSignatures()); + + // 生成签名决定; + this.localDecision = makeDecision(currentId, initializer.getLedgerHash(), privKey); + this.decisions = new DecisionResultHandle[ledgerInitConfig.getParticipantCount()]; + for (int i = 0; i < decisions.length; i++) { + // 参与者的 id 是依次递增的; + this.decisions[i] = new DecisionResultHandle(i); + } + // 预置当前参与方的“决定”到列表,避免向自己发起请求; + this.decisions[currentId].setValue(localDecision); + } finally { + // 释放,以便于其他节点接收 + this.decisionsCountDownLatch.countDown(); } - // 预置当前参与方的“决定”到列表,避免向自己发起请求; - this.decisions[currentId].setValue(localDecision); + return localDecision; } @@ -678,6 +692,9 @@ public class LedgerInitializeWebController implements LedgerInitProcess, LedgerI try { + // 必须等待本地处理完成,此处线程阻塞 + this.decisionsCountDownLatch.await(); + DecisionResultHandle resultHandle = this.decisions[remoteId]; if (!validateAndRecordDecision(initDecision, resultHandle)) { // 签名无效; From be6d99ae3e15f89f28e854913e14efb51c4263c5 Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Wed, 11 Sep 2019 16:00:36 +0800 Subject: [PATCH 101/124] solve transactionengineimpl null pointer bug --- .../com/jd/blockchain/ledger/core/TransactionEngineImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index 416a269d..0d429811 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -66,6 +66,7 @@ public class TransactionEngineImpl implements TransactionEngine { public InnerTransactionBatchProcessor(LedgerRepository ledgerRepo, OperationHandleRegisteration handlesRegisteration) { super(ledgerRepo, handlesRegisteration); + ledgerHash = ledgerRepo.getHash(); } @Override From 3deb0effb39b7dc2b26fc599b10e5aaa2227f9f4 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 11 Sep 2019 16:23:21 +0800 Subject: [PATCH 102/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B4=A6=E6=9C=AC?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blockchain/gateway/web/BlockBrowserController.java | 4 ++-- .../blockchain/ledger/core/TransactionEngineImpl.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java index 162702ab..7de12a75 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java @@ -70,13 +70,13 @@ public class BlockBrowserController implements BlockchainExtendQueryService { @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/admininfo") @Override - public LedgerAdminInfo getLedgerAdminInfo(HashDigest ledgerHash) { + public LedgerAdminInfo getLedgerAdminInfo(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { return peerService.getQueryService().getLedgerAdminInfo(ledgerHash); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/participants") @Override - public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash) { + public ParticipantNode[] getConsensusParticipants(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { return peerService.getQueryService().getConsensusParticipants(ledgerHash); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java index 0d429811..2d0fb55a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionEngineImpl.java @@ -54,31 +54,31 @@ public class TransactionEngineImpl implements TransactionEngine { private class InnerTransactionBatchProcessor extends TransactionBatchProcessor { - private HashDigest ledgerHash; +// private HashDigest ledgerHash; /** * 创建交易批处理器; * * @param ledgerRepo 账本; * @param handlesRegisteration 操作处理对象注册表; - * @param blockHeight + * */ public InnerTransactionBatchProcessor(LedgerRepository ledgerRepo, OperationHandleRegisteration handlesRegisteration) { super(ledgerRepo, handlesRegisteration); - ledgerHash = ledgerRepo.getHash(); +// ledgerHash = ledgerRepo.getHash(); } @Override protected void onCommitted() { super.onCommitted(); - finishBatch(ledgerHash); + finishBatch(getLedgerHash()); } @Override protected void onCanceled() { super.onCanceled(); - finishBatch(ledgerHash); + finishBatch(getLedgerHash()); } } From 05940518a04bf270c5608994036a9d1dea959e3e Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 11 Sep 2019 16:33:32 +0800 Subject: [PATCH 103/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ledger/core/handles/DataAccountKVSetOperationHandle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index b597f327..4a8209b2 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -35,7 +35,7 @@ public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHand throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); } KVWriteEntry[] writeSet = kvWriteOp.getWriteSet(); - long v = -1; + long v = -1L; for (KVWriteEntry kvw : writeSet) { v = account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); if (v < 0) { From 536cfdf44305e4a7e26d100029169accb65a0869 Mon Sep 17 00:00:00 2001 From: zhaoguangwei Date: Thu, 12 Sep 2019 18:18:34 +0800 Subject: [PATCH 104/124] should use: partiConf.getAddress().toBase58() --- .../com/jd/blockchain/tools/initializer/LedgerInitCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java index c39da297..0073a346 100644 --- a/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java +++ b/source/tools/tools-initializer/src/main/java/com/jd/blockchain/tools/initializer/LedgerInitCommand.java @@ -103,7 +103,7 @@ public class LedgerInitCommand { // partiAddress = partiConf.getAddress(); // } // } - if (localNodeAddress.equals(partiConf.getAddress())) { + if (localNodeAddress.equals(partiConf.getAddress().toBase58())) { currId = i; } } From e2126851fae49e1a637561e5b7cf4f5f823de886 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 16 Sep 2019 10:30:36 +0800 Subject: [PATCH 105/124] Refactor LedgerAdminInfo as a binary serializable Object; --- .../com/jd/blockchain/consts/DataCodes.java | 2 +- .../ledger/core/EmptyLedgerDataset.java | 4 ++-- .../ledger/core/LedgerAdminDataQuery.java | 4 ++-- .../ledger/core/LedgerAdminDataset.java | 6 +++--- .../ledger/core/LedgerInitializer.java | 11 ++++++++++ .../blockchain/ledger/core/LedgerQuery.java | 5 +++++ .../ledger/core/LedgerRepositoryImpl.java | 21 ++++++++++++++----- .../ledger/core/LedgerAdminDatasetTest.java | 18 ++++++++++++++-- .../jd/blockchain/ledger/LedgerAdminInfo.java | 18 ++++++++++------ .../ledger/LedgerAdminSettings.java | 8 +++++++ .../test/ledger/RolesAuthorizationTest.java | 4 ++-- 11 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 607c051d..279daea0 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -22,7 +22,7 @@ public interface DataCodes { public static final int DATA_SNAPSHOT = 0x130; -// public static final int LEDGER_ADMIN_DATA = 0x131; + public static final int LEDGER_ADMIN_INFO = 0x131; public static final int TX = 0x200; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java index 64e08117..3bd87b8d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java @@ -2,7 +2,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.ledger.ParticipantDataQuery; import com.jd.blockchain.ledger.ParticipantNode; @@ -45,7 +45,7 @@ public class EmptyLedgerDataset implements LedgerDataQuery { @Override - public LedgerAdminInfo getAdminInfo() { + public LedgerAdminSettings getAdminInfo() { return null; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java index 05c62138..623a47b1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataQuery.java @@ -1,11 +1,11 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.ParticipantDataQuery; public interface LedgerAdminDataQuery { - LedgerAdminInfo getAdminInfo(); + LedgerAdminSettings getAdminInfo(); ParticipantDataQuery getParticipantDataset(); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index 9c794f82..f7cf951c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -8,7 +8,7 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.HashFunction; -import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.LedgerMetadata; @@ -23,7 +23,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, LedgerAdminInfo { +public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, LedgerAdminSettings { static { DataContractRegistry.register(LedgerMetadata.class); @@ -110,7 +110,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, } @Override - public LedgerAdminInfo getAdminInfo() { + public LedgerAdminSettings getAdminInfo() { return this; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index ddd5a68d..06fce128 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -8,6 +8,7 @@ import com.jd.blockchain.ledger.BlockchainIdentityData; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.DigitalSignature; import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerInitException; import com.jd.blockchain.ledger.LedgerInitOperation; @@ -254,6 +255,16 @@ public class LedgerInitializer { public LedgerAdminInfo getAdminInfo(LedgerBlock block) { return null; } + + @Override + public LedgerAdminSettings getAdminSettings() { + return null; + } + + @Override + public LedgerAdminSettings getAdminSettings(LedgerBlock block) { + return null; + } @Override public LedgerBlock getBlock(HashDigest hash) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java index 0bed1953..748cb212 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java @@ -2,6 +2,7 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.LedgerBlock; public interface LedgerQuery { @@ -53,6 +54,10 @@ public interface LedgerQuery { LedgerAdminInfo getAdminInfo(); LedgerAdminInfo getAdminInfo(LedgerBlock block); + + LedgerAdminSettings getAdminSettings(); + + LedgerAdminSettings getAdminSettings(LedgerBlock block); LedgerBlock getBlock(HashDigest hash); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 55c9d6d6..caba630f 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -7,6 +7,7 @@ import com.jd.blockchain.crypto.HashFunction; import com.jd.blockchain.ledger.BlockBody; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.LedgerBlock; import com.jd.blockchain.ledger.LedgerDataSnapshot; import com.jd.blockchain.ledger.LedgerInitSetting; @@ -244,7 +245,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public LedgerAdminInfo getAdminInfo() { - return getAdminInfo(getLatestBlock()); + return getAdminSettings(getLatestBlock()); } private LedgerBlock deserialize(byte[] blockBytes) { @@ -265,7 +266,17 @@ class LedgerRepositoryImpl implements LedgerRepository { } @Override - public LedgerAdminDataset getAdminInfo(LedgerBlock block) { + public LedgerAdminInfo getAdminInfo(LedgerBlock block) { + return getAdminSettings(block); + } + + @Override + public LedgerAdminSettings getAdminSettings() { + return getAdminSettings(getLatestBlock()); + } + + @Override + public LedgerAdminSettings getAdminSettings(LedgerBlock block) { long height = getLatestBlockHeight(); if (height == block.getHeight()) { return latestState.getAdminDataset(); @@ -284,7 +295,7 @@ class LedgerRepositoryImpl implements LedgerRepository { if (height == block.getHeight()) { return latestState.getUserAccountSet(); } - LedgerAdminDataset adminAccount = getAdminInfo(block); + LedgerAdminSettings adminAccount = getAdminSettings(block); return createUserAccountSet(block, adminAccount.getSettings().getCryptoSetting()); } @@ -300,7 +311,7 @@ class LedgerRepositoryImpl implements LedgerRepository { return latestState.getDataAccountSet(); } - LedgerAdminDataset adminAccount = getAdminInfo(block); + LedgerAdminSettings adminAccount = getAdminSettings(block); return createDataAccountSet(block, adminAccount.getSettings().getCryptoSetting()); } @@ -316,7 +327,7 @@ class LedgerRepositoryImpl implements LedgerRepository { return latestState.getContractAccountSet(); } - LedgerAdminDataset adminAccount = getAdminInfo(block); + LedgerAdminSettings adminAccount = getAdminSettings(block); return createContractAccountSet(block, adminAccount.getSettings().getCryptoSetting()); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index 08c3ec89..684d720a 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -10,7 +10,6 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.Random; -import com.jd.blockchain.ledger.*; import org.junit.Test; import com.jd.blockchain.crypto.AddressEncoding; @@ -20,6 +19,21 @@ import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; +import com.jd.blockchain.ledger.BlockchainKeyGenerator; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.LedgerAdminSettings; +import com.jd.blockchain.ledger.LedgerMetadata_V2; +import com.jd.blockchain.ledger.LedgerPermission; +import com.jd.blockchain.ledger.LedgerSettings; +import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.ParticipantNodeState; +import com.jd.blockchain.ledger.RolePrivilegeSettings; +import com.jd.blockchain.ledger.RolePrivileges; +import com.jd.blockchain.ledger.RolesPolicy; +import com.jd.blockchain.ledger.TransactionPermission; +import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.ledger.UserRolesSettings; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; @@ -213,7 +227,7 @@ public class LedgerAdminDatasetTest { actualLedgerSettings.getCryptoSetting().getHashAlgorithm()); } - private void verifyRealoadingRoleAuthorizations(LedgerAdminInfo actualAccount, + private void verifyRealoadingRoleAuthorizations(LedgerAdminSettings actualAccount, RolePrivilegeSettings expRolePrivilegeSettings, UserRolesSettings expUserRoleSettings) { // 验证基本信息; RolePrivilegeSettings actualRolePrivileges = actualAccount.getRolePrivileges(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java index b666535e..62a2aa2f 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminInfo.java @@ -1,17 +1,23 @@ package com.jd.blockchain.ledger; +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; + +@DataContract(code=DataCodes.LEDGER_ADMIN_INFO) public interface LedgerAdminInfo { + @DataField(order=1, refContract = true) LedgerMetadata_V2 getMetadata(); + @DataField(order=2, refContract = true) LedgerSettings getSettings(); - long getParticipantCount(); - + @DataField(order=3, refContract = true, list = true) ParticipantNode[] getParticipants(); - - UserRolesSettings getUserRoles(); - - RolePrivilegeSettings getRolePrivileges(); + + @DataField(order=4, primitiveType = PrimitiveType.INT64) + long getParticipantCount(); } \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java new file mode 100644 index 00000000..1c8cc9ff --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java @@ -0,0 +1,8 @@ +package com.jd.blockchain.ledger; + +public interface LedgerAdminSettings extends LedgerAdminInfo { + + UserRolesSettings getUserRoles(); + + RolePrivilegeSettings getRolePrivileges(); +} diff --git a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java index 2cd06f96..0e015086 100644 --- a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java +++ b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java @@ -192,7 +192,7 @@ public class RolesAuthorizationTest { if (roles == null) { roles = new String[0]; } - UserRoles userRoles = ledger.getAdminInfo().getUserRoles().getUserRoles(address); + UserRoles userRoles = ledger.getAdminSettings().getUserRoles().getUserRoles(address); assertNotNull(userRoles); assertEquals(policy, userRoles.getPolicy()); @@ -206,7 +206,7 @@ public class RolesAuthorizationTest { private void assertRolePermissions(LedgerQuery ledger, String roleName, LedgerPermission[] ledgerPermissions, TransactionPermission[] txPermissions) { - RolePrivilegeSettings roles = ledger.getAdminInfo().getRolePrivileges(); + RolePrivilegeSettings roles = ledger.getAdminSettings().getRolePrivileges(); assertTrue(roles.contains(roleName)); RolePrivileges privileges = roles.getRolePrivilege(roleName); assertEquals(ledgerPermissions.length, privileges.getLedgerPrivilege().getPermissionCount()); From ab0978146e25f6d33ac7a3a53f81f307b31a2502 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 16 Sep 2019 15:05:19 +0800 Subject: [PATCH 106/124] Updated default init configuration template; --- .../main/resources/config/init/ledger.init | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/source/deployment/deployment-peer/src/main/resources/config/init/ledger.init b/source/deployment/deployment-peer/src/main/resources/config/init/ledger.init index 14a95134..e84f24f5 100644 --- a/source/deployment/deployment-peer/src/main/resources/config/init/ledger.init +++ b/source/deployment/deployment-peer/src/main/resources/config/init/ledger.init @@ -7,6 +7,48 @@ ledger.name= #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 + +#----------------------------------------------- +# 初始的角色名称列表;可选项; +# 角色名称不区分大小写,最长不超过20个字符;多个角色名称之间用半角的逗点“,”分隔; +# 系统会预置一个默认角色“DEFAULT”,所有未指定角色的用户都以赋予该角色的权限;若初始化时未配置默认角色的权限,则为默认角色分配所有权限; +# +# 注:如果声明了角色,但未声明角色对应的权限清单,这会忽略该角色的初始化; +# +#security.roles=DEFAULT, ADMIN, MANAGER, GUEST + +# 赋予角色的账本权限清单;可选项; +# 可选的权限如下; +# AUTHORIZE_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, +# REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, +# SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +# APPROVE_TX, CONSENSUS_TX +# 多项权限之间用逗点“,”分隔; +# +#security.role.DEFAULT.ledger-privileges=REGISTER_USER, REGISTER_DATA_ACCOUNT + +# 赋予角色的交易权限清单;可选项; +# 可选的权限如下; +# DIRECT_OPERATION, CONTRACT_OPERATION +# 多项权限之间用逗点“,”分隔; +# +#security.role.DEFAULT.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 其它角色的配置示例; +# 系统管理员角色:只能操作全局性的参数配置和用户注册,只能执行直接操作指令; +#security.role.ADMIN.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, SET_CONSENSUS, SET_CRYPTO, REGISTER_PARTICIPANT, REGISTER_USER +#security.role.ADMIN.tx-privileges=DIRECT_OPERATION + +# 业务主管角色:只能够执行账本数据相关的操作,包括注册用户、注册数据账户、注册合约、升级合约、写入数据等;能够执行直接操作指令和调用合约; +#security.role.MANAGER.ledger-privileges=CONFIGURE_ROLES, AUTHORIZE_USER_ROLES, REGISTER_USER, REGISTER_DATA_ACCOUNT, REGISTER_CONTRACT, UPGRADE_CONTRACT, SET_USER_ATTRIBUTES, WRITE_DATA_ACCOUNT, +#security.role.MANAGER.tx-privileges=DIRECT_OPERATION, CONTRACT_OPERATION + +# 访客角色:不具备任何的账本权限,只有数据读取的操作;也只能够通过调用合约来读取数据; +#security.role.GUEST.ledger-privileges= +#security.role.GUEST.tx-privileges=CONTRACT_OPERATION + + +#----------------------------------------------- #共识服务提供者;必须; consensus.service-provider=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider @@ -17,16 +59,36 @@ consensus.conf=bftsmart.config crypto.service-providers=com.jd.blockchain.crypto.service.classic.ClassicCryptoService, \ com.jd.blockchain.crypto.service.sm.SMCryptoService +#从存储中加载账本数据时,是否校验哈希;可选; +crypto.verify-hash=true + +#哈希算法; +crypto.hash-algorithm=SHA256 + #参与方的个数,后续以 cons_parti.id 分别标识每一个参与方的配置; cons_parti.count=4 +#--------------------- #第0个参与方的名称; cons_parti.0.name= #第0个参与方的公钥文件路径; cons_parti.0.pubkey-path= #第0个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.0.pubkey= + +#第0个参与方的角色清单;可选项; +#cons_parti.0.roles=ADMIN, MANAGER +#第0个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +#cons_parti.0.roles-policy=UNION + +#第0个参与方的共识服务的主机地址; +cons_parti.0.consensus.host=127.0.0.1 +#第0个参与方的共识服务的端口; +cons_parti.0.consensus.port=8900 +#第0个参与方的共识服务是否开启安全连接; +cons_parti.0.consensus.secure=true + #第0个参与方的账本初始服务的主机; cons_parti.0.initializer.host=127.0.0.1 #第0个参与方的账本初始服务的端口; @@ -34,12 +96,27 @@ cons_parti.0.initializer.port=8800 #第0个参与方的账本初始服务是否开启安全连接; cons_parti.0.initializer.secure=false + +#--------------------- #第1个参与方的名称; cons_parti.1.name= #第1个参与方的公钥文件路径; cons_parti.1.pubkey-path= #第1个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.1.pubkey= + +#第1个参与方的角色清单;可选项; +#cons_parti.1.roles=MANAGER +#第1个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +#cons_parti.1.roles-policy=UNION + +#第1个参与方的共识服务的主机地址; +cons_parti.1.consensus.host=127.0.0.1 +#第1个参与方的共识服务的端口; +cons_parti.1.consensus.port=8910 +#第1个参与方的共识服务是否开启安全连接; +cons_parti.1.consensus.secure=false + #第1个参与方的账本初始服务的主机; cons_parti.1.initializer.host=127.0.0.1 #第1个参与方的账本初始服务的端口; @@ -47,12 +124,26 @@ cons_parti.1.initializer.port=8810 #第1个参与方的账本初始服务是否开启安全连接; cons_parti.1.initializer.secure=false +#--------------------- #第2个参与方的名称; cons_parti.2.name= #第2个参与方的公钥文件路径; cons_parti.2.pubkey-path= #第2个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.2.pubkey= + +#第2个参与方的角色清单;可选项; +#cons_parti.2.roles=MANAGER +#第2个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +#cons_parti.2.roles-policy=UNION + +#第2个参与方的共识服务的主机地址; +cons_parti.2.consensus.host=127.0.0.1 +#第2个参与方的共识服务的端口; +cons_parti.2.consensus.port=8920 +#第2个参与方的共识服务是否开启安全连接; +cons_parti.2.consensus.secure=false + #第2个参与方的账本初始服务的主机; cons_parti.2.initializer.host=127.0.0.1 #第2个参与方的账本初始服务的端口; @@ -60,12 +151,26 @@ cons_parti.2.initializer.port=8820 #第2个参与方的账本初始服务是否开启安全连接; cons_parti.2.initializer.secure=false +#--------------------- #第3个参与方的名称; cons_parti.3.name= #第3个参与方的公钥文件路径; cons_parti.3.pubkey-path= #第3个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.3.pubkey= + +#第3个参与方的角色清单;可选项; +#cons_parti.3.roles=GUEST +#第3个参与方的角色权限策略,可选值有:UNION(并集),INTERSECT(交集);可选项; +#cons_parti.3.roles-policy=INTERSECT + +#第3个参与方的共识服务的主机地址; +cons_parti.3.consensus.host=127.0.0.1 +#第3个参与方的共识服务的端口; +cons_parti.3.consensus.port=8930 +#第3个参与方的共识服务是否开启安全连接; +cons_parti.3.consensus.secure=false + #第3个参与方的账本初始服务的主机; cons_parti.3.initializer.host=127.0.0.1 #第3个参与方的账本初始服务的端口; From 539f9e9a0709527136ef9f7fc23c1c5b50f2fa36 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Mon, 16 Sep 2019 15:29:42 +0800 Subject: [PATCH 107/124] Fixed bugs that no explicit exception when data contract encoder is not found; --- .../java/com/jd/blockchain/binaryproto/BinaryProtocol.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/binary-proto/src/main/java/com/jd/blockchain/binaryproto/BinaryProtocol.java b/source/binary-proto/src/main/java/com/jd/blockchain/binaryproto/BinaryProtocol.java index ddf8e012..c06c6039 100644 --- a/source/binary-proto/src/main/java/com/jd/blockchain/binaryproto/BinaryProtocol.java +++ b/source/binary-proto/src/main/java/com/jd/blockchain/binaryproto/BinaryProtocol.java @@ -42,6 +42,10 @@ public class BinaryProtocol { long version = HeaderEncoder.resolveVersion(bytes); DataContractEncoder encoder = DataContractContext.ENCODER_LOOKUP.lookup(code, version); + if (encoder == null) { + throw new DataContractException( + String.format("No data contract was registered with code[%s] and version[%s]!", code, version)); + } return encoder.decode(bytes.getInputStream()); } From 03eb3c70ed3e0b1ec6233681c8d30d691363efd7 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Mon, 16 Sep 2019 17:09:54 +0800 Subject: [PATCH 108/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E8=B4=A6=E6=9C=AC=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF=E6=A0=88?= =?UTF-8?q?=E6=BA=A2=E5=87=BA=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contract/contract-maven-plugin/ReadME.MD | 2 +- .../gateway/service/GatewayQueryService.java | 4 +- .../service/GatewayQueryServiceHandler.java | 56 +++--- .../gateway/web/BlockBrowserController.java | 13 +- .../ledger/core/LedgerAdminInfoData.java | 161 ++++++++++++++++++ .../ledger/core/LedgerRepositoryImpl.java | 15 +- ...tSettings.java => LedgerBaseSettings.java} | 2 +- .../sdk/proxy/HttpBlockchainQueryService.java | 14 +- 8 files changed, 227 insertions(+), 40 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java rename source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/{LedgerInitSettings.java => LedgerBaseSettings.java} (98%) diff --git a/source/contract/contract-maven-plugin/ReadME.MD b/source/contract/contract-maven-plugin/ReadME.MD index fce27392..1cc457b6 100644 --- a/source/contract/contract-maven-plugin/ReadME.MD +++ b/source/contract/contract-maven-plugin/ReadME.MD @@ -35,7 +35,7 @@ + 3)executions->execution->phase:建议使用package及其后续阶段(若不了解phase含义,请自行查阅相关信息); + 4)executions->execution->goals->goal:必须使用compile; + 5)mainClass:必填,该类为需要发布的合约执行类(注意此处是类,不是接口),必须正确配置; - + 6)finalName:必填,最终在编译正常的情况下,会产生{finalName}-jdchain-contract.jar文件,只有该文件是可以发布到JDChain的合约包; + + 6)finalName:必填,最终在编译正常的情况下,会产生{finalName}-JDChain-Contract.jar文件,只有该文件是可以发布到JDChain的合约包; ### 2、执行命令 diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java index d88f49aa..8a856953 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryService.java @@ -3,7 +3,7 @@ package com.jd.blockchain.gateway.service; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.sdk.ContractSettings; -import com.jd.blockchain.sdk.LedgerInitSettings; +import com.jd.blockchain.sdk.LedgerBaseSettings; /** * queryService only for gateway; @@ -34,7 +34,7 @@ public interface GatewayQueryService { * 账本Hash * @return */ - LedgerInitSettings getLedgerInitSettings(HashDigest ledgerHash); + LedgerBaseSettings getLedgerBaseSettings(HashDigest ledgerHash); /** * 获取账本指定合约信息 diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java index 451beec4..aeedd64c 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/service/GatewayQueryServiceHandler.java @@ -10,7 +10,7 @@ import com.jd.blockchain.ledger.LedgerAdminInfo; import com.jd.blockchain.ledger.LedgerMetadata; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.sdk.ContractSettings; -import com.jd.blockchain.sdk.LedgerInitSettings; +import com.jd.blockchain.sdk.LedgerBaseSettings; import com.jd.blockchain.utils.QueryUtil; import com.jd.blockchain.utils.codec.HexUtils; import com.jd.blockchain.utils.decompiler.utils.DecompilerUtils; @@ -31,30 +31,26 @@ public class GatewayQueryServiceHandler implements GatewayQueryService { @Override public HashDigest[] getLedgersHash(int fromIndex, int count) { - HashDigest ledgersHash[] = peerService.getQueryService().getLedgerHashs(); - int indexAndCount[] = QueryUtil.calFromIndexAndCount(fromIndex,count,ledgersHash.length); - HashDigest ledgersHashNew[] = Arrays.copyOfRange(ledgersHash,indexAndCount[0],indexAndCount[0]+indexAndCount[1]); - return ledgersHashNew; + HashDigest[] ledgersHashs = peerService.getQueryService().getLedgerHashs(); + int[] indexAndCount = QueryUtil.calFromIndexAndCount(fromIndex, count, ledgersHashs.length); + return Arrays.copyOfRange(ledgersHashs, indexAndCount[0], indexAndCount[0] + indexAndCount[1]); } @Override public ParticipantNode[] getConsensusParticipants(HashDigest ledgerHash, int fromIndex, int count) { - ParticipantNode participantNode[] = peerService.getQueryService().getConsensusParticipants(ledgerHash); - int indexAndCount[] = QueryUtil.calFromIndexAndCount(fromIndex,count,participantNode.length); - ParticipantNode participantNodesNew[] = Arrays.copyOfRange(participantNode,indexAndCount[0],indexAndCount[0]+indexAndCount[1]); - return participantNodesNew; + ParticipantNode[] participantNodes = peerService.getQueryService().getConsensusParticipants(ledgerHash); + int[] indexAndCount = QueryUtil.calFromIndexAndCount(fromIndex, count, participantNodes.length); + ParticipantNode[] participantNodesNews = Arrays.copyOfRange(participantNodes, indexAndCount[0], + indexAndCount[0] + indexAndCount[1]); + return participantNodesNews; } @Override - public LedgerInitSettings getLedgerInitSettings(HashDigest ledgerHash) { - - ParticipantNode[] participantNodes = peerService.getQueryService().getConsensusParticipants(ledgerHash); - - LedgerMetadata ledgerMetadata = peerService.getQueryService().getLedgerMetadata(ledgerHash); + public LedgerBaseSettings getLedgerBaseSettings(HashDigest ledgerHash) { LedgerAdminInfo ledgerAdminInfo = peerService.getQueryService().getLedgerAdminInfo(ledgerHash); - return initLedgerInitSettings(participantNodes, ledgerMetadata, ledgerAdminInfo); + return initLedgerBaseSettings(ledgerAdminInfo); } @Override @@ -73,36 +69,38 @@ public class GatewayQueryServiceHandler implements GatewayQueryService { } /** - * 初始化账本配置 + * 初始化账本的基本配置 + * + * @param ledgerAdminInfo + * 账本信息 * - * @param participantNodes - * 参与方列表 - * @param ledgerMetadata - * 账本元数据 * @return */ - private LedgerInitSettings initLedgerInitSettings(ParticipantNode[] participantNodes, LedgerMetadata ledgerMetadata, LedgerAdminInfo ledgerAdminInfo) { - LedgerInitSettings ledgerInitSettings = new LedgerInitSettings(); + private LedgerBaseSettings initLedgerBaseSettings(LedgerAdminInfo ledgerAdminInfo) { + + LedgerMetadata ledgerMetadata = ledgerAdminInfo.getMetadata(); + + LedgerBaseSettings ledgerBaseSettings = new LedgerBaseSettings(); // 设置参与方 - ledgerInitSettings.setParticipantNodes(participantNodes); + ledgerBaseSettings.setParticipantNodes(ledgerAdminInfo.getParticipants()); // 设置共识设置 - ledgerInitSettings.setConsensusSettings(initConsensusSettings(ledgerAdminInfo)); + ledgerBaseSettings.setConsensusSettings(initConsensusSettings(ledgerAdminInfo)); // 设置参与方根Hash - ledgerInitSettings.setParticipantsHash(ledgerMetadata.getParticipantsHash()); + ledgerBaseSettings.setParticipantsHash(ledgerMetadata.getParticipantsHash()); // 设置算法配置 - ledgerInitSettings.setCryptoSetting(ledgerAdminInfo.getSettings().getCryptoSetting()); + ledgerBaseSettings.setCryptoSetting(ledgerAdminInfo.getSettings().getCryptoSetting()); // 设置种子 - ledgerInitSettings.setSeed(initSeed(ledgerMetadata.getSeed())); + ledgerBaseSettings.setSeed(initSeed(ledgerMetadata.getSeed())); // 设置共识协议 - ledgerInitSettings.setConsensusProtocol(ledgerAdminInfo.getSettings().getConsensusProvider()); + ledgerBaseSettings.setConsensusProtocol(ledgerAdminInfo.getSettings().getConsensusProvider()); - return ledgerInitSettings; + return ledgerBaseSettings; } /** diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java index 7de12a75..a6ef2f9b 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/BlockBrowserController.java @@ -5,6 +5,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; +import com.jd.blockchain.gateway.service.GatewayQueryService; +import com.jd.blockchain.sdk.LedgerBaseSettings; import com.jd.blockchain.utils.decompiler.utils.DecompilerUtils; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -47,6 +49,9 @@ public class BlockBrowserController implements BlockchainExtendQueryService { @Autowired private PeerService peerService; + @Autowired + private GatewayQueryService gatewayQueryService; + @Autowired private DataRetrievalService dataRetrievalService; @@ -86,10 +91,10 @@ public class BlockBrowserController implements BlockchainExtendQueryService { return peerService.getQueryService().getLedgerMetadata(ledgerHash); } -// @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/settings") -// public LedgerInitSettings getLedgerInitSettings(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { -// return gatewayQueryService.getLedgerInitSettings(ledgerHash); -// } + @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/settings") + public LedgerBaseSettings getLedgerInitSettings(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { + return gatewayQueryService.getLedgerBaseSettings(ledgerHash); + } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks") public LedgerBlock[] getBlocks(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java new file mode 100644 index 00000000..6c2660fa --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java @@ -0,0 +1,161 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.binaryproto.BinaryProtocol; +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.Crypto; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.HashFunction; +import com.jd.blockchain.ledger.LedgerMetadata; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.storage.service.ExPolicyKVStorage; +import com.jd.blockchain.storage.service.VersioningKVStorage; +import com.jd.blockchain.utils.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.jd.blockchain.ledger.core.LedgerAdminDataset.*; + +/** + * @author shaozhuguang + * @date 2019-09-16 + * + * LedgerAdminInfo的独立实现类,主要用于页面展示,区分 {@link LedgerAdminDataset} + */ +public class LedgerAdminInfoData implements LedgerAdminInfo { + + static { + DataContractRegistry.register(LedgerMetadata.class); + DataContractRegistry.register(LedgerMetadata_V2.class); + } + + private static Logger LOGGER = LoggerFactory.getLogger(LedgerAdminInfoData.class); + + private final Bytes metaPrefix; + + private final Bytes settingPrefix; + + private LedgerMetadata_V2 origMetadata; + + private LedgerAdminDataset.LedgerMetadataInfo metadata; + + /** + * 原来的账本设置; + * + *
        + * 对 LedgerMetadata 修改的新配置不能立即生效,需要达成共识后,在下一次区块计算中才生效; + */ + private LedgerSettings previousSettings; + + /** + * 账本的参与节点; + */ + private ParticipantDataset participants; + + /** + * 账本参数配置; + */ + private LedgerSettings settings; + + private ExPolicyKVStorage storage; + + public LedgerAdminInfoData(HashDigest adminAccountHash, String keyPrefix, ExPolicyKVStorage kvStorage, + VersioningKVStorage versioningKVStorage, boolean readonly) { + + this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); + this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); + this.storage = kvStorage; + this.origMetadata = loadAndVerifyMetadata(adminAccountHash); + this.metadata = new LedgerMetadataInfo(origMetadata); + this.settings = loadAndVerifySettings(metadata.getSettingsHash()); + // 复制记录一份配置作为上一个区块的原始配置,该实例仅供读取,不做修改,也不会回写到存储; + this.previousSettings = new LedgerConfiguration(settings); + + String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; + this.participants = new ParticipantDataset(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), + partiPrefix, kvStorage, versioningKVStorage, readonly); + } + + private LedgerMetadata_V2 loadAndVerifyMetadata(HashDigest adminAccountHash) { + Bytes key = encodeMetadataKey(adminAccountHash); + byte[] bytes = storage.get(key); + HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); + if (!hashFunc.verify(adminAccountHash, bytes)) { + String errorMsg = "Verification of the hash for ledger metadata failed! --[HASH=" + key + "]"; + LOGGER.error(errorMsg); + throw new LedgerException(errorMsg); + } + return deserializeMetadata(bytes); + } + + + private LedgerSettings loadAndVerifySettings(HashDigest settingsHash) { + if (settingsHash == null) { + return null; + } + Bytes key = encodeSettingsKey(settingsHash); + byte[] bytes = storage.get(key); + HashFunction hashFunc = Crypto.getHashFunction(settingsHash.getAlgorithm()); + if (!hashFunc.verify(settingsHash, bytes)) { + String errorMsg = "Verification of the hash for ledger setting failed! --[HASH=" + key + "]"; + LOGGER.error(errorMsg); + throw new LedgerException(errorMsg); + } + return deserializeSettings(bytes); + } + + private Bytes encodeMetadataKey(HashDigest metadataHash) { + return metaPrefix.concat(metadataHash); + } + + private LedgerMetadata_V2 deserializeMetadata(byte[] bytes) { + return BinaryProtocol.decode(bytes); + } + + private LedgerSettings deserializeSettings(byte[] bytes) { + return BinaryProtocol.decode(bytes); + } + + private Bytes encodeSettingsKey(HashDigest settingsHash) { + return settingPrefix.concat(settingsHash); + } + + /** + * 返回元数据配置信息 + * + * @return + */ + @Override + public LedgerMetadata_V2 getMetadata() { + return metadata; + } + + /** + * 返回当前设置的账本配置; + * + * @return + */ + @Override + public LedgerSettings getSettings() { + return settings; + } + + /** + * 返回当前参与方的数量 + * + * @return + */ + @Override + public long getParticipantCount() { + return participants.getParticipantCount(); + } + + /** + * 返回当前参与方列表 + * + * @return + */ + @Override + public ParticipantNode[] getParticipants() { + return participants.getParticipants(); + } +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index caba630f..90890e4e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -245,7 +245,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public LedgerAdminInfo getAdminInfo() { - return getAdminSettings(getLatestBlock()); + return createAdminData(getLatestBlock()); } private LedgerBlock deserialize(byte[] blockBytes) { @@ -267,7 +267,7 @@ class LedgerRepositoryImpl implements LedgerRepository { @Override public LedgerAdminInfo getAdminInfo(LedgerBlock block) { - return getAdminSettings(block); + return createAdminData(block); } @Override @@ -285,6 +285,17 @@ class LedgerRepositoryImpl implements LedgerRepository { return createAdminDataset(block); } + /** + * 生成LedgerAdminInfoData对象 + * 该对象主要用于页面展示 + * + * @param block + * @return + */ + private LedgerAdminInfoData createAdminData(LedgerBlock block) { + return new LedgerAdminInfoData(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); + } + private LedgerAdminDataset createAdminDataset(LedgerBlock block) { return new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); } diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerBaseSettings.java similarity index 98% rename from source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java rename to source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerBaseSettings.java index d4f287ab..7cc58594 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerInitSettings.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/LedgerBaseSettings.java @@ -14,7 +14,7 @@ import com.jd.blockchain.ledger.ParticipantNode; * @since 1.0.0 * */ -public class LedgerInitSettings { +public class LedgerBaseSettings { /** * 账本初始化种子 diff --git a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/HttpBlockchainQueryService.java b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/HttpBlockchainQueryService.java index 838ad829..ccd5ceec 100644 --- a/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/HttpBlockchainQueryService.java +++ b/source/sdk/sdk-base/src/main/java/com/jd/blockchain/sdk/proxy/HttpBlockchainQueryService.java @@ -8,6 +8,8 @@ import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.utils.http.*; import com.jd.blockchain.utils.web.client.WebResponseConverterFactory; import com.jd.blockchain.sdk.converters.HashDigestToStringConverter; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; /** * 作为内部使用的适配接口,用于声明 HTTP 协议的服务请求; @@ -194,6 +196,17 @@ public interface HttpBlockchainQueryService extends BlockchainExtendQueryService @Override long getAdditionalContractCount(@PathParam(name="ledgerHash", converter=HashDigestToStringConverter.class) HashDigest ledgerHash); + + /** + * 获取账本信息; + * + * @param ledgerHash + * @return 账本对象;如果不存在,则返回 null; + */ + @HttpAction(method=HttpMethod.GET, path="ledgers/{ledgerHash}/admininfo") + @Override + LedgerAdminInfo getLedgerAdminInfo(@PathParam(name="ledgerHash", converter=HashDigestToStringConverter.class) HashDigest ledgerHash); + /** * 返回指定账本的参与列表 * @@ -204,7 +217,6 @@ public interface HttpBlockchainQueryService extends BlockchainExtendQueryService @Override ParticipantNode[] getConsensusParticipants(@PathParam(name="ledgerHash", converter=HashDigestToStringConverter.class) HashDigest ledgerHash); - /** * 返回指定账本的元数据 * From f1cb118dc5b953e0212b6e2c3a395ec87aed6d34 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Mon, 16 Sep 2019 17:52:11 +0800 Subject: [PATCH 109/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9LedgerAdminInfoData?= =?UTF-8?q?=E5=8C=85=E8=A3=85=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ledger/core/LedgerAdminInfoData.java | 116 ++---------------- .../ledger/core/LedgerRepositoryImpl.java | 8 +- 2 files changed, 16 insertions(+), 108 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java index 6c2660fa..06837128 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java @@ -1,19 +1,6 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.binaryproto.BinaryProtocol; -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.crypto.Crypto; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.crypto.HashFunction; -import com.jd.blockchain.ledger.LedgerMetadata; import com.jd.blockchain.ledger.*; -import com.jd.blockchain.storage.service.ExPolicyKVStorage; -import com.jd.blockchain.storage.service.VersioningKVStorage; -import com.jd.blockchain.utils.Bytes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static com.jd.blockchain.ledger.core.LedgerAdminDataset.*; /** * @author shaozhuguang @@ -23,100 +10,15 @@ import static com.jd.blockchain.ledger.core.LedgerAdminDataset.*; */ public class LedgerAdminInfoData implements LedgerAdminInfo { - static { - DataContractRegistry.register(LedgerMetadata.class); - DataContractRegistry.register(LedgerMetadata_V2.class); - } - - private static Logger LOGGER = LoggerFactory.getLogger(LedgerAdminInfoData.class); - - private final Bytes metaPrefix; - - private final Bytes settingPrefix; - - private LedgerMetadata_V2 origMetadata; - - private LedgerAdminDataset.LedgerMetadataInfo metadata; - /** - * 原来的账本设置; + * 包装类型 + * 将LedgerAdminInfo重新封装,用于页面显示 * - *
        - * 对 LedgerMetadata 修改的新配置不能立即生效,需要达成共识后,在下一次区块计算中才生效; */ - private LedgerSettings previousSettings; - - /** - * 账本的参与节点; - */ - private ParticipantDataset participants; - - /** - * 账本参数配置; - */ - private LedgerSettings settings; - - private ExPolicyKVStorage storage; - - public LedgerAdminInfoData(HashDigest adminAccountHash, String keyPrefix, ExPolicyKVStorage kvStorage, - VersioningKVStorage versioningKVStorage, boolean readonly) { - - this.metaPrefix = Bytes.fromString(keyPrefix + LEDGER_META_PREFIX); - this.settingPrefix = Bytes.fromString(keyPrefix + LEDGER_SETTING_PREFIX); - this.storage = kvStorage; - this.origMetadata = loadAndVerifyMetadata(adminAccountHash); - this.metadata = new LedgerMetadataInfo(origMetadata); - this.settings = loadAndVerifySettings(metadata.getSettingsHash()); - // 复制记录一份配置作为上一个区块的原始配置,该实例仅供读取,不做修改,也不会回写到存储; - this.previousSettings = new LedgerConfiguration(settings); - - String partiPrefix = keyPrefix + LEDGER_PARTICIPANT_PREFIX; - this.participants = new ParticipantDataset(metadata.getParticipantsHash(), previousSettings.getCryptoSetting(), - partiPrefix, kvStorage, versioningKVStorage, readonly); - } - - private LedgerMetadata_V2 loadAndVerifyMetadata(HashDigest adminAccountHash) { - Bytes key = encodeMetadataKey(adminAccountHash); - byte[] bytes = storage.get(key); - HashFunction hashFunc = Crypto.getHashFunction(adminAccountHash.getAlgorithm()); - if (!hashFunc.verify(adminAccountHash, bytes)) { - String errorMsg = "Verification of the hash for ledger metadata failed! --[HASH=" + key + "]"; - LOGGER.error(errorMsg); - throw new LedgerException(errorMsg); - } - return deserializeMetadata(bytes); - } - - - private LedgerSettings loadAndVerifySettings(HashDigest settingsHash) { - if (settingsHash == null) { - return null; - } - Bytes key = encodeSettingsKey(settingsHash); - byte[] bytes = storage.get(key); - HashFunction hashFunc = Crypto.getHashFunction(settingsHash.getAlgorithm()); - if (!hashFunc.verify(settingsHash, bytes)) { - String errorMsg = "Verification of the hash for ledger setting failed! --[HASH=" + key + "]"; - LOGGER.error(errorMsg); - throw new LedgerException(errorMsg); - } - return deserializeSettings(bytes); - } - - private Bytes encodeMetadataKey(HashDigest metadataHash) { - return metaPrefix.concat(metadataHash); - } - - private LedgerMetadata_V2 deserializeMetadata(byte[] bytes) { - return BinaryProtocol.decode(bytes); - } - - private LedgerSettings deserializeSettings(byte[] bytes) { - return BinaryProtocol.decode(bytes); - } + private LedgerAdminInfo ledgerAdminInfo; - private Bytes encodeSettingsKey(HashDigest settingsHash) { - return settingPrefix.concat(settingsHash); + public LedgerAdminInfoData(LedgerAdminInfo ledgerAdminInfo) { + this.ledgerAdminInfo = ledgerAdminInfo; } /** @@ -126,7 +28,7 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public LedgerMetadata_V2 getMetadata() { - return metadata; + return ledgerAdminInfo.getMetadata(); } /** @@ -136,7 +38,7 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public LedgerSettings getSettings() { - return settings; + return ledgerAdminInfo.getSettings(); } /** @@ -146,7 +48,7 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public long getParticipantCount() { - return participants.getParticipantCount(); + return ledgerAdminInfo.getParticipantCount(); } /** @@ -156,6 +58,6 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public ParticipantNode[] getParticipants() { - return participants.getParticipants(); + return ledgerAdminInfo.getParticipants(); } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 90890e4e..5a35488a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -293,9 +293,15 @@ class LedgerRepositoryImpl implements LedgerRepository { * @return */ private LedgerAdminInfoData createAdminData(LedgerBlock block) { - return new LedgerAdminInfoData(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); + return new LedgerAdminInfoData(createAdminDataset(block)); } + /** + * 生成LedgerAdminDataset对象 + * + * @param block + * @return + */ private LedgerAdminDataset createAdminDataset(LedgerBlock block) { return new LedgerAdminDataset(block.getAdminAccountHash(), keyPrefix, exPolicyStorage, versioningStorage, true); } From c849affc4f7ca866f17992e58b0297de30caf481 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Mon, 16 Sep 2019 18:26:55 +0800 Subject: [PATCH 110/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9Gateway=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=8F=8ALedgerSettings=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/boot/GatewayBooter.java | 1 - .../gateway/GatewayServerBooter.java | 33 +++++++++++++++++-- .../peer/web/ManagementController.java | 3 +- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java b/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java index f6c637ad..4c9d5537 100644 --- a/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java +++ b/source/deployment/deployment-gateway/src/main/java/com/jd/blockchain/gateway/boot/GatewayBooter.java @@ -18,7 +18,6 @@ public class GatewayBooter { writePID(); GatewayServerBooter.main(args); } catch (Exception e) { - e.printStackTrace(); System.err.println("Error!!! --[" + e.getClass().getName() + "] " + e.getMessage()); } } diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java index 76c39b61..bdc8e881 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/GatewayServerBooter.java @@ -2,6 +2,7 @@ package com.jd.blockchain.gateway; import java.io.File; import java.io.InputStream; +import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -23,10 +24,14 @@ import com.jd.blockchain.utils.ConsoleUtils; public class GatewayServerBooter { + private static final String DEFAULT_GATEWAY_PROPS = "application-gw.properties"; + // 当前参与方在初始化配置中的参与方列表的编号; private static final String HOST_ARG = "-c"; + //sp;针对spring.config.location这个参数进行包装; private static final String SPRING_CF_LOCATION = BaseConstant.SPRING_CF_LOCATION; + // 是否输出调试信息; private static final String DEBUG_OPT = "-debug"; @@ -57,11 +62,20 @@ public class GatewayServerBooter { }else { //if no the config file, then should tip as follows. but it's not a good feeling, so we create it by inputStream; ConsoleUtils.info("no param:-sp, format: -sp /x/xx.properties, use the default application-gw.properties "); - ClassPathResource configResource = new ClassPathResource("application-gw.properties"); + ClassPathResource configResource = new ClassPathResource(DEFAULT_GATEWAY_PROPS); InputStream in = configResource.getInputStream(); - File targetFile = new File(System.getProperty("user.dir")+File.separator+"conf"+File.separator+"application-gw.properties"); + + // 将文件写入至config目录下 + String configPath = bootPath() + "config" + File.separator + DEFAULT_GATEWAY_PROPS; + File targetFile = new File(configPath); + + // 先将原来文件删除再Copy + if (targetFile.exists()) { + FileUtils.forceDelete(targetFile); + } + FileUtils.copyInputStreamToFile(in, targetFile); - springConfigLocation = "file:"+targetFile.getAbsolutePath(); + springConfigLocation = "file:" + targetFile.getAbsolutePath(); } // 启动服务器; @@ -147,4 +161,17 @@ public class GatewayServerBooter { return appCtx; } + private static String bootPath() throws Exception { + URL url = GatewayServerBooter.class.getProtectionDomain().getCodeSource().getLocation(); + String currPath = java.net.URLDecoder.decode(url.getPath(), "UTF-8"); + // 处理打包至SpringBoot问题 + if (currPath.contains("!/")) { + currPath = currPath.substring(5, currPath.indexOf("!/")); + } + if (currPath.endsWith(".jar")) { + currPath = currPath.substring(0, currPath.lastIndexOf("/") + 1); + } + System.out.printf("Current Project Boot Path = %s \r\n", currPath); + return new File(currPath).getParent() + File.separator; + } } \ No newline at end of file diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index cf4b8bac..9e771a33 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -111,7 +111,8 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(BftsmartConsensusSettings.class); DataContractRegistry.register(BftsmartNodeSettings.class); -// DataContractRegistry.register(LedgerAdminDataQuery.class); + DataContractRegistry.register(LedgerAdminInfo.class); + DataContractRegistry.register(LedgerSettings.class); } From ae87c7a35f93efec77b42b679b8fef677c6fc2e3 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 17 Sep 2019 10:38:42 +0800 Subject: [PATCH 111/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9LedgerAdminInfoData?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ledger/core/LedgerAdminInfoData.java | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java index 06837128..a0a74190 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminInfoData.java @@ -11,14 +11,43 @@ import com.jd.blockchain.ledger.*; public class LedgerAdminInfoData implements LedgerAdminInfo { /** - * 包装类型 - * 将LedgerAdminInfo重新封装,用于页面显示 + * 元数据 + */ + private LedgerMetadata_V2 metadata; + + /** + * 账本配置 + * + */ + private LedgerSettings ledgerSettings; + + /** + * 参与方数量 + * + */ + private long participantCount; + + /** + * 参与方 * */ - private LedgerAdminInfo ledgerAdminInfo; + private ParticipantNode[] participantNodes; + /** + * 包装构造方法 + * + * @param ledgerAdminInfo + */ public LedgerAdminInfoData(LedgerAdminInfo ledgerAdminInfo) { - this.ledgerAdminInfo = ledgerAdminInfo; + this(ledgerAdminInfo.getMetadata(), ledgerAdminInfo.getSettings(), + ledgerAdminInfo.getParticipantCount(), ledgerAdminInfo.getParticipants()); + } + + public LedgerAdminInfoData(LedgerMetadata_V2 metadata, LedgerSettings ledgerSettings, long participantCount, ParticipantNode[] participantNodes) { + this.metadata = metadata; + this.ledgerSettings = ledgerSettings; + this.participantCount = participantCount; + this.participantNodes = participantNodes; } /** @@ -28,7 +57,7 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public LedgerMetadata_V2 getMetadata() { - return ledgerAdminInfo.getMetadata(); + return this.metadata; } /** @@ -38,7 +67,7 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public LedgerSettings getSettings() { - return ledgerAdminInfo.getSettings(); + return this.ledgerSettings; } /** @@ -48,7 +77,7 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public long getParticipantCount() { - return ledgerAdminInfo.getParticipantCount(); + return this.participantCount; } /** @@ -58,6 +87,6 @@ public class LedgerAdminInfoData implements LedgerAdminInfo { */ @Override public ParticipantNode[] getParticipants() { - return ledgerAdminInfo.getParticipants(); + return this.participantNodes; } } From 5f0a7ba0941a45365deccd522a6149d448ee5bf0 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 17 Sep 2019 15:05:51 +0800 Subject: [PATCH 112/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=9C=AA=E6=B3=A8=E5=86=8C=E7=9A=84=E5=BA=8F=E5=88=97=E5=8C=96?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/jd/blockchain/ledger/RoleInitData.java | 6 ++++++ .../java/com/jd/blockchain/ledger/UserAuthInitData.java | 5 +++++ .../src/main/java/com/jd/blockchain/ledger/UserRoles.java | 5 +++++ .../java/com/jd/blockchain/transaction/LedgerInitData.java | 6 ++++++ .../com/jd/blockchain/peer/web/ManagementController.java | 4 ++++ 5 files changed, 26 insertions(+) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java index ea1822e2..090b19f8 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/RoleInitData.java @@ -1,7 +1,13 @@ package com.jd.blockchain.ledger; +import com.jd.blockchain.binaryproto.DataContractRegistry; + public class RoleInitData implements RoleInitSettings { + static { + DataContractRegistry.register(RoleInitSettings.class); + } + private String roleName; private LedgerPermission[] ledgerPermissions; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java index 6866c991..81dd5587 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthInitData.java @@ -1,9 +1,14 @@ package com.jd.blockchain.ledger; +import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.utils.Bytes; public class UserAuthInitData implements UserAuthInitSettings { + static { + DataContractRegistry.register(UserAuthInitSettings.class); + } + private Bytes userAddress; private String[] roles; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java index 2cae56d1..19f86ed8 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRoles.java @@ -5,10 +5,15 @@ import java.util.Collections; import java.util.Set; import java.util.TreeSet; +import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.utils.Bytes; public class UserRoles implements RoleSet { + static { + DataContractRegistry.register(RoleSet.class); + } + private Bytes userAddress; private RolesPolicy policy; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java index 6de96680..656b4aa3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/LedgerInitData.java @@ -1,12 +1,18 @@ package com.jd.blockchain.transaction; +import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerInitSetting; import com.jd.blockchain.ledger.ParticipantNode; +import com.jd.blockchain.ledger.UserAuthInitSettings; import com.jd.blockchain.utils.Bytes; public class LedgerInitData implements LedgerInitSetting { + static { + DataContractRegistry.register(LedgerInitSetting.class); + } + private byte[] ledgerSeed; private ParticipantNode[] consensusParticipants; diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index 9e771a33..fde7b1d4 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -113,6 +113,10 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(LedgerAdminInfo.class); DataContractRegistry.register(LedgerSettings.class); + DataContractRegistry.register(RoleSet.class); + DataContractRegistry.register(SecurityInitSettings.class); + DataContractRegistry.register(RoleInitSettings.class); + DataContractRegistry.register(UserAuthInitSettings.class); } From 290af1b9b769a179b7713d7315cd8a682e7a835f Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 17 Sep 2019 16:23:01 +0800 Subject: [PATCH 113/124] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8C=96=E6=B3=A8=E5=86=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jd/blockchain/ledger/SecurityInitData.java | 6 ++++++ .../jd/blockchain/transaction/RolesConfigureOpTemplate.java | 1 + .../jd/blockchain/transaction/UserAuthorizeOpTemplate.java | 1 + .../com/jd/blockchain/peer/web/ManagementController.java | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java index 626adef1..90f51869 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/SecurityInitData.java @@ -3,10 +3,16 @@ package com.jd.blockchain.ledger; import java.util.LinkedHashMap; import java.util.Map; +import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.utils.Bytes; public class SecurityInitData implements SecurityInitSettings { + static { + DataContractRegistry.register(SecurityInitSettings.class); + } + + private Map roles = new LinkedHashMap<>(); private Map userAuthentications = new LinkedHashMap<>(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java index ab8d19dc..ba5fc4e5 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/RolesConfigureOpTemplate.java @@ -20,6 +20,7 @@ public class RolesConfigureOpTemplate implements RolesConfigurer, RolesConfigure static { DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(RolesConfigureOperation.class); + DataContractRegistry.register(RolePrivilegeEntry.class); } private Map rolesMap = Collections diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java index ed851763..2575fdd5 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java @@ -19,6 +19,7 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe static { DataContractRegistry.register(UserRegisterOperation.class); DataContractRegistry.register(UserAuthorizeOperation.class); + DataContractRegistry.register(UserRolesEntry.class); } private Set userAuthMap = Collections diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index fde7b1d4..791ab862 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -118,6 +118,11 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(RoleInitSettings.class); DataContractRegistry.register(UserAuthInitSettings.class); + DataContractRegistry.register(TransactionPermission.class); + DataContractRegistry.register(LedgerPermission.class); + DataContractRegistry.register(RolesPolicy.class); + DataContractRegistry.register(PrivilegeSet.class); + } /** From 3255597b541afbb92c6d31104a26bb20820c3c89 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Tue, 17 Sep 2019 18:08:18 +0800 Subject: [PATCH 114/124] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=88=E7=BA=A6?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=96=87=E4=BB=B6=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jd/blockchain/contract/ContractJarUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java index d27323f2..775d0ec5 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java @@ -17,9 +17,9 @@ import java.util.jar.JarOutputStream; public class ContractJarUtils { - public static final String BLACK_CONF = "black.conf"; + public static final String BLACK_CONF = "blacks.conf"; - public static final String WHITE_CONF = "white.conf"; + public static final String WHITE_CONF = "whites.conf"; private static final String CONTRACT_MF = "META-INF/CONTRACT.MF"; From 87dd1b63cb3f5d337507b3a9c33f4a0155037e50 Mon Sep 17 00:00:00 2001 From: zhaoguangwei Date: Tue, 17 Sep 2019 18:41:21 +0800 Subject: [PATCH 115/124] delete the register of TransactionPermission/LedgerPermission/RolesPolicy, because it's enum type. --- .../peer/web/ManagementController.java | 554 +++++++++--------- 1 file changed, 275 insertions(+), 279 deletions(-) diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index 791ab862..ced6284c 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -1,279 +1,275 @@ -package com.jd.blockchain.peer.web; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.jd.blockchain.ledger.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import com.jd.blockchain.binaryproto.DataContractRegistry; -import com.jd.blockchain.consensus.ClientIdentification; -import com.jd.blockchain.consensus.ClientIdentifications; -import com.jd.blockchain.consensus.ClientIncomingSettings; -import com.jd.blockchain.consensus.ConsensusProvider; -import com.jd.blockchain.consensus.ConsensusProviders; -import com.jd.blockchain.consensus.ConsensusSettings; -import com.jd.blockchain.consensus.NodeSettings; -import com.jd.blockchain.consensus.action.ActionResponse; -import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; -import com.jd.blockchain.consensus.bftsmart.BftsmartNodeSettings; -import com.jd.blockchain.consensus.mq.server.MsgQueueMessageDispatcher; -import com.jd.blockchain.consensus.service.MessageHandle; -import com.jd.blockchain.consensus.service.NodeServer; -import com.jd.blockchain.consensus.service.ServerSettings; -import com.jd.blockchain.consensus.service.StateMachineReplicate; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.LedgerAdminInfo; -import com.jd.blockchain.ledger.core.LedgerAdminDataQuery; -import com.jd.blockchain.ledger.core.LedgerManage; -import com.jd.blockchain.ledger.core.LedgerQuery; -import com.jd.blockchain.peer.ConsensusRealm; -import com.jd.blockchain.peer.LedgerBindingConfigAware; -import com.jd.blockchain.peer.PeerManage; -import com.jd.blockchain.setting.GatewayIncomingSetting; -import com.jd.blockchain.setting.LedgerIncomingSetting; -import com.jd.blockchain.storage.service.DbConnection; -import com.jd.blockchain.storage.service.DbConnectionFactory; -import com.jd.blockchain.tools.initializer.LedgerBindingConfig; -import com.jd.blockchain.utils.Bytes; -import com.jd.blockchain.utils.io.ByteArray; -import com.jd.blockchain.web.converters.BinaryMessageConverter; - -/** - * 网关管理服务; - * - * 提供 - * - * @author huanghaiquan - * - */ -@RestController -@RequestMapping(path = "/management") -public class ManagementController implements LedgerBindingConfigAware, PeerManage { - - private static Logger LOGGER = LoggerFactory.getLogger(ManagementController.class); - - public static final String GATEWAY_PUB_EXT_NAME = ".gw.pub"; - - public static final int MIN_GATEWAY_ID = 10000; - - @Autowired - private LedgerManage ledgerManager; - - @Autowired - private DbConnectionFactory connFactory; - - private Map ledgerTxConverters = new ConcurrentHashMap<>(); - - private Map ledgerPeers = new ConcurrentHashMap<>(); - private Map ledgerCryptoSettings = new ConcurrentHashMap<>(); - - - private LedgerBindingConfig config; - - @Autowired - private MessageHandle consensusMessageHandler; - - @Autowired - private StateMachineReplicate consensusStateManager; - - static { - DataContractRegistry.register(LedgerInitOperation.class); - DataContractRegistry.register(LedgerBlock.class); - DataContractRegistry.register(TransactionContent.class); - DataContractRegistry.register(TransactionContentBody.class); - DataContractRegistry.register(TransactionRequest.class); - DataContractRegistry.register(NodeRequest.class); - DataContractRegistry.register(EndpointRequest.class); - DataContractRegistry.register(TransactionResponse.class); - DataContractRegistry.register(DataAccountKVSetOperation.class); - DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); - - DataContractRegistry.register(Operation.class); - DataContractRegistry.register(ContractCodeDeployOperation.class); - DataContractRegistry.register(ContractEventSendOperation.class); - DataContractRegistry.register(DataAccountRegisterOperation.class); - DataContractRegistry.register(UserRegisterOperation.class); - DataContractRegistry.register(ParticipantRegisterOperation.class); - DataContractRegistry.register(ParticipantStateUpdateOperation.class); - - DataContractRegistry.register(ActionResponse.class); - - DataContractRegistry.register(BftsmartConsensusSettings.class); - DataContractRegistry.register(BftsmartNodeSettings.class); - - DataContractRegistry.register(LedgerAdminInfo.class); - DataContractRegistry.register(LedgerSettings.class); - DataContractRegistry.register(RoleSet.class); - DataContractRegistry.register(SecurityInitSettings.class); - DataContractRegistry.register(RoleInitSettings.class); - DataContractRegistry.register(UserAuthInitSettings.class); - - DataContractRegistry.register(TransactionPermission.class); - DataContractRegistry.register(LedgerPermission.class); - DataContractRegistry.register(RolesPolicy.class); - DataContractRegistry.register(PrivilegeSet.class); - - } - - /** - * 接入认证; - * - * @param clientIdentifications - * @return - */ - @RequestMapping(path = "/gateway/auth", method = RequestMethod.POST, consumes = BinaryMessageConverter.CONTENT_TYPE_VALUE) - public GatewayIncomingSetting authenticateGateway(@RequestBody ClientIdentifications clientIdentifications) { - // 去掉不严谨的网关注册和认证逻辑;暂时先放开,不做认证,后续应该在链上注册网关信息,并基于链上的网关信息进行认证; - // by: huanghaiquan; at 2018-09-11 18:34; - // TODO: 实现网关的链上注册与认证机制; - // TODO: 暂时先返回全部账本对应的共识网络配置信息;以账本哈希为 key 标识每一个账本对应的共识域、以及共识配置参数; - if (ledgerPeers.size() == 0 || clientIdentifications == null) { - return null; - } - - ClientIdentification[] identificationArray = clientIdentifications.getClientIdentifications(); - if (identificationArray == null || identificationArray.length <= 0) { - return null; - } - - GatewayIncomingSetting setting = new GatewayIncomingSetting(); - List ledgerIncomingList = new ArrayList(); - - for (HashDigest ledgerHash : ledgerPeers.keySet()) { - - NodeServer peer = ledgerPeers.get(ledgerHash); - - String peerProviderName = peer.getProviderName(); - - ConsensusProvider provider = ConsensusProviders.getProvider(peer.getProviderName()); - - ClientIncomingSettings clientIncomingSettings = null; - for (ClientIdentification authId : identificationArray) { - if (authId.getProviderName() == null || - authId.getProviderName().length() <= 0 || - !authId.getProviderName().equalsIgnoreCase(peerProviderName)) { - continue; - } - try { - clientIncomingSettings = peer.getManageService().authClientIncoming(authId); - break; - } catch (Exception e) { - throw new AuthenticationServiceException(e.getMessage(), e); - } - } - if (clientIncomingSettings == null) { - continue; - } - - byte[] clientIncomingBytes = provider.getSettingsFactory().getIncomingSettingsEncoder() - .encode(clientIncomingSettings); - String base64ClientIncomingSettings = ByteArray.toBase64(clientIncomingBytes); - - LedgerIncomingSetting ledgerIncomingSetting = new LedgerIncomingSetting(); - ledgerIncomingSetting.setLedgerHash(ledgerHash); - ledgerIncomingSetting.setCryptoSetting(ledgerCryptoSettings.get(ledgerHash)); - ledgerIncomingSetting.setClientSetting(base64ClientIncomingSettings); - ledgerIncomingSetting.setProviderName(peerProviderName); - - ledgerIncomingList.add(ledgerIncomingSetting); - - } - setting.setLedgers(ledgerIncomingList.toArray(new LedgerIncomingSetting[ledgerIncomingList.size()])); - return setting; - } - - @Override - public void setConfig(LedgerBindingConfig config) { - // TODO 更新配置;暂时不考虑变化过程的平滑切换问题,后续完善该流程; - // 1、检查账本的数据库配置;a、配置发生变化的账本,建立新的账本库(LedgerRepository)替换旧的实例;b、加入新增加的账本库实例;c、移除已经废弃的账本库; - // 2、完成账本库更改后,读取最新的共识配置信息,更新共识域; - // 3、基于当前共识地址检查共识域;a、启动新增加的共识地址,以及更新相应的共识域关系;c、已经废弃的共识域直接停止; - try { - // remove all existing ledger repositories; - HashDigest[] existingLedgerHashs = ledgerManager.getLedgerHashs(); - for (HashDigest lh : existingLedgerHashs) { - ledgerManager.unregister(lh); - } - HashDigest[] ledgerHashs = config.getLedgerHashs(); - for (HashDigest ledgerHash : ledgerHashs) { - setConfig(config,ledgerHash); - } - - this.config = config; - - } catch (Exception e) { - LOGGER.error("Error occurred on configing LedgerBindingConfig! --" + e.getMessage(), e); - throw new IllegalStateException(e); - } - } - - @Override - public NodeServer setConfig(LedgerBindingConfig config, HashDigest ledgerHash) { - LedgerBindingConfig.BindingConfig bindingConfig = config.getLedger(ledgerHash); - DbConnection dbConnNew = connFactory.connect(bindingConfig.getDbConnection().getUri(), - bindingConfig.getDbConnection().getPassword()); - LedgerQuery ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); - - // load provider; - LedgerAdminInfo ledgerAdminAccount = ledgerRepository.getAdminInfo(); - String consensusProvider = ledgerAdminAccount.getSettings().getConsensusProvider(); - ConsensusProvider provider = ConsensusProviders.getProvider(consensusProvider); - // find current node; - Bytes csSettingBytes = ledgerAdminAccount.getSettings().getConsensusSetting(); - ConsensusSettings csSettings = provider.getSettingsFactory().getConsensusSettingsEncoder() - .decode(csSettingBytes.toBytes()); - NodeSettings currentNode = null; - for (NodeSettings nodeSettings : csSettings.getNodes()) { - if (nodeSettings.getAddress().equals(bindingConfig.getParticipant().getAddress())) { - currentNode = nodeSettings; - } - } - if (currentNode == null) { - throw new IllegalArgumentException( - "Current node is not found from the consensus settings of ledger[" + ledgerHash.toBase58() - + "]!"); - } - ServerSettings serverSettings = provider.getServerFactory().buildServerSettings(ledgerHash.toBase58(), csSettings, currentNode.getAddress()); - - NodeServer server = provider.getServerFactory().setupServer(serverSettings, consensusMessageHandler, - consensusStateManager); - ledgerPeers.put(ledgerHash, server); - ledgerCryptoSettings.put(ledgerHash, ledgerAdminAccount.getSettings().getCryptoSetting()); - - return server; - } - - @Override - public ConsensusRealm[] getRealms() { - throw new IllegalStateException("Not implemented!"); - } - - @Override - public void runAllRealms() { - for (NodeServer peer : ledgerPeers.values()) { - runRealm(peer); - } - } - - @Override - public void runRealm(NodeServer nodeServer) { - nodeServer.start(); - } - - @Override - public void closeAllRealms() { - for (NodeServer peer : ledgerPeers.values()) { - peer.stop(); - } - } -} +package com.jd.blockchain.peer.web; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.jd.blockchain.ledger.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.consensus.ClientIdentification; +import com.jd.blockchain.consensus.ClientIdentifications; +import com.jd.blockchain.consensus.ClientIncomingSettings; +import com.jd.blockchain.consensus.ConsensusProvider; +import com.jd.blockchain.consensus.ConsensusProviders; +import com.jd.blockchain.consensus.ConsensusSettings; +import com.jd.blockchain.consensus.NodeSettings; +import com.jd.blockchain.consensus.action.ActionResponse; +import com.jd.blockchain.consensus.bftsmart.BftsmartConsensusSettings; +import com.jd.blockchain.consensus.bftsmart.BftsmartNodeSettings; +import com.jd.blockchain.consensus.mq.server.MsgQueueMessageDispatcher; +import com.jd.blockchain.consensus.service.MessageHandle; +import com.jd.blockchain.consensus.service.NodeServer; +import com.jd.blockchain.consensus.service.ServerSettings; +import com.jd.blockchain.consensus.service.StateMachineReplicate; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.LedgerAdminInfo; +import com.jd.blockchain.ledger.core.LedgerAdminDataQuery; +import com.jd.blockchain.ledger.core.LedgerManage; +import com.jd.blockchain.ledger.core.LedgerQuery; +import com.jd.blockchain.peer.ConsensusRealm; +import com.jd.blockchain.peer.LedgerBindingConfigAware; +import com.jd.blockchain.peer.PeerManage; +import com.jd.blockchain.setting.GatewayIncomingSetting; +import com.jd.blockchain.setting.LedgerIncomingSetting; +import com.jd.blockchain.storage.service.DbConnection; +import com.jd.blockchain.storage.service.DbConnectionFactory; +import com.jd.blockchain.tools.initializer.LedgerBindingConfig; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.io.ByteArray; +import com.jd.blockchain.web.converters.BinaryMessageConverter; + +/** + * 网关管理服务; + * + * 提供 + * + * @author huanghaiquan + * + */ +@RestController +@RequestMapping(path = "/management") +public class ManagementController implements LedgerBindingConfigAware, PeerManage { + + private static Logger LOGGER = LoggerFactory.getLogger(ManagementController.class); + + public static final String GATEWAY_PUB_EXT_NAME = ".gw.pub"; + + public static final int MIN_GATEWAY_ID = 10000; + + @Autowired + private LedgerManage ledgerManager; + + @Autowired + private DbConnectionFactory connFactory; + + private Map ledgerTxConverters = new ConcurrentHashMap<>(); + + private Map ledgerPeers = new ConcurrentHashMap<>(); + private Map ledgerCryptoSettings = new ConcurrentHashMap<>(); + + + private LedgerBindingConfig config; + + @Autowired + private MessageHandle consensusMessageHandler; + + @Autowired + private StateMachineReplicate consensusStateManager; + + static { + DataContractRegistry.register(LedgerInitOperation.class); + DataContractRegistry.register(LedgerBlock.class); + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); + DataContractRegistry.register(DataAccountKVSetOperation.class); + DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); + + DataContractRegistry.register(Operation.class); + DataContractRegistry.register(ContractCodeDeployOperation.class); + DataContractRegistry.register(ContractEventSendOperation.class); + DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(UserRegisterOperation.class); + DataContractRegistry.register(ParticipantRegisterOperation.class); + DataContractRegistry.register(ParticipantStateUpdateOperation.class); + + DataContractRegistry.register(ActionResponse.class); + + DataContractRegistry.register(BftsmartConsensusSettings.class); + DataContractRegistry.register(BftsmartNodeSettings.class); + + DataContractRegistry.register(LedgerAdminInfo.class); + DataContractRegistry.register(LedgerSettings.class); + DataContractRegistry.register(RoleSet.class); + DataContractRegistry.register(SecurityInitSettings.class); + DataContractRegistry.register(RoleInitSettings.class); + DataContractRegistry.register(UserAuthInitSettings.class); + DataContractRegistry.register(PrivilegeSet.class); + + } + + /** + * 接入认证; + * + * @param clientIdentifications + * @return + */ + @RequestMapping(path = "/gateway/auth", method = RequestMethod.POST, consumes = BinaryMessageConverter.CONTENT_TYPE_VALUE) + public GatewayIncomingSetting authenticateGateway(@RequestBody ClientIdentifications clientIdentifications) { + // 去掉不严谨的网关注册和认证逻辑;暂时先放开,不做认证,后续应该在链上注册网关信息,并基于链上的网关信息进行认证; + // by: huanghaiquan; at 2018-09-11 18:34; + // TODO: 实现网关的链上注册与认证机制; + // TODO: 暂时先返回全部账本对应的共识网络配置信息;以账本哈希为 key 标识每一个账本对应的共识域、以及共识配置参数; + if (ledgerPeers.size() == 0 || clientIdentifications == null) { + return null; + } + + ClientIdentification[] identificationArray = clientIdentifications.getClientIdentifications(); + if (identificationArray == null || identificationArray.length <= 0) { + return null; + } + + GatewayIncomingSetting setting = new GatewayIncomingSetting(); + List ledgerIncomingList = new ArrayList(); + + for (HashDigest ledgerHash : ledgerPeers.keySet()) { + + NodeServer peer = ledgerPeers.get(ledgerHash); + + String peerProviderName = peer.getProviderName(); + + ConsensusProvider provider = ConsensusProviders.getProvider(peer.getProviderName()); + + ClientIncomingSettings clientIncomingSettings = null; + for (ClientIdentification authId : identificationArray) { + if (authId.getProviderName() == null || + authId.getProviderName().length() <= 0 || + !authId.getProviderName().equalsIgnoreCase(peerProviderName)) { + continue; + } + try { + clientIncomingSettings = peer.getManageService().authClientIncoming(authId); + break; + } catch (Exception e) { + throw new AuthenticationServiceException(e.getMessage(), e); + } + } + if (clientIncomingSettings == null) { + continue; + } + + byte[] clientIncomingBytes = provider.getSettingsFactory().getIncomingSettingsEncoder() + .encode(clientIncomingSettings); + String base64ClientIncomingSettings = ByteArray.toBase64(clientIncomingBytes); + + LedgerIncomingSetting ledgerIncomingSetting = new LedgerIncomingSetting(); + ledgerIncomingSetting.setLedgerHash(ledgerHash); + ledgerIncomingSetting.setCryptoSetting(ledgerCryptoSettings.get(ledgerHash)); + ledgerIncomingSetting.setClientSetting(base64ClientIncomingSettings); + ledgerIncomingSetting.setProviderName(peerProviderName); + + ledgerIncomingList.add(ledgerIncomingSetting); + + } + setting.setLedgers(ledgerIncomingList.toArray(new LedgerIncomingSetting[ledgerIncomingList.size()])); + return setting; + } + + @Override + public void setConfig(LedgerBindingConfig config) { + // TODO 更新配置;暂时不考虑变化过程的平滑切换问题,后续完善该流程; + // 1、检查账本的数据库配置;a、配置发生变化的账本,建立新的账本库(LedgerRepository)替换旧的实例;b、加入新增加的账本库实例;c、移除已经废弃的账本库; + // 2、完成账本库更改后,读取最新的共识配置信息,更新共识域; + // 3、基于当前共识地址检查共识域;a、启动新增加的共识地址,以及更新相应的共识域关系;c、已经废弃的共识域直接停止; + try { + // remove all existing ledger repositories; + HashDigest[] existingLedgerHashs = ledgerManager.getLedgerHashs(); + for (HashDigest lh : existingLedgerHashs) { + ledgerManager.unregister(lh); + } + HashDigest[] ledgerHashs = config.getLedgerHashs(); + for (HashDigest ledgerHash : ledgerHashs) { + setConfig(config,ledgerHash); + } + + this.config = config; + + } catch (Exception e) { + LOGGER.error("Error occurred on configing LedgerBindingConfig! --" + e.getMessage(), e); + throw new IllegalStateException(e); + } + } + + @Override + public NodeServer setConfig(LedgerBindingConfig config, HashDigest ledgerHash) { + LedgerBindingConfig.BindingConfig bindingConfig = config.getLedger(ledgerHash); + DbConnection dbConnNew = connFactory.connect(bindingConfig.getDbConnection().getUri(), + bindingConfig.getDbConnection().getPassword()); + LedgerQuery ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); + + // load provider; + LedgerAdminInfo ledgerAdminAccount = ledgerRepository.getAdminInfo(); + String consensusProvider = ledgerAdminAccount.getSettings().getConsensusProvider(); + ConsensusProvider provider = ConsensusProviders.getProvider(consensusProvider); + // find current node; + Bytes csSettingBytes = ledgerAdminAccount.getSettings().getConsensusSetting(); + ConsensusSettings csSettings = provider.getSettingsFactory().getConsensusSettingsEncoder() + .decode(csSettingBytes.toBytes()); + NodeSettings currentNode = null; + for (NodeSettings nodeSettings : csSettings.getNodes()) { + if (nodeSettings.getAddress().equals(bindingConfig.getParticipant().getAddress())) { + currentNode = nodeSettings; + } + } + if (currentNode == null) { + throw new IllegalArgumentException( + "Current node is not found from the consensus settings of ledger[" + ledgerHash.toBase58() + + "]!"); + } + ServerSettings serverSettings = provider.getServerFactory().buildServerSettings(ledgerHash.toBase58(), csSettings, currentNode.getAddress()); + + NodeServer server = provider.getServerFactory().setupServer(serverSettings, consensusMessageHandler, + consensusStateManager); + ledgerPeers.put(ledgerHash, server); + ledgerCryptoSettings.put(ledgerHash, ledgerAdminAccount.getSettings().getCryptoSetting()); + + return server; + } + + @Override + public ConsensusRealm[] getRealms() { + throw new IllegalStateException("Not implemented!"); + } + + @Override + public void runAllRealms() { + for (NodeServer peer : ledgerPeers.values()) { + runRealm(peer); + } + } + + @Override + public void runRealm(NodeServer nodeServer) { + nodeServer.start(); + } + + @Override + public void closeAllRealms() { + for (NodeServer peer : ledgerPeers.values()) { + peer.stop(); + } + } +} From 981892caf9091708c17bda2d7879352177f8bcfa Mon Sep 17 00:00:00 2001 From: zhangshuang Date: Wed, 18 Sep 2019 10:09:06 +0800 Subject: [PATCH 116/124] distinguish operations for the browser --- .../handles/ParticipantRegisterOperationHandle.java | 4 ++-- .../handles/ParticipantStateUpdateOperationHandle.java | 2 +- .../ledger/ParticipantRegisterOperation.java | 2 +- .../ledger/ParticipantStateUpdateOperation.java | 2 +- .../transaction/ParticipantRegisterOpTemplate.java | 10 +++++----- .../transaction/ParticipantStateUpdateOpTemplate.java | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java index e3e8f6e1..e02c2220 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantRegisterOperationHandle.java @@ -39,7 +39,7 @@ public class ParticipantRegisterOperationHandle extends AbstractLedgerOperationH LedgerAdminDataset adminAccountDataSet = newBlockDataset.getAdminDataset(); - ParticipantInfo participantInfo = new ParticipantInfoData(participantRegOp.getParticipantName(), participantRegOp.getParticipantIdentity().getPubKey(), participantRegOp.getNetworkAddress()); + ParticipantInfo participantInfo = new ParticipantInfoData(participantRegOp.getParticipantName(), participantRegOp.getParticipantRegisterIdentity().getPubKey(), participantRegOp.getNetworkAddress()); ParticipantNode participantNode = new PartNode((int)(adminAccountDataSet.getParticipantCount()), participantInfo.getName(), participantInfo.getPubKey(), ParticipantNodeState.REGISTERED); @@ -47,7 +47,7 @@ public class ParticipantRegisterOperationHandle extends AbstractLedgerOperationH adminAccountDataSet.addParticipant(participantNode); // Build UserRegisterOperation, reg participant as user - UserRegisterOperation userRegOp = new UserRegisterOpTemplate(participantRegOp.getParticipantIdentity()); + UserRegisterOperation userRegOp = new UserRegisterOpTemplate(participantRegOp.getParticipantRegisterIdentity()); handleContext.handle(userRegOp); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java index 799ac36c..094a2e1a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/ParticipantStateUpdateOperationHandle.java @@ -48,7 +48,7 @@ public class ParticipantStateUpdateOperationHandle extends AbstractLedgerOperati ParticipantNode participantNode = null; for(int i = 0; i < participants.length; i++) { - if (stateUpdateOperation.getParticipantIdentity().getPubKey().equals(participants[i].getPubKey())) { + if (stateUpdateOperation.getStateUpdateIdentity().getPubKey().equals(participants[i].getPubKey())) { participantNode = new PartNode(participants[i].getId(), participants[i].getName(), participants[i].getPubKey(), ParticipantNodeState.ACTIVED); break; } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java index a8ccf227..ec946593 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantRegisterOperation.java @@ -13,7 +13,7 @@ public interface ParticipantRegisterOperation extends Operation { String getParticipantName(); @DataField(order = 1, refContract = true) - BlockchainIdentity getParticipantIdentity(); + BlockchainIdentity getParticipantRegisterIdentity(); @DataField(order = 2, primitiveType = PrimitiveType.BYTES) NetworkAddress getNetworkAddress(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java index 46cfea0e..fa2791a7 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/ParticipantStateUpdateOperation.java @@ -10,7 +10,7 @@ import com.jd.blockchain.utils.net.NetworkAddress; public interface ParticipantStateUpdateOperation extends Operation { @DataField(order = 0, refContract = true) - BlockchainIdentity getParticipantIdentity(); + BlockchainIdentity getStateUpdateIdentity(); @DataField(order = 1, primitiveType = PrimitiveType.BYTES) NetworkAddress getNetworkAddress(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java index 2b5c608d..925b8338 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantRegisterOpTemplate.java @@ -14,12 +14,12 @@ public class ParticipantRegisterOpTemplate implements ParticipantRegisterOperati } private String participantName; - private BlockchainIdentity participantPubKey; + private BlockchainIdentity participantRegisterIdentity; private NetworkAddress networkAddress; - public ParticipantRegisterOpTemplate(String participantName, BlockchainIdentity participantPubKey, NetworkAddress networkAddress) { + public ParticipantRegisterOpTemplate(String participantName, BlockchainIdentity participantRegisterIdentity, NetworkAddress networkAddress) { this.participantName = participantName; - this.participantPubKey = participantPubKey; + this.participantRegisterIdentity = participantRegisterIdentity; this.networkAddress = networkAddress; } @@ -30,8 +30,8 @@ public class ParticipantRegisterOpTemplate implements ParticipantRegisterOperati } @Override - public BlockchainIdentity getParticipantIdentity() { - return participantPubKey; + public BlockchainIdentity getParticipantRegisterIdentity() { + return participantRegisterIdentity; } @Override diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java index abb3b28d..ff91eba5 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/ParticipantStateUpdateOpTemplate.java @@ -12,21 +12,21 @@ public class ParticipantStateUpdateOpTemplate implements ParticipantStateUpdateO DataContractRegistry.register(ParticipantStateUpdateOperation.class); } - private BlockchainIdentity blockchainIdentity; + private BlockchainIdentity stateUpdateIdentity; private NetworkAddress networkAddress; private ParticipantNodeState participantNodeState; - public ParticipantStateUpdateOpTemplate(BlockchainIdentity blockchainIdentity, NetworkAddress networkAddress, ParticipantNodeState participantNodeState) { + public ParticipantStateUpdateOpTemplate(BlockchainIdentity stateUpdateIdentity, NetworkAddress networkAddress, ParticipantNodeState participantNodeState) { - this.blockchainIdentity = blockchainIdentity; + this.stateUpdateIdentity = stateUpdateIdentity; this.networkAddress = networkAddress; this.participantNodeState = participantNodeState; } @Override - public BlockchainIdentity getParticipantIdentity() { - return blockchainIdentity; + public BlockchainIdentity getStateUpdateIdentity() { + return stateUpdateIdentity; } @Override From 922fe5449074ea1c041c9a4f71641475915073c2 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 18 Sep 2019 15:35:37 +0800 Subject: [PATCH 117/124] Adding SDK usage examples --- .../sdk/samples/SDKDemo_Constant.java | 38 ++++++++-------- .../sdk/samples/SDK_RoleConfig_Demo.java | 26 +++++++++++ .../sdk/samples/SDK_User2Role_Demo.java | 44 +++++++++++++++++++ 3 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_RoleConfig_Demo.java create mode 100644 source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_User2Role_Demo.java diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java index f0237cec..3ee48e47 100644 --- a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDKDemo_Constant.java @@ -7,37 +7,37 @@ import java.io.File; public class SDKDemo_Constant { -// public static final String GW_IPADDR = "127.0.0.1"; - public static final String GW_IPADDR = "192.168.151.41"; - -// public static final int GW_PORT = 11000; - public static final int GW_PORT = 18081; - -// public static final String[] PUB_KEYS = { -// "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", -// "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", -// "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", -// "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; -// -// public static final String[] PRIV_KEYS = { -// "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", -// "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", -// "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", -// "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; + public static final String GW_IPADDR = "127.0.0.1"; +// public static final String GW_IPADDR = "192.168.151.41"; + public static final int GW_PORT = 11000; +// public static final int GW_PORT = 18081; public static final String[] PUB_KEYS = { - "3snPdw7i7PXvEDgq96QyzcKhfWL4mgYspzKwvgXiuAidWb2rkRMgDY", + "3snPdw7i7PjVKiTH2VnXZu5H8QmNaSXpnk4ei533jFpuifyjS5zzH9", "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; public static final String[] PRIV_KEYS = { - "177gjsxj2ezADGthZ4tGqWeCAqRAwtNvesPjRnyKqCb1huU8LKZmJ3HGZNMPKWQJK3DP1B2", + "177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; + +// public static final String[] PUB_KEYS = { +// "3snPdw7i7PXvEDgq96QyzcKhfWL4mgYspzKwvgXiuAidWb2rkRMgDY", +// "3snPdw7i7PajLB35tEau1kmixc6ZrjLXgxwKbkv5bHhP7nT5dhD9eX", +// "3snPdw7i7PZi6TStiyc6mzjprnNhgs2atSGNS8wPYzhbKaUWGFJt7x", +// "3snPdw7i7PifPuRX7fu3jBjsb3rJRfDe9GtbDfvFJaJ4V4hHXQfhwk"}; +// +// public static final String[] PRIV_KEYS = { +// "177gjsxj2ezADGthZ4tGqWeCAqRAwtNvesPjRnyKqCb1huU8LKZmJ3HGZNMPKWQJK3DP1B2", +// "177gju9p5zrNdHJVEQnEEKF4ZjDDYmAXyfG84V5RPGVc5xFfmtwnHA7j51nyNLUFffzz5UT", +// "177gjtwLgmSx5v1hFb46ijh7L9kdbKUpJYqdKVf9afiEmAuLgo8Rck9yu5UuUcHknWJuWaF", +// "177gk1pudweTq5zgJTh8y3ENCTwtSFsKyX7YnpuKPo7rKgCkCBXVXh5z2syaTCPEMbuWRns"}; + public static final String PASSWORD = "abc"; public static final byte[] readChainCodes(String contractZip) { diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_RoleConfig_Demo.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_RoleConfig_Demo.java new file mode 100644 index 00000000..6b768a82 --- /dev/null +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_RoleConfig_Demo.java @@ -0,0 +1,26 @@ +package com.jd.blockchain.sdk.samples; + +import com.jd.blockchain.ledger.*; + +public class SDK_RoleConfig_Demo extends SDK_Base_Demo { + + public static void main(String[] args) { + new SDK_RoleConfig_Demo().executeRoleConfig(); + } + + public void executeRoleConfig() { + + // 定义交易模板 + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + + // 新增加一个角色 + + txTpl.security().roles().configure("MyRole") + .enable(LedgerPermission.APPROVE_TX, LedgerPermission.CONSENSUS_TX) + .disable(TransactionPermission.CONTRACT_OPERATION); + TransactionResponse txResp = commit(txTpl); + + System.out.println(txResp.isSuccess()); + + } +} diff --git a/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_User2Role_Demo.java b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_User2Role_Demo.java new file mode 100644 index 00000000..5f18f6aa --- /dev/null +++ b/source/sdk/sdk-samples/src/main/java/com/jd/blockchain/sdk/samples/SDK_User2Role_Demo.java @@ -0,0 +1,44 @@ +package com.jd.blockchain.sdk.samples; + +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.utils.Bytes; + +public class SDK_User2Role_Demo extends SDK_Base_Demo { + + public static void main(String[] args) { + new SDK_User2Role_Demo().executeUser2Role(); + } + + public void executeUser2Role() { + + // 定义交易模板 + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + + // 注册一个用户 + BlockchainKeypair user = createUser(); + + Bytes userAddress = user.getAddress(); + // 获取数据账户地址 + System.out.printf("UserAddress = %s \r\n", userAddress.toBase58()); + + + txTpl.security().authorziations().forUser(user.getIdentity()) + .authorize("MYROLE") + .setPolicy(RolesPolicy.UNION) + .unauthorize("MYROLE"); + TransactionResponse txResp = commit(txTpl); + + System.out.println(txResp.isSuccess()); + + } + + private BlockchainKeypair createUser() { + // 首先注册一个数据账户 + BlockchainKeypair newUser = BlockchainKeyGenerator.getInstance().generate(); + + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + txTpl.users().register(newUser.getIdentity()); + commit(txTpl); + return newUser; + } +} From fef6979380fcee606164410a3991b727d388a224 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 18 Sep 2019 16:11:50 +0800 Subject: [PATCH 118/124] SDK-Client Module Increases Serialized Interface Registration --- .../gateway/web/GatewayWebServerConfigurer.java | 13 ++++++++++++- .../sdk/client/GatewayServiceFactory.java | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java index 83268ad8..cfed9081 100644 --- a/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java +++ b/source/gateway/src/main/java/com/jd/blockchain/gateway/web/GatewayWebServerConfigurer.java @@ -35,7 +35,18 @@ public class GatewayWebServerConfigurer implements WebMvcConfigurer { JSONSerializeUtils.disableCircularReferenceDetect(); JSONSerializeUtils.configStringSerializer(ByteArray.class); DataContractRegistry.register(BftsmartNodeSettings.class); -// DataContractRegistry.register(LedgerAdminInfo.class); + + // 注册角色/权限相关接口 + DataContractRegistry.register(RolesConfigureOperation.class); + DataContractRegistry.register(RolesConfigureOperation.RolePrivilegeEntry.class); + DataContractRegistry.register(UserAuthorizeOperation.class); + DataContractRegistry.register(UserAuthorizeOperation.UserRolesEntry.class); + DataContractRegistry.register(PrivilegeSet.class); + DataContractRegistry.register(RoleSet.class); + DataContractRegistry.register(SecurityInitSettings.class); + DataContractRegistry.register(RoleInitSettings.class); + DataContractRegistry.register(UserAuthInitSettings.class); + DataContractRegistry.register(LedgerMetadata_V2.class); } diff --git a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java index 7e0729a7..79a329af 100644 --- a/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java +++ b/source/sdk/sdk-client/src/main/java/com/jd/blockchain/sdk/client/GatewayServiceFactory.java @@ -56,6 +56,18 @@ public class GatewayServiceFactory implements BlockchainServiceFactory, Closeabl DataContractRegistry.register(ClientIdentification.class); DataContractRegistry.register(BytesValueList.class); + // 注册角色/权限相关接口 + DataContractRegistry.register(RolesConfigureOperation.class); + DataContractRegistry.register(RolesConfigureOperation.RolePrivilegeEntry.class); + DataContractRegistry.register(UserAuthorizeOperation.class); + DataContractRegistry.register(UserAuthorizeOperation.UserRolesEntry.class); + DataContractRegistry.register(PrivilegeSet.class); + DataContractRegistry.register(RoleSet.class); + DataContractRegistry.register(SecurityInitSettings.class); + DataContractRegistry.register(RoleInitSettings.class); + DataContractRegistry.register(UserAuthInitSettings.class); + DataContractRegistry.register(LedgerMetadata_V2.class); + ByteArrayObjectUtil.init(); } From eb60792594517b0c01d6c41899eaa58e21f77bcc Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 18 Sep 2019 16:39:07 +0800 Subject: [PATCH 119/124] renamed shell scripts; --- .../main/resources/scripts/{jump-start.sh => manager-start.sh} | 0 .../src/main/resources/scripts/{jump-stop.sh => manager-stop.sh} | 0 .../src/main/resources/scripts/{shutdown.sh => peer-shutdown.sh} | 0 .../src/main/resources/scripts/{startup.sh => peer-startup.sh} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename source/deployment/deployment-peer/src/main/resources/scripts/{jump-start.sh => manager-start.sh} (100%) rename source/deployment/deployment-peer/src/main/resources/scripts/{jump-stop.sh => manager-stop.sh} (100%) rename source/deployment/deployment-peer/src/main/resources/scripts/{shutdown.sh => peer-shutdown.sh} (100%) rename source/deployment/deployment-peer/src/main/resources/scripts/{startup.sh => peer-startup.sh} (100%) diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh b/source/deployment/deployment-peer/src/main/resources/scripts/manager-start.sh similarity index 100% rename from source/deployment/deployment-peer/src/main/resources/scripts/jump-start.sh rename to source/deployment/deployment-peer/src/main/resources/scripts/manager-start.sh diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/jump-stop.sh b/source/deployment/deployment-peer/src/main/resources/scripts/manager-stop.sh similarity index 100% rename from source/deployment/deployment-peer/src/main/resources/scripts/jump-stop.sh rename to source/deployment/deployment-peer/src/main/resources/scripts/manager-stop.sh diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/shutdown.sh b/source/deployment/deployment-peer/src/main/resources/scripts/peer-shutdown.sh similarity index 100% rename from source/deployment/deployment-peer/src/main/resources/scripts/shutdown.sh rename to source/deployment/deployment-peer/src/main/resources/scripts/peer-shutdown.sh diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/startup.sh b/source/deployment/deployment-peer/src/main/resources/scripts/peer-startup.sh similarity index 100% rename from source/deployment/deployment-peer/src/main/resources/scripts/startup.sh rename to source/deployment/deployment-peer/src/main/resources/scripts/peer-startup.sh From 55453d9dbb451edd22444b984b1d68028232a765 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 18 Sep 2019 17:04:12 +0800 Subject: [PATCH 120/124] Delete the -l parameter from the keygen.sh --- .../deployment-peer/src/main/resources/scripts/keygen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/deployment/deployment-peer/src/main/resources/scripts/keygen.sh b/source/deployment/deployment-peer/src/main/resources/scripts/keygen.sh index 0249e85d..f0844f4c 100644 --- a/source/deployment/deployment-peer/src/main/resources/scripts/keygen.sh +++ b/source/deployment/deployment-peer/src/main/resources/scripts/keygen.sh @@ -12,5 +12,5 @@ else else echo "keys file will be saved $HOME/config/keys" fi - java -jar $HOME/libs/$boot_file -o $HOME/config/keys -l $HOME/config/init/local.conf $* + java -jar $HOME/libs/$boot_file -o $HOME/config/keys $* fi \ No newline at end of file From f0d4ef694ac7483ab76851e4cbf7b45c45066277 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 18 Sep 2019 17:18:59 +0800 Subject: [PATCH 121/124] Adding Partial Serialization Interface --- .../jd/blockchain/peer/web/ManagementController.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java index ced6284c..b21ba8da 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/ManagementController.java @@ -113,12 +113,18 @@ public class ManagementController implements LedgerBindingConfigAware, PeerManag DataContractRegistry.register(LedgerAdminInfo.class); DataContractRegistry.register(LedgerSettings.class); + + // 注册角色/权限相关接口 + DataContractRegistry.register(RolesConfigureOperation.class); + DataContractRegistry.register(RolesConfigureOperation.RolePrivilegeEntry.class); + DataContractRegistry.register(UserAuthorizeOperation.class); + DataContractRegistry.register(UserAuthorizeOperation.UserRolesEntry.class); + DataContractRegistry.register(PrivilegeSet.class); DataContractRegistry.register(RoleSet.class); DataContractRegistry.register(SecurityInitSettings.class); DataContractRegistry.register(RoleInitSettings.class); DataContractRegistry.register(UserAuthInitSettings.class); - DataContractRegistry.register(PrivilegeSet.class); - + DataContractRegistry.register(LedgerMetadata_V2.class); } /** From a733c75989ecac4c5a760ba251bdddc9378a9890 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 18 Sep 2019 18:47:22 +0800 Subject: [PATCH 122/124] Improved test cases of RolesAuthorizationTest; --- .../ledger/core/LedgerAdminDataset.java | 4 +- .../core/LedgerSecurityManagerImpl.java | 6 +- .../core/TransactionBatchProcessor.java | 4 +- .../ledger/core/UserRoleDataset.java | 4 +- .../handles/UserAuthorizeOperationHandle.java | 4 +- .../ledger/core/LedgerAdminDatasetTest.java | 14 +- .../ledger/LedgerAdminSettings.java | 2 +- ...gs.java => UserAuthorizationSettings.java} | 2 +- .../transaction/UserAuthorizeOpTemplate.java | 14 +- .../test/ledger/RolesAuthorizationTest.java | 120 +++++++++++++++++- 10 files changed, 140 insertions(+), 34 deletions(-) rename source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/{UserRolesSettings.java => UserAuthorizationSettings.java} (97%) diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java index f7cf951c..dfd06a1e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerAdminDataset.java @@ -16,7 +16,7 @@ import com.jd.blockchain.ledger.LedgerMetadata_V2; import com.jd.blockchain.ledger.LedgerSettings; import com.jd.blockchain.ledger.ParticipantNode; import com.jd.blockchain.ledger.RolePrivilegeSettings; -import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.UserAuthorizationSettings; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.ExPolicyKVStorage.ExPolicy; import com.jd.blockchain.storage.service.VersioningKVStorage; @@ -105,7 +105,7 @@ public class LedgerAdminDataset implements Transactional, LedgerAdminDataQuery, } @Override - public UserRolesSettings getUserRoles() { + public UserAuthorizationSettings getAuthorizations() { return userRoles; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java index e0987732..56daa556 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerSecurityManagerImpl.java @@ -17,7 +17,7 @@ import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.UserDoesNotExistException; import com.jd.blockchain.ledger.UserRoles; -import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.UserAuthorizationSettings; import com.jd.blockchain.utils.Bytes; /** @@ -30,7 +30,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private RolePrivilegeSettings rolePrivilegeSettings; - private UserRolesSettings userRolesSettings; + private UserAuthorizationSettings userRolesSettings; // 用户的权限配置 private Map userPrivilegesCache = new ConcurrentHashMap<>(); @@ -41,7 +41,7 @@ public class LedgerSecurityManagerImpl implements LedgerSecurityManager { private ParticipantDataQuery participantsQuery; private UserAccountQuery userAccountsQuery; - public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserRolesSettings userRolesSettings, + public LedgerSecurityManagerImpl(RolePrivilegeSettings rolePrivilegeSettings, UserAuthorizationSettings userRolesSettings, ParticipantDataQuery participantsQuery, UserAccountQuery userAccountsQuery) { this.rolePrivilegeSettings = rolePrivilegeSettings; this.userRolesSettings = userRolesSettings; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index a6982585..82b0e6be 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -82,7 +82,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { LedgerDataQuery ledgerDataQuery = ledgerRepo.getDataSet(ledgerBlock); LedgerAdminDataQuery previousAdminDataset = ledgerDataQuery.getAdminDataset(); this.securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getAdminInfo().getRolePrivileges(), - previousAdminDataset.getAdminInfo().getUserRoles(), previousAdminDataset.getParticipantDataset(), + previousAdminDataset.getAdminInfo().getAuthorizations(), previousAdminDataset.getParticipantDataset(), ledgerDataQuery.getUserAccountSet()); this.newBlockEditor = ledgerRepo.createNextBlock(); @@ -98,7 +98,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { LedgerAdminDataQuery previousAdminDataset = previousBlockDataset.getAdminDataset(); LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl( previousAdminDataset.getAdminInfo().getRolePrivileges(), - previousAdminDataset.getAdminInfo().getUserRoles(), previousAdminDataset.getParticipantDataset(), + previousAdminDataset.getAdminInfo().getAuthorizations(), previousAdminDataset.getParticipantDataset(), previousBlockDataset.getUserAccountSet()); TransactionBatchProcessor processor = new TransactionBatchProcessor(securityManager, newBlockEditor, ledgerRepo, diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java index 2f4eb514..25ecd85a 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserRoleDataset.java @@ -11,7 +11,7 @@ import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.ledger.RoleSet; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.UserRoles; -import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.UserAuthorizationSettings; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVEntry; import com.jd.blockchain.storage.service.VersioningKVStorage; @@ -24,7 +24,7 @@ import com.jd.blockchain.utils.Transactional; * @author huanghaiquan * */ -public class UserRoleDataset implements Transactional, MerkleProvable, UserRolesSettings { +public class UserRoleDataset implements Transactional, MerkleProvable, UserAuthorizationSettings { private MerkleDataSet dataset; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java index 8a50808d..295cb0fb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/UserAuthorizeOperationHandle.java @@ -9,7 +9,7 @@ import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.UserAuthorizeOperation; import com.jd.blockchain.ledger.UserAuthorizeOperation.UserRolesEntry; import com.jd.blockchain.ledger.UserRoles; -import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.UserAuthorizationSettings; import com.jd.blockchain.ledger.core.LedgerDataset; import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.MultiIDsPolicy; @@ -36,7 +36,7 @@ public class UserAuthorizeOperationHandle extends AbstractLedgerOperationHandle< // 操作账本; UserRolesEntry[] urcfgs = operation.getUserRolesAuthorizations(); - UserRolesSettings urSettings = newBlockDataset.getAdminDataset().getUserRoles(); + UserAuthorizationSettings urSettings = newBlockDataset.getAdminDataset().getAuthorizations(); RolePrivilegeSettings rolesSettings = newBlockDataset.getAdminDataset().getRolePrivileges(); if (urcfgs != null) { for (UserRolesEntry urcfg : urcfgs) { diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java index 684d720a..e7fd2c27 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAdminDatasetTest.java @@ -33,7 +33,7 @@ import com.jd.blockchain.ledger.RolePrivileges; import com.jd.blockchain.ledger.RolesPolicy; import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.UserRoles; -import com.jd.blockchain.ledger.UserRolesSettings; +import com.jd.blockchain.ledger.UserAuthorizationSettings; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.LedgerAdminDataset; import com.jd.blockchain.ledger.core.LedgerConfiguration; @@ -100,7 +100,7 @@ public class LedgerAdminDatasetTest { new TransactionPermission[] { TransactionPermission.DIRECT_OPERATION, TransactionPermission.CONTRACT_OPERATION }); - ledgerAdminDataset.getUserRoles().addUserRoles(parties[0].getAddress(), RolesPolicy.UNION, "DEFAULT"); + ledgerAdminDataset.getAuthorizations().addUserRoles(parties[0].getAddress(), RolesPolicy.UNION, "DEFAULT"); // New created instance is updated until being committed; assertTrue(ledgerAdminDataset.isUpdated()); @@ -148,7 +148,7 @@ public class LedgerAdminDatasetTest { verifyReadonlyState(reloadAdminAccount1); verifyRealoadingRoleAuthorizations(reloadAdminAccount1, ledgerAdminDataset.getRolePrivileges(), - ledgerAdminDataset.getUserRoles()); + ledgerAdminDataset.getAuthorizations()); // -------------- // 重新加载,并进行修改; @@ -168,7 +168,7 @@ public class LedgerAdminDatasetTest { reloadAdminAccount2.getRolePrivileges().disablePermissions("DEFAULT", TransactionPermission.CONTRACT_OPERATION); - reloadAdminAccount2.getUserRoles().addUserRoles(parties[1].getAddress(), RolesPolicy.UNION, "DEFAULT", "ADMIN"); + reloadAdminAccount2.getAuthorizations().addUserRoles(parties[1].getAddress(), RolesPolicy.UNION, "DEFAULT", "ADMIN"); reloadAdminAccount2.commit(); @@ -228,7 +228,7 @@ public class LedgerAdminDatasetTest { } private void verifyRealoadingRoleAuthorizations(LedgerAdminSettings actualAccount, - RolePrivilegeSettings expRolePrivilegeSettings, UserRolesSettings expUserRoleSettings) { + RolePrivilegeSettings expRolePrivilegeSettings, UserAuthorizationSettings expUserRoleSettings) { // 验证基本信息; RolePrivilegeSettings actualRolePrivileges = actualAccount.getRolePrivileges(); RolePrivileges[] expRPs = expRolePrivilegeSettings.getRolePrivileges(); @@ -242,12 +242,12 @@ public class LedgerAdminDatasetTest { assertArrayEquals(expRP.getTransactionPrivilege().toBytes(), actualRP.getTransactionPrivilege().toBytes()); } - UserRolesSettings actualUserRoleSettings = actualAccount.getUserRoles(); + UserAuthorizationSettings actualUserRoleSettings = actualAccount.getAuthorizations(); UserRoles[] expUserRoles = expUserRoleSettings.getUserRoles(); assertEquals(expUserRoles.length, actualUserRoleSettings.getUserCount()); for (UserRoles expUR : expUserRoles) { - UserRoles actualUR = actualAccount.getUserRoles().getUserRoles(expUR.getUserAddress()); + UserRoles actualUR = actualAccount.getAuthorizations().getUserRoles(expUR.getUserAddress()); assertNotNull(actualUR); assertEquals(expUR.getPolicy(), actualUR.getPolicy()); String[] expRoles = expUR.getRoles(); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java index 1c8cc9ff..2f4420fe 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/LedgerAdminSettings.java @@ -2,7 +2,7 @@ package com.jd.blockchain.ledger; public interface LedgerAdminSettings extends LedgerAdminInfo { - UserRolesSettings getUserRoles(); + UserAuthorizationSettings getAuthorizations(); RolePrivilegeSettings getRolePrivileges(); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRolesSettings.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizationSettings.java similarity index 97% rename from source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRolesSettings.java rename to source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizationSettings.java index af822b82..95e8755e 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserRolesSettings.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/UserAuthorizationSettings.java @@ -4,7 +4,7 @@ import java.util.Collection; import com.jd.blockchain.utils.Bytes; -public interface UserRolesSettings { +public interface UserAuthorizationSettings { /** * 单一用户可被授权的角色数量的最大值; diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java index 2575fdd5..ca66a6d9 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/UserAuthorizeOpTemplate.java @@ -22,8 +22,8 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe DataContractRegistry.register(UserRolesEntry.class); } - private Set userAuthMap = Collections - .synchronizedSet(new LinkedHashSet()); + private Set userAuthMap = Collections + .synchronizedSet(new LinkedHashSet()); public UserAuthorizeOpTemplate() { } @@ -32,8 +32,8 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe } @Override - public UserRolesAuthorization[] getUserRolesAuthorizations() { - return ArrayUtils.toArray(userAuthMap, UserRolesAuthorization.class); + public AuthorizationDataEntry[] getUserRolesAuthorizations() { + return ArrayUtils.toArray(userAuthMap, AuthorizationDataEntry.class); } @Override @@ -43,7 +43,7 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe @Override public UserRolesAuthorizer forUser(Bytes... userAddresses) { - UserRolesAuthorization userRolesAuth = new UserRolesAuthorization(userAddresses); + AuthorizationDataEntry userRolesAuth = new AuthorizationDataEntry(userAddresses); userAuthMap.add(userRolesAuth); return userRolesAuth; } @@ -54,7 +54,7 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe return forUser(addresses); } - private class UserRolesAuthorization implements UserRolesAuthorizer, UserRolesEntry { + private class AuthorizationDataEntry implements UserRolesAuthorizer, UserRolesEntry { private Bytes[] userAddress; @@ -63,7 +63,7 @@ public class UserAuthorizeOpTemplate implements UserAuthorizer, UserAuthorizeOpe private Set authRoles = new LinkedHashSet(); private Set unauthRoles = new LinkedHashSet(); - private UserRolesAuthorization(Bytes[] userAddress) { + private AuthorizationDataEntry(Bytes[] userAddress) { this.userAddress = userAddress; } diff --git a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java index 0e015086..8402a07a 100644 --- a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java +++ b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import org.junit.Test; @@ -36,9 +37,12 @@ import com.jd.blockchain.ledger.TransactionBuilder; import com.jd.blockchain.ledger.TransactionPermission; import com.jd.blockchain.ledger.TransactionRequest; import com.jd.blockchain.ledger.TransactionRequestBuilder; +import com.jd.blockchain.ledger.TransactionResponse; +import com.jd.blockchain.ledger.TransactionState; import com.jd.blockchain.ledger.UserAuthorizeOperation; import com.jd.blockchain.ledger.UserRegisterOperation; import com.jd.blockchain.ledger.UserRoles; +import com.jd.blockchain.ledger.core.DataAccount; import com.jd.blockchain.ledger.core.DefaultOperationHandleRegisteration; import com.jd.blockchain.ledger.core.LedgerInitializer; import com.jd.blockchain.ledger.core.LedgerManager; @@ -46,6 +50,7 @@ import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.LedgerRepository; import com.jd.blockchain.ledger.core.OperationHandleRegisteration; import com.jd.blockchain.ledger.core.TransactionBatchProcessor; +import com.jd.blockchain.ledger.core.UserAccount; import com.jd.blockchain.service.TransactionBatchResult; import com.jd.blockchain.service.TransactionBatchResultHandle; import com.jd.blockchain.storage.service.KVStorageService; @@ -79,6 +84,8 @@ public class RolesAuthorizationTest { private static final BlockchainKeypair DEFAULT_USER; private static final BlockchainKeypair GUEST_USER; + // 预置的新普通用户; + private static final BlockchainKeypair NEW_USER = BlockchainKeyGenerator.getInstance().generate(); // 预置的数据账户; private static final BlockchainIdentity DATA_ACCOUNT_ID = BlockchainKeyGenerator.getInstance().generate() .getIdentity(); @@ -107,24 +114,123 @@ public class RolesAuthorizationTest { public void test() { MemoryKVStorage storage = new MemoryKVStorage(); LedgerBlock genesisBlock = initLedger(storage); + final HashDigest ledgerHash = genesisBlock.getHash(); LedgerManager ledgerManager = new LedgerManager(); - LedgerRepository ledger = ledgerManager.register(genesisBlock.getHash(), storage); + LedgerRepository ledger = ledgerManager.register(ledgerHash, storage); // 验证角色和用户的权限配置; assertUserRolesPermissions(ledger); - // 预置数据; - TransactionRequest tx = buildRequest(ledger.getHash(), ADMIN_USER, ADMIN_USER, new TransactionDefiner() { + // 预置数据:准备一个新用户和数据账户; + TransactionRequest predefinedTx = buildRequest(ledger.getHash(), ADMIN_USER, ADMIN_USER, + new TransactionDefiner() { + @Override + public void define(TransactionBuilder txBuilder) { + txBuilder.security().roles().configure("NORMAL").enable(LedgerPermission.REGISTER_DATA_ACCOUNT) + .disable(LedgerPermission.REGISTER_USER) + .enable(TransactionPermission.CONTRACT_OPERATION); + + txBuilder.users().register(NEW_USER.getIdentity()); + + txBuilder.security().authorziations().forUser(NEW_USER.getAddress()).authorize("NORMAL"); + + txBuilder.dataAccounts().register(DATA_ACCOUNT_ID); + } + }); + + TransactionBatchResult procResult = executeTransactions(ledger, predefinedTx); + + //断言预定义数据的交易和区块成功; + assertBlock(1, procResult); + assertTransactionAllSuccess(procResult); + + //断言预定义的数据符合预期; + assertPredefineData(ledgerHash, storage); + + // 用不具备“注册用户”权限的用户,注册另一个新用户,预期交易失败; + BlockchainKeypair tempUser = BlockchainKeyGenerator.getInstance().generate(); + TransactionRequest tx = buildRequest(ledger.getHash(), NEW_USER, ADMIN_USER, new TransactionDefiner() { @Override public void define(TransactionBuilder txBuilder) { - txBuilder.dataAccounts().register(DATA_ACCOUNT_ID); + txBuilder.users().register(tempUser.getIdentity()); } }); - TransactionBatchResult procResult = executeTransactions(ledger, tx); - assertEquals(1, procResult.getBlock().getHeight()); + procResult = executeTransactions(ledger, tx); + assertBlock(2, procResult); + + assertTransactionAllFail(procResult, TransactionState.REJECTED_BY_SECURITY_POLICY); + } + + /** + * 断言区块高度; + * + * @param blockHeight + * @param procResult + */ + private void assertBlock(long blockHeight, TransactionBatchResult procResult) { + assertEquals(blockHeight, procResult.getBlock().getHeight()); + } + /** + * 断言全部交易结果都是成功的; + * + * @param procResult + */ + private void assertTransactionAllSuccess(TransactionBatchResult procResult) { + + Iterator responses = procResult.getResponses(); + while (responses.hasNext()) { + TransactionResponse transactionResponse = (TransactionResponse) responses.next(); + + assertEquals(true, transactionResponse.isSuccess()); + assertEquals(TransactionState.SUCCESS, transactionResponse.getExecutionState()); + assertEquals(procResult.getBlock().getHash(), transactionResponse.getBlockHash()); + assertEquals(procResult.getBlock().getHeight(), transactionResponse.getBlockHeight()); + } + } + + /** + * 断言全部交易结果都是失败的; + * + * @param procResult + */ + private void assertTransactionAllFail(TransactionBatchResult procResult, TransactionState txState) { + Iterator responses = procResult.getResponses(); + while (responses.hasNext()) { + TransactionResponse transactionResponse = (TransactionResponse) responses.next(); + + assertEquals(false, transactionResponse.isSuccess()); + assertEquals(txState, transactionResponse.getExecutionState()); + } + } + + /** + * 断言预定义的数据符合预期; + * + * @param ledgerHash + * @param storage + */ + private void assertPredefineData(HashDigest ledgerHash, MemoryKVStorage storage) { + LedgerManager ledgerManager = new LedgerManager(); + LedgerRepository ledger = ledgerManager.register(ledgerHash, storage); + UserAccount newUser = ledger.getUserAccountSet().getUser(NEW_USER.getAddress()); + assertNotNull(newUser); + DataAccount dataAccount = ledger.getDataAccountSet().getDataAccount(DATA_ACCOUNT_ID.getAddress()); + assertNotNull(dataAccount); + + UserRoles userRoles = ledger.getAdminSettings().getAuthorizations().getUserRoles(NEW_USER.getAddress()); + assertNotNull(userRoles); + assertEquals(1, userRoles.getRoleCount()); + assertEquals("NORMAL", userRoles.getRoles()[0]); + + RolePrivileges normalRole = ledger.getAdminSettings().getRolePrivileges().getRolePrivilege("NORMAL"); + assertNotNull(normalRole); + assertEquals(true, normalRole.getLedgerPrivilege().isEnable(LedgerPermission.REGISTER_DATA_ACCOUNT)); + assertEquals(false, normalRole.getLedgerPrivilege().isEnable(LedgerPermission.REGISTER_USER)); + assertEquals(true, normalRole.getTransactionPrivilege().isEnable(TransactionPermission.CONTRACT_OPERATION)); + assertEquals(false, normalRole.getTransactionPrivilege().isEnable(TransactionPermission.DIRECT_OPERATION)); } private TransactionBatchResult executeTransactions(LedgerRepository ledger, TransactionRequest... transactions) { @@ -192,7 +298,7 @@ public class RolesAuthorizationTest { if (roles == null) { roles = new String[0]; } - UserRoles userRoles = ledger.getAdminSettings().getUserRoles().getUserRoles(address); + UserRoles userRoles = ledger.getAdminSettings().getAuthorizations().getUserRoles(address); assertNotNull(userRoles); assertEquals(policy, userRoles.getPolicy()); From 00e07f79ec29aa89ee3fdb052a5bb2116a3c3725 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Wed, 18 Sep 2019 22:49:14 +0800 Subject: [PATCH 123/124] Refactored ledger core; --- .../blockchain/ledger/core/AccountQuery.java | 31 +++++ .../ledger/core/ContractAccountQuery.java | 26 +--- .../ledger/core/ContractAccountSet.java | 40 +++--- .../ledger/core/DataAccountQuery.java | 29 +---- .../ledger/core/DataAccountSet.java | 22 +++- .../ledger/core/EmptyAccountSet.java | 52 ++++++++ .../ledger/core/EmptyLedgerDataset.java | 116 +----------------- .../ledger/core/LedgerInitializer.java | 4 +- .../blockchain/ledger/core/LedgerQuery.java | 32 +++-- .../ledger/core/LedgerQueryService.java | 60 ++++----- .../ledger/core/LedgerRepositoryImpl.java | 12 +- .../ledger/core/LedgerTransactionContext.java | 2 +- .../core/TransactionBatchProcessor.java | 4 +- .../ledger/core/TransactionQuery.java | 24 ++++ .../ledger/core/TransactionSet.java | 14 ++- .../ledger/core/UserAccountQuery.java | 28 +---- .../ledger/core/UserAccountSet.java | 14 +-- ...tractContractEventSendOperationHandle.java | 2 +- .../DataAccountKVSetOperationHandle.java | 2 +- .../ledger/core/ContractInvokingTest.java | 24 ++-- .../core/TransactionBatchProcessorTest.java | 42 +++---- .../ledger/core/TransactionSetTest.java | 3 +- .../jd/blockchain/contract/EventHandle.java | 7 +- .../peer/web/LedgerQueryController.java | 62 +++++----- .../jd/blockchain/intgr/IntegrationTest.java | 18 +-- .../intgr/perf/LedgerInitializeTest.java | 8 +- .../intgr/perf/LedgerInitializeWebTest.java | 8 +- .../intgr/perf/LedgerPerformanceTest.java | 4 +- .../jd/blockchain/intgr/IntegrationBase.java | 6 +- .../intgr/IntegrationTest4Bftsmart.java | 4 +- .../blockchain/intgr/IntegrationTest4MQ.java | 4 +- .../intgr/IntegrationTestAll4Redis.java | 30 ++--- .../initializer/LedgerInitializeTest.java | 8 +- .../LedgerInitializeWeb4Nodes.java | 8 +- .../ledger/LedgerBlockGeneratingTest.java | 2 +- .../test/ledger/RolesAuthorizationTest.java | 7 +- .../blockchain/mocker/MockerNodeContext.java | 2 +- 37 files changed, 352 insertions(+), 409 deletions(-) create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountQuery.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyAccountSet.java create mode 100644 source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionQuery.java diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountQuery.java new file mode 100644 index 00000000..8f1f3cb0 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountQuery.java @@ -0,0 +1,31 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.utils.Bytes; + +public interface AccountQuery extends MerkleProvable { + + AccountHeader[] getHeaders(int fromIndex, int count); + + /** + * 返回总数; + * + * @return + */ + long getTotal(); + + boolean contains(Bytes address); + + /** + * 返回账户实例; + * + * @param address Base58 格式的账户地址; + * @return 账户实例,如果不存在则返回 null; + */ + T getAccount(String address); + + T getAccount(Bytes address); + + T getAccount(Bytes address, long version); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java index 4013a239..3c1d52a5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountQuery.java @@ -1,29 +1,5 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.MerkleProof; -import com.jd.blockchain.utils.Bytes; - -public interface ContractAccountQuery { - - AccountHeader[] getAccounts(int fromIndex, int count); - - HashDigest getRootHash(); - - /** - * 返回合约总数; - * - * @return - */ - long getTotalCount(); - - MerkleProof getProof(Bytes address); - - boolean contains(Bytes address); - - ContractAccount getContract(Bytes address); - - ContractAccount getContract(Bytes address, long version); +public interface ContractAccountQuery extends AccountQuery { } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java index 89d3ab3d..fc927d9e 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java @@ -11,7 +11,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class ContractAccountSet implements MerkleProvable, Transactional, ContractAccountQuery { +public class ContractAccountSet implements Transactional, ContractAccountQuery { private AccountSet accountSet; @@ -27,8 +27,8 @@ public class ContractAccountSet implements MerkleProvable, Transactional, Contra } @Override - public AccountHeader[] getAccounts(int fromIndex, int count) { - return accountSet.getAccounts(fromIndex,count); + public AccountHeader[] getHeaders(int fromIndex, int count) { + return accountSet.getAccounts(fromIndex, count); } public boolean isReadonly() { @@ -38,7 +38,7 @@ public class ContractAccountSet implements MerkleProvable, Transactional, Contra void setReadonly() { accountSet.setReadonly(); } - + @Override public HashDigest getRootHash() { return accountSet.getRootHash(); @@ -50,7 +50,7 @@ public class ContractAccountSet implements MerkleProvable, Transactional, Contra * @return */ @Override - public long getTotalCount() { + public long getTotal() { return accountSet.getTotalCount(); } @@ -65,13 +65,18 @@ public class ContractAccountSet implements MerkleProvable, Transactional, Contra } @Override - public ContractAccount getContract(Bytes address) { + public ContractAccount getAccount(Bytes address) { BaseAccount accBase = accountSet.getAccount(address); return new ContractAccount(accBase); } @Override - public ContractAccount getContract(Bytes address, long version) { + public ContractAccount getAccount(String address) { + return getAccount(Bytes.fromBase58(address)); + } + + @Override + public ContractAccount getAccount(Bytes address, long version) { BaseAccount accBase = accountSet.getAccount(address, version); return new ContractAccount(accBase); } @@ -79,14 +84,10 @@ public class ContractAccountSet implements MerkleProvable, Transactional, Contra /** * 部署一项新的合约链码; * - * @param address - * 合约账户地址; - * @param pubKey - * 合约账户公钥; - * @param addressSignature - * 地址签名;合约账户的私钥对地址的签名; - * @param chaincode - * 链码内容; + * @param address 合约账户地址; + * @param pubKey 合约账户公钥; + * @param addressSignature 地址签名;合约账户的私钥对地址的签名; + * @param chaincode 链码内容; * @return 合约账户; */ public ContractAccount deploy(Bytes address, PubKey pubKey, DigitalSignature addressSignature, byte[] chaincode) { @@ -100,12 +101,9 @@ public class ContractAccountSet implements MerkleProvable, Transactional, Contra /** * 更新指定账户的链码; * - * @param address - * 合约账户地址; - * @param chaincode - * 链码内容; - * @param version - * 链码版本; + * @param address 合约账户地址; + * @param chaincode 链码内容; + * @param version 链码版本; * @return 返回链码的新版本号; */ public long update(Bytes address, byte[] chaincode, long version) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java index b7cc8d43..c2bcb17d 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountQuery.java @@ -1,32 +1,5 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.MerkleProof; -import com.jd.blockchain.utils.Bytes; - -public interface DataAccountQuery { - - AccountHeader[] getAccounts(int fromIndex, int count); - - HashDigest getRootHash(); - - long getTotalCount(); - - /** - * 返回账户的存在性证明; - */ - MerkleProof getProof(Bytes address); - - /** - * 返回数据账户;
        - * 如果不存在,则返回 null; - * - * @param address - * @return - */ - DataAccount getDataAccount(Bytes address); - - DataAccount getDataAccount(Bytes address, long version); +public interface DataAccountQuery extends AccountQuery { } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java index dbc77437..8a78f6f0 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java @@ -11,7 +11,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class DataAccountSet implements MerkleProvable, Transactional, DataAccountQuery { +public class DataAccountSet implements Transactional, DataAccountQuery { private AccountSet accountSet; @@ -27,7 +27,7 @@ public class DataAccountSet implements MerkleProvable, Transactional, DataAccoun } @Override - public AccountHeader[] getAccounts(int fromIndex, int count) { + public AccountHeader[] getHeaders(int fromIndex, int count) { return accountSet.getAccounts(fromIndex, count); } @@ -38,17 +38,22 @@ public class DataAccountSet implements MerkleProvable, Transactional, DataAccoun void setReadonly() { accountSet.setReadonly(); } - + @Override public HashDigest getRootHash() { return accountSet.getRootHash(); } @Override - public long getTotalCount() { + public long getTotal() { return accountSet.getTotalCount(); } + @Override + public boolean contains(Bytes address) { + return accountSet.contains(address); + } + /** * 返回账户的存在性证明; */ @@ -63,6 +68,11 @@ public class DataAccountSet implements MerkleProvable, Transactional, DataAccoun return new DataAccount(accBase); } + @Override + public DataAccount getAccount(String address) { + return getAccount(Bytes.fromBase58(address)); + } + /** * 返回数据账户;
        * 如果不存在,则返回 null; @@ -71,7 +81,7 @@ public class DataAccountSet implements MerkleProvable, Transactional, DataAccoun * @return */ @Override - public DataAccount getDataAccount(Bytes address) { + public DataAccount getAccount(Bytes address) { BaseAccount accBase = accountSet.getAccount(address); if (accBase == null) { return null; @@ -80,7 +90,7 @@ public class DataAccountSet implements MerkleProvable, Transactional, DataAccoun } @Override - public DataAccount getDataAccount(Bytes address, long version) { + public DataAccount getAccount(Bytes address, long version) { BaseAccount accBase = accountSet.getAccount(address, version); return new DataAccount(accBase); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyAccountSet.java new file mode 100644 index 00000000..879bf702 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyAccountSet.java @@ -0,0 +1,52 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.AccountHeader; +import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.utils.Bytes; + +public class EmptyAccountSet implements AccountQuery { + + private static final AccountHeader[] EMPTY = {}; + + @Override + public HashDigest getRootHash() { + return null; + } + + @Override + public MerkleProof getProof(Bytes key) { + return null; + } + + @Override + public AccountHeader[] getHeaders(int fromIndex, int count) { + return EMPTY; + } + + @Override + public long getTotal() { + return 0; + } + + @Override + public boolean contains(Bytes address) { + return false; + } + + @Override + public T getAccount(String address) { + return null; + } + + @Override + public T getAccount(Bytes address) { + return null; + } + + @Override + public T getAccount(Bytes address, long version) { + return null; + } + +} diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java index 3bd87b8d..958ef8aa 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/EmptyLedgerDataset.java @@ -1,7 +1,6 @@ package com.jd.blockchain.ledger.core; import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.LedgerAdminSettings; import com.jd.blockchain.ledger.MerkleProof; import com.jd.blockchain.ledger.ParticipantDataQuery; @@ -90,120 +89,17 @@ public class EmptyLedgerDataset implements LedgerDataQuery { } - private static class EmptyUserAccountSet implements UserAccountQuery{ + private static class EmptyUserAccountSet extends EmptyAccountSet implements UserAccountQuery{ - @Override - public AccountHeader[] getAccounts(int fromIndex, int count) { - return null; - } - - @Override - public long getTotalCount() { - return 0; - } - - @Override - public HashDigest getRootHash() { - return null; - } - - @Override - public MerkleProof getProof(Bytes key) { - return null; - } - - @Override - public UserAccount getUser(String address) { - return null; - } - - @Override - public UserAccount getUser(Bytes address) { - return null; - } - - @Override - public boolean contains(Bytes address) { - return false; - } - - @Override - public UserAccount getUser(Bytes address, long version) { - return null; - } - - } - private static class EmptyDataAccountSet implements DataAccountQuery{ - - @Override - public AccountHeader[] getAccounts(int fromIndex, int count) { - return null; - } - - @Override - public HashDigest getRootHash() { - return null; - } - - @Override - public long getTotalCount() { - return 0; - } - - @Override - public MerkleProof getProof(Bytes address) { - return null; - } + private static class EmptyDataAccountSet extends EmptyAccountSet implements DataAccountQuery{ - @Override - public DataAccount getDataAccount(Bytes address) { - return null; - } - - @Override - public DataAccount getDataAccount(Bytes address, long version) { - return null; - } + } + + private static class EmptyContractAccountSet extends EmptyAccountSet implements ContractAccountQuery{ } - private static class EmptyContractAccountSet implements ContractAccountQuery{ - - @Override - public AccountHeader[] getAccounts(int fromIndex, int count) { - return null; - } - - @Override - public HashDigest getRootHash() { - return null; - } - - @Override - public long getTotalCount() { - return 0; - } - - @Override - public MerkleProof getProof(Bytes address) { - return null; - } - - @Override - public boolean contains(Bytes address) { - return false; - } - - @Override - public ContractAccount getContract(Bytes address) { - return null; - } - - @Override - public ContractAccount getContract(Bytes address, long version) { - return null; - } - } + } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java index 06fce128..c8c04712 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerInitializer.java @@ -272,12 +272,12 @@ public class LedgerInitializer { } @Override - public LedgerDataQuery getDataSet(LedgerBlock block) { + public LedgerDataQuery getLedgerData(LedgerBlock block) { return dataset; } @Override - public TransactionSet getTransactionSet(LedgerBlock block) { + public TransactionQuery getTransactionSet(LedgerBlock block) { return null; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java index 748cb212..75233ae5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQuery.java @@ -54,16 +54,30 @@ public interface LedgerQuery { LedgerAdminInfo getAdminInfo(); LedgerAdminInfo getAdminInfo(LedgerBlock block); - + LedgerAdminSettings getAdminSettings(); - + LedgerAdminSettings getAdminSettings(LedgerBlock block); LedgerBlock getBlock(HashDigest hash); - LedgerDataQuery getDataSet(LedgerBlock block); + /** + * 返回指定 + * @param block + * @return + */ + LedgerDataQuery getLedgerData(LedgerBlock block); + + /** + * 返回最新区块对应的账本数据; + * + * @return + */ + default LedgerDataQuery getLedgerData() { + return getLedgerData(getLatestBlock()); + } - TransactionSet getTransactionSet(LedgerBlock block); + TransactionQuery getTransactionSet(LedgerBlock block); UserAccountQuery getUserAccountSet(LedgerBlock block); @@ -71,11 +85,7 @@ public interface LedgerQuery { ContractAccountQuery getContractAccountSet(LedgerBlock block); - default LedgerDataQuery getDataSet() { - return getDataSet(getLatestBlock()); - } - - default TransactionSet getTransactionSet() { + default TransactionQuery getTransactionSet() { return getTransactionSet(getLatestBlock()); } @@ -90,7 +100,7 @@ public interface LedgerQuery { default ContractAccountQuery getContractAccountset() { return getContractAccountSet(getLatestBlock()); } - + /** * 重新检索最新区块,同时更新缓存; * @@ -111,7 +121,5 @@ public interface LedgerQuery { * @return */ HashDigest retrieveLatestBlockHash(); - - } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java index 6c1bed81..55b989d5 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerQueryService.java @@ -93,7 +93,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getTransactionCount(HashDigest ledgerHash, long height) { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); - TransactionSet txset = ledger.getTransactionSet(block); + TransactionQuery txset = ledger.getTransactionSet(block); return txset.getTotalCount(); } @@ -101,7 +101,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getTransactionCount(HashDigest ledgerHash, HashDigest blockHash) { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - TransactionSet txset = ledger.getTransactionSet(block); + TransactionQuery txset = ledger.getTransactionSet(block); return txset.getTotalCount(); } @@ -109,7 +109,7 @@ public class LedgerQueryService implements BlockchainQueryService { public long getTransactionTotalCount(HashDigest ledgerHash) { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - TransactionSet txset = ledger.getTransactionSet(block); + TransactionQuery txset = ledger.getTransactionSet(block); return txset.getTotalCount(); } @@ -118,7 +118,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getTotalCount(); + return dataAccountSet.getTotal(); } @Override @@ -126,7 +126,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getTotalCount(); + return dataAccountSet.getTotal(); } @Override @@ -134,7 +134,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getTotalCount(); + return dataAccountSet.getTotal(); } @Override @@ -142,7 +142,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getTotalCount(); + return userAccountSet.getTotal(); } @Override @@ -150,7 +150,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getTotalCount(); + return userAccountSet.getTotal(); } @Override @@ -158,7 +158,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getTotalCount(); + return userAccountSet.getTotal(); } @Override @@ -166,7 +166,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(height); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getTotalCount(); + return contractAccountSet.getTotal(); } @Override @@ -174,7 +174,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getTotalCount(); + return contractAccountSet.getTotal(); } @Override @@ -182,14 +182,14 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getTotalCount(); + return contractAccountSet.getTotal(); } @Override public LedgerTransaction[] getTransactions(HashDigest ledgerHash, long height, int fromIndex, int count) { checkLedgerHash(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(height); - TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); + TransactionQuery transactionSet = ledger.getTransactionSet(ledgerBlock); int lastHeightTxTotalNums = 0; if (height > 0) { @@ -219,7 +219,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(blockHash); long height = ledgerBlock.getHeight(); - TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); + TransactionQuery transactionSet = ledger.getTransactionSet(ledgerBlock); int lastHeightTxTotalNums = 0; if (height > 0) { @@ -248,7 +248,7 @@ public class LedgerQueryService implements BlockchainQueryService { public LedgerTransaction getTransactionByContentHash(HashDigest ledgerHash, HashDigest contentHash) { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - TransactionSet txset = ledger.getTransactionSet(block); + TransactionQuery txset = ledger.getTransactionSet(block); return txset.get(contentHash); } @@ -256,8 +256,8 @@ public class LedgerQueryService implements BlockchainQueryService { public TransactionState getTransactionStateByContentHash(HashDigest ledgerHash, HashDigest contentHash) { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - TransactionSet txset = ledger.getTransactionSet(block); - return txset.getTxState(contentHash); + TransactionQuery txset = ledger.getTransactionSet(block); + return txset.getState(contentHash); } @Override @@ -265,7 +265,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getUser(address); + return userAccountSet.getAccount(address); } @@ -274,7 +274,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + return dataAccountSet.getAccount(Bytes.fromBase58(address)); } @Override @@ -285,7 +285,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; long ver; @@ -333,7 +333,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; long ver = -1; @@ -363,7 +363,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccount.getDataEntriesTotalCount()); return dataAccount.getDataEntries(pages[0], pages[1]); @@ -374,7 +374,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); return dataAccount.getDataEntriesTotalCount(); } @@ -384,7 +384,7 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getContract(Bytes.fromBase58(address)); + return contractAccountSet.getAccount(Bytes.fromBase58(address)); } @Override @@ -392,8 +392,8 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotalCount()); - return userAccountSet.getAccounts(pages[0], pages[1]); + int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotal()); + return userAccountSet.getHeaders(pages[0], pages[1]); } @Override @@ -401,8 +401,8 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotalCount()); - return dataAccountSet.getAccounts(pages[0], pages[1]); + int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotal()); + return dataAccountSet.getHeaders(pages[0], pages[1]); } @Override @@ -410,8 +410,8 @@ public class LedgerQueryService implements BlockchainQueryService { checkLedgerHash(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotalCount()); - return contractAccountSet.getAccounts(pages[0], pages[1]); + int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotal()); + return contractAccountSet.getHeaders(pages[0], pages[1]); } } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java index 5a35488a..9734f880 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerRepositoryImpl.java @@ -118,7 +118,7 @@ class LedgerRepositoryImpl implements LedgerRepository { private LedgerState retrieveLatestState() { LedgerBlock latestBlock = innerGetBlock(innerGetLatestBlockHeight()); LedgerDataset ledgerDataset = innerGetLedgerDataset(latestBlock); - TransactionSet txSet = loadTransactionSet(latestBlock.getTransactionSetHash(), + TransactionQuery txSet = loadTransactionSet(latestBlock.getTransactionSetHash(), ledgerDataset.getAdminDataset().getSettings().getCryptoSetting(), keyPrefix, exPolicyStorage, versioningStorage, true); this.latestState = new LedgerState(latestBlock, ledgerDataset, txSet); @@ -253,7 +253,7 @@ class LedgerRepositoryImpl implements LedgerRepository { } @Override - public TransactionSet getTransactionSet(LedgerBlock block) { + public TransactionQuery getTransactionSet(LedgerBlock block) { long height = getLatestBlockHeight(); if (height == block.getHeight()) { // 从缓存中返回最新区块的数据集; @@ -354,7 +354,7 @@ class LedgerRepositoryImpl implements LedgerRepository { } @Override - public LedgerDataset getDataSet(LedgerBlock block) { + public LedgerDataset getLedgerData(LedgerBlock block) { long height = getLatestBlockHeight(); if (height == block.getHeight()) { return latestState.getLedgerDataset(); @@ -579,11 +579,11 @@ class LedgerRepositoryImpl implements LedgerRepository { private final LedgerBlock block; - private final TransactionSet transactionSet; + private final TransactionQuery transactionSet; private final LedgerDataset ledgerDataset; - public LedgerState(LedgerBlock block, LedgerDataset ledgerDataset, TransactionSet transactionSet) { + public LedgerState(LedgerBlock block, LedgerDataset ledgerDataset, TransactionQuery transactionSet) { this.block = block; this.ledgerDataset = ledgerDataset; this.transactionSet = transactionSet; @@ -610,7 +610,7 @@ class LedgerRepositoryImpl implements LedgerRepository { return ledgerDataset.getUserAccountSet(); } - public TransactionSet getTransactionSet() { + public TransactionQuery getTransactionSet() { return transactionSet; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java index b06721e6..5dccc736 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java @@ -24,7 +24,7 @@ public interface LedgerTransactionContext { * * @return */ - TransactionSet getTransactionSet(); + TransactionQuery getTransactionSet(); /** * 交易请求; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java index 82b0e6be..ae62cdf7 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionBatchProcessor.java @@ -79,7 +79,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { this.handlesRegisteration = handlesRegisteration; LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); - LedgerDataQuery ledgerDataQuery = ledgerRepo.getDataSet(ledgerBlock); + LedgerDataQuery ledgerDataQuery = ledgerRepo.getLedgerData(ledgerBlock); LedgerAdminDataQuery previousAdminDataset = ledgerDataQuery.getAdminDataset(); this.securityManager = new LedgerSecurityManagerImpl(previousAdminDataset.getAdminInfo().getRolePrivileges(), previousAdminDataset.getAdminInfo().getAuthorizations(), previousAdminDataset.getParticipantDataset(), @@ -93,7 +93,7 @@ public class TransactionBatchProcessor implements TransactionBatchProcess { OperationHandleRegisteration handlesRegisteration) { LedgerBlock ledgerBlock = ledgerRepo.getLatestBlock(); LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(ledgerBlock); LedgerAdminDataQuery previousAdminDataset = previousBlockDataset.getAdminDataset(); LedgerSecurityManager securityManager = new LedgerSecurityManagerImpl( diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionQuery.java new file mode 100644 index 00000000..887782d4 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionQuery.java @@ -0,0 +1,24 @@ +package com.jd.blockchain.ledger.core; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.LedgerTransaction; +import com.jd.blockchain.ledger.TransactionState; + +public interface TransactionQuery extends MerkleProvable { + + LedgerTransaction[] getTxs(int fromIndex, int count); + + byte[][] getValuesByIndex(int fromIndex, int count); + + long getTotalCount(); + + /** + * @param txContentHash + * Base58 编码的交易内容的哈希; + * @return + */ + LedgerTransaction get(HashDigest txContentHash); + + TransactionState getState(HashDigest txContentHash); + +} \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java index 24cb6416..28c6bff4 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/TransactionSet.java @@ -13,7 +13,7 @@ import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class TransactionSet implements Transactional, MerkleProvable { +public class TransactionSet implements Transactional, TransactionQuery { static { DataContractRegistry.register(LedgerTransaction.class); @@ -25,6 +25,7 @@ public class TransactionSet implements Transactional, MerkleProvable { private MerkleDataSet txSet; + @Override public LedgerTransaction[] getTxs(int fromIndex, int count) { if (count > LedgerConsts.MAX_LIST_COUNT) { throw new IllegalArgumentException("Count exceed the upper limit[" + LedgerConsts.MAX_LIST_COUNT + "]!"); @@ -38,6 +39,7 @@ public class TransactionSet implements Transactional, MerkleProvable { return ledgerTransactions; } + @Override public byte[][] getValuesByIndex(int fromIndex, int count) { byte[][] values = new byte[count][]; for (int i = 0; i < count; i++) { @@ -57,6 +59,7 @@ public class TransactionSet implements Transactional, MerkleProvable { return txSet.getProof(key); } + @Override public long getTotalCount() { // 每写入一个交易,同时写入交易内容Hash与交易结果的索引,因此交易记录数为集合总记录数除以 2; return txSet.getDataCount() / 2; @@ -113,10 +116,10 @@ public class TransactionSet implements Transactional, MerkleProvable { } /** - * @param txContentHash - * Base58 编码的交易内容的哈希; + * @param txContentHash Base58 编码的交易内容的哈希; * @return */ + @Override public LedgerTransaction get(HashDigest txContentHash) { // transaction has only one version; Bytes key = new Bytes(txContentHash.toBytes()); @@ -129,7 +132,8 @@ public class TransactionSet implements Transactional, MerkleProvable { return tx; } - public TransactionState getTxState(HashDigest txContentHash) { + @Override + public TransactionState getState(HashDigest txContentHash) { Bytes resultKey = encodeTxStateKey(txContentHash); // transaction has only one version; byte[] bytes = txSet.getValue(resultKey, 0); @@ -154,7 +158,7 @@ public class TransactionSet implements Transactional, MerkleProvable { public boolean isReadonly() { return txSet.isReadonly(); } - + void setReadonly() { txSet.setReadonly(); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java index 3d920b5c..44e85b03 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountQuery.java @@ -1,31 +1,5 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.AccountHeader; -import com.jd.blockchain.ledger.MerkleProof; -import com.jd.blockchain.utils.Bytes; - -public interface UserAccountQuery { - - AccountHeader[] getAccounts(int fromIndex, int count); - - /** - * 返回用户总数; - * - * @return - */ - long getTotalCount(); - - HashDigest getRootHash(); - - MerkleProof getProof(Bytes key); - - UserAccount getUser(String address); - - UserAccount getUser(Bytes address); - - boolean contains(Bytes address); - - UserAccount getUser(Bytes address, long version); +public interface UserAccountQuery extends AccountQuery { } \ No newline at end of file diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java index f1e8bbc3..440d7fdb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java @@ -15,7 +15,7 @@ import com.jd.blockchain.utils.Transactional; * @author huanghaiquan * */ -public class UserAccountSet implements Transactional, MerkleProvable, UserAccountQuery { +public class UserAccountSet implements Transactional, UserAccountQuery { private AccountSet accountSet; @@ -32,7 +32,7 @@ public class UserAccountSet implements Transactional, MerkleProvable, UserAccoun } @Override - public AccountHeader[] getAccounts(int fromIndex, int count) { + public AccountHeader[] getHeaders(int fromIndex, int count) { return accountSet.getAccounts(fromIndex,count); } @@ -42,7 +42,7 @@ public class UserAccountSet implements Transactional, MerkleProvable, UserAccoun * @return */ @Override - public long getTotalCount() { + public long getTotal() { return accountSet.getTotalCount(); } @@ -65,12 +65,12 @@ public class UserAccountSet implements Transactional, MerkleProvable, UserAccoun } @Override - public UserAccount getUser(String address) { - return getUser(Bytes.fromBase58(address)); + public UserAccount getAccount(String address) { + return getAccount(Bytes.fromBase58(address)); } @Override - public UserAccount getUser(Bytes address) { + public UserAccount getAccount(Bytes address) { BaseAccount baseAccount = accountSet.getAccount(address); return new UserAccount(baseAccount); } @@ -81,7 +81,7 @@ public class UserAccountSet implements Transactional, MerkleProvable, UserAccoun } @Override - public UserAccount getUser(Bytes address, long version) { + public UserAccount getAccount(Bytes address, long version) { BaseAccount baseAccount = accountSet.getAccount(address, version); return new UserAccount(baseAccount); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java index eeb3ceb0..5cd6dcfb 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/AbtractContractEventSendOperationHandle.java @@ -57,7 +57,7 @@ public abstract class AbtractContractEventSendOperationHandle implements Operati ContractLedgerContext ledgerContext = new ContractLedgerContext(queryService, opHandleContext); // 先检查合约引擎是否已经加载合约;如果未加载,再从账本中读取合约代码并装载到引擎中执行; - ContractAccount contract = contractSet.getContract(contractOP.getContractAddress()); + ContractAccount contract = contractSet.getAccount(contractOP.getContractAddress()); if (contract == null) { throw new LedgerException(String.format("Contract was not registered! --[ContractAddress=%s]", contractOP.getContractAddress())); diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java index 4a8209b2..0f9fdb2b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/handles/DataAccountKVSetOperationHandle.java @@ -30,7 +30,7 @@ public class DataAccountKVSetOperationHandle extends AbstractLedgerOperationHand securityPolicy.checkEndpointPermission(LedgerPermission.WRITE_DATA_ACCOUNT, MultiIDsPolicy.AT_LEAST_ONE); // 操作账本; - DataAccount account = newBlockDataset.getDataAccountSet().getDataAccount(kvWriteOp.getAccountAddress()); + DataAccount account = newBlockDataset.getDataAccountSet().getAccount(kvWriteOp.getAccountAddress()); if (account == null) { throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java index 66b62cc2..3232c385 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/ContractInvokingTest.java @@ -106,7 +106,7 @@ public class ContractInvokingTest { deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); @@ -181,7 +181,7 @@ public class ContractInvokingTest { // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); @@ -218,7 +218,7 @@ public class ContractInvokingTest { TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); txResultHandle.commit(); - BytesValue latestValue = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getBytes(key, + BytesValue latestValue = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getBytes(key, -1); System.out.printf("latest value=[%s] %s \r\n", latestValue.getType(), latestValue.getValue().toUTF8String()); @@ -278,9 +278,9 @@ public class ContractInvokingTest { } }); // 预期数据都能够正常写入; - KVDataEntry kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", + KVDataEntry kv1 = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getDataEntry("K1", 0); - KVDataEntry kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", + KVDataEntry kv2 = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getDataEntry("K2", 0); assertEquals(0, kv1.getVersion()); assertEquals(0, kv2.getVersion()); @@ -299,8 +299,8 @@ public class ContractInvokingTest { } }); // 预期数据都能够正常写入; - kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); - kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", 1); + kv1 = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); + kv2 = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getDataEntry("K2", 1); assertEquals(1, kv1.getVersion()); assertEquals(1, kv2.getVersion()); assertEquals("V1-1", kv1.getValue()); @@ -318,10 +318,10 @@ public class ContractInvokingTest { } }); // 预期数据都能够正常写入; - kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); + kv1 = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); assertEquals(1, kv1.getVersion()); assertEquals("V1-1", kv1.getValue()); - kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 2); + kv1 = ledgerRepo.getDataAccountSet().getAccount(kpDataAccount.getAddress()).getDataEntry("K1", 2); assertEquals(-1, kv1.getVersion()); assertEquals(null, kv1.getValue()); @@ -330,7 +330,7 @@ public class ContractInvokingTest { private LedgerBlock buildBlock(LedgerRepository ledgerRepo, LedgerService ledgerService, OperationHandleRegisteration opReg, TxDefinitor txDefinitor) { LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(preBlock); LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(getSecurityManager(), newBlockEditor, ledgerRepo, opReg); @@ -363,7 +363,7 @@ public class ContractInvokingTest { private void registerDataAccount(LedgerRepository ledgerRepo, LedgerManager ledgerManager, DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair kpDataAccount) { LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); @@ -393,7 +393,7 @@ public class ContractInvokingTest { DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair contractKey) { // 创建新区块的交易处理器; LedgerBlock preBlock = ledgerRepo.getLatestBlock(); - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(preBlock); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(preBlock); // 加载合约 LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java index 5f6ff055..06bf987d 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionBatchProcessorTest.java @@ -84,8 +84,8 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); - UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(ledgerRepo.getLatestBlock()); + UserAccount user0 = previousBlockDataset.getUserAccountSet().getAccount(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); assertTrue(partiRegistered); @@ -144,8 +144,8 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); - UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(ledgerRepo.getLatestBlock()); + UserAccount user0 = previousBlockDataset.getUserAccountSet().getAccount(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); assertTrue(partiRegistered); @@ -183,7 +183,7 @@ public class TransactionBatchProcessorTest { assertEquals(newBlock.getHash(), latestBlock.getHash()); assertEquals(1, newBlock.getHeight()); - LedgerDataQuery ledgerDS = ledgerRepo.getDataSet(latestBlock); + LedgerDataQuery ledgerDS = ledgerRepo.getLedgerData(latestBlock); boolean existUser1 = ledgerDS.getUserAccountSet().contains(userKeypair1.getAddress()); boolean existUser2 = ledgerDS.getUserAccountSet().contains(userKeypair2.getAddress()); assertTrue(existUser1); @@ -202,8 +202,8 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); - UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(ledgerRepo.getLatestBlock()); + UserAccount user0 = previousBlockDataset.getUserAccountSet().getAccount(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); assertTrue(partiRegistered); @@ -261,7 +261,7 @@ public class TransactionBatchProcessorTest { assertNotNull(tx3); assertEquals(TransactionState.SUCCESS, tx3.getExecutionState()); - LedgerDataQuery ledgerDS = ledgerRepo.getDataSet(latestBlock); + LedgerDataQuery ledgerDS = ledgerRepo.getLedgerData(latestBlock); boolean existUser1 = ledgerDS.getUserAccountSet().contains(userKeypair1.getAddress()); boolean existUser2 = ledgerDS.getUserAccountSet().contains(userKeypair2.getAddress()); boolean existUser3 = ledgerDS.getUserAccountSet().contains(userKeypair3.getAddress()); @@ -282,8 +282,8 @@ public class TransactionBatchProcessorTest { LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE); // 验证参与方账户的存在; - LedgerDataQuery previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); - UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress()); + LedgerDataQuery previousBlockDataset = ledgerRepo.getLedgerData(ledgerRepo.getLatestBlock()); + UserAccount user0 = previousBlockDataset.getUserAccountSet().getAccount(parti0.getAddress()); assertNotNull(user0); boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress()); assertTrue(partiRegistered); @@ -305,7 +305,7 @@ public class TransactionBatchProcessorTest { newBlockEditor.commit(); assertEquals(TransactionState.SUCCESS, txResp1.getExecutionState()); - DataAccount dataAccount = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()); + DataAccount dataAccount = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()); assertNotNull(dataAccount); // 正确写入 KV 数据; @@ -321,7 +321,7 @@ public class TransactionBatchProcessorTest { "K1", "V-1-2", 0, ledgerHash, parti0, parti0); newBlockEditor = ledgerRepo.createNextBlock(); - previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + previousBlockDataset = ledgerRepo.getLedgerData(ledgerRepo.getLatestBlock()); txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, ledgerRepo, opReg); txbatchProcessor.schedule(txreq1); @@ -332,13 +332,13 @@ public class TransactionBatchProcessorTest { newBlock = newBlockEditor.prepare(); newBlockEditor.commit(); - BytesValue v1_0 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1", + BytesValue v1_0 = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()).getBytes("K1", 0); - BytesValue v1_1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1", + BytesValue v1_1 = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()).getBytes("K1", 1); - BytesValue v2 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K2", + BytesValue v2 = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()).getBytes("K2", 0); - BytesValue v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3", + BytesValue v3 = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()).getBytes("K3", 0); assertNotNull(v1_0); @@ -360,7 +360,7 @@ public class TransactionBatchProcessorTest { "K1", "V-1-3", 0, ledgerHash, parti0, parti0); newBlockEditor = ledgerRepo.createNextBlock(); - previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + previousBlockDataset = ledgerRepo.getLedgerData(ledgerRepo.getLatestBlock()); txbatchProcessor = new TransactionBatchProcessor(securityManager, newBlockEditor, ledgerRepo, opReg); txbatchProcessor.schedule(txreq5); @@ -376,15 +376,15 @@ public class TransactionBatchProcessorTest { newBlock = newBlockEditor.prepare(); newBlockEditor.commit(); - BytesValue v1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1"); - v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3"); + BytesValue v1 = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()).getBytes("K1"); + v3 = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()).getBytes("K3"); // k1 的版本仍然为1,没有更新; - long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) + long k1_version = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()) .getDataVersion("K1"); assertEquals(1, k1_version); - long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) + long k3_version = ledgerRepo.getDataAccountSet().getAccount(dataAccountKeypair.getAddress()) .getDataVersion("K3"); assertEquals(1, k3_version); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java index 7f8d73d5..cd5ebc9f 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/TransactionSetTest.java @@ -17,6 +17,7 @@ import com.jd.blockchain.binaryproto.DataContractRegistry; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; import com.jd.blockchain.ledger.core.LedgerTransactionData; +import com.jd.blockchain.ledger.core.TransactionQuery; import com.jd.blockchain.ledger.core.TransactionSet; import com.jd.blockchain.ledger.core.TransactionStagedSnapshot; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; @@ -103,7 +104,7 @@ public class TransactionSetTest { assertEquals(5, tx.getTransactionContent().getOperations().length); // Reload ; - TransactionSet reloadTxset = new TransactionSet(txsetRootHash, defCryptoSetting, keyPrefix, testStorage, + TransactionQuery reloadTxset = new TransactionSet(txsetRootHash, defCryptoSetting, keyPrefix, testStorage, testStorage, true); assertEquals(1, reloadTxset.getTotalCount()); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/EventHandle.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/EventHandle.java index 9b301460..ac193ab9 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/EventHandle.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/EventHandle.java @@ -5,12 +5,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * HTTP 服务方法; - * - * @author haiq - * - */ + @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface EventHandle { diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java index 6dfd85d1..ad5f3aeb 100644 --- a/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java +++ b/source/peer/src/main/java/com/jd/blockchain/peer/web/LedgerQueryController.java @@ -20,7 +20,7 @@ import com.jd.blockchain.ledger.core.DataAccountQuery; import com.jd.blockchain.ledger.core.LedgerQuery; import com.jd.blockchain.ledger.core.LedgerService; import com.jd.blockchain.ledger.core.ParticipantCertData; -import com.jd.blockchain.ledger.core.TransactionSet; +import com.jd.blockchain.ledger.core.TransactionQuery; import com.jd.blockchain.ledger.core.UserAccountQuery; import com.jd.blockchain.transaction.BlockchainQueryService; import com.jd.blockchain.utils.Bytes; @@ -116,7 +116,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHeight") long blockHeight) { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHeight); - TransactionSet txSet = ledger.getTransactionSet(block); + TransactionQuery txSet = ledger.getTransactionSet(block); return txSet.getTotalCount(); } @@ -126,7 +126,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "blockHash") HashDigest blockHash) { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); - TransactionSet txSet = ledger.getTransactionSet(block); + TransactionQuery txSet = ledger.getTransactionSet(block); return txSet.getTotalCount(); } @@ -135,7 +135,7 @@ public class LedgerQueryController implements BlockchainQueryService { public long getTransactionTotalCount(@PathVariable(name = "ledgerHash") HashDigest ledgerHash) { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - TransactionSet txSet = ledger.getTransactionSet(block); + TransactionQuery txSet = ledger.getTransactionSet(block); return txSet.getTotalCount(); } @@ -146,7 +146,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getTotalCount(); + return dataAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/accounts/count") @@ -156,7 +156,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getTotalCount(); + return dataAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/count") @@ -165,7 +165,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getTotalCount(); + return dataAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/users/count") @@ -175,7 +175,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getTotalCount(); + return userAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/users/count") @@ -185,7 +185,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getTotalCount(); + return userAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/count") @@ -194,7 +194,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getTotalCount(); + return userAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/contracts/count") @@ -204,7 +204,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(height); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getTotalCount(); + return contractAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/hash/{blockHash}/contracts/count") @@ -214,7 +214,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getBlock(blockHash); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getTotalCount(); + return contractAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts/count") @@ -223,7 +223,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getTotalCount(); + return contractAccountSet.getTotal(); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/blocks/height/{blockHeight}/txs") @@ -235,7 +235,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(blockHeight); - TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); + TransactionQuery transactionSet = ledger.getTransactionSet(ledgerBlock); int lastHeightTxTotalNums = 0; if (blockHeight > 0) { @@ -269,7 +269,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock ledgerBlock = ledger.getBlock(blockHash); long height = ledgerBlock.getHeight(); - TransactionSet transactionSet = ledger.getTransactionSet(ledgerBlock); + TransactionQuery transactionSet = ledger.getTransactionSet(ledgerBlock); int lastHeightTxTotalNums = 0; if (height > 0) { @@ -300,7 +300,7 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "contentHash") HashDigest contentHash) { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - TransactionSet txset = ledger.getTransactionSet(block); + TransactionQuery txset = ledger.getTransactionSet(block); return txset.get(contentHash); } @@ -310,8 +310,8 @@ public class LedgerQueryController implements BlockchainQueryService { @PathVariable(name = "contentHash") HashDigest contentHash) { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); - TransactionSet txset = ledger.getTransactionSet(block); - return txset.getTxState(contentHash); + TransactionQuery txset = ledger.getTransactionSet(block); + return txset.getState(contentHash); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/users/address/{address}") @@ -321,7 +321,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - return userAccountSet.getUser(address); + return userAccountSet.getAccount(address); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/accounts/address/{address}") @@ -331,7 +331,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - return dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + return dataAccountSet.getAccount(Bytes.fromBase58(address)); } @RequestMapping(method = { RequestMethod.GET, @@ -345,7 +345,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; long ver; @@ -394,7 +394,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); KVDataEntry[] entries = new KVDataEntry[keys.length]; long ver = -1; @@ -429,7 +429,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccount.getDataEntriesTotalCount()); return dataAccount.getDataEntries(pages[0], pages[1]); @@ -443,7 +443,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - DataAccount dataAccount = dataAccountSet.getDataAccount(Bytes.fromBase58(address)); + DataAccount dataAccount = dataAccountSet.getAccount(Bytes.fromBase58(address)); return dataAccount.getDataEntriesTotalCount(); } @@ -455,7 +455,7 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - return contractAccountSet.getContract(Bytes.fromBase58(address)); + return contractAccountSet.getAccount(Bytes.fromBase58(address)); } /** @@ -474,8 +474,8 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); UserAccountQuery userAccountSet = ledger.getUserAccountSet(block); - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotalCount()); - return userAccountSet.getAccounts(pages[0], pages[1]); + int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) userAccountSet.getTotal()); + return userAccountSet.getHeaders(pages[0], pages[1]); } /** @@ -494,8 +494,8 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); DataAccountQuery dataAccountSet = ledger.getDataAccountSet(block); - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotalCount()); - return dataAccountSet.getAccounts(pages[0], pages[1]); + int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) dataAccountSet.getTotal()); + return dataAccountSet.getHeaders(pages[0], pages[1]); } @RequestMapping(method = RequestMethod.GET, path = "ledgers/{ledgerHash}/contracts") @@ -506,8 +506,8 @@ public class LedgerQueryController implements BlockchainQueryService { LedgerQuery ledger = ledgerService.getLedger(ledgerHash); LedgerBlock block = ledger.getLatestBlock(); ContractAccountQuery contractAccountSet = ledger.getContractAccountSet(block); - int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotalCount()); - return contractAccountSet.getAccounts(pages[0], pages[1]); + int pages[] = QueryUtil.calFromIndexAndCount(fromIndex, count, (int) contractAccountSet.getTotal()); + return contractAccountSet.getHeaders(pages[0], pages[1]); } } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java index 4cb241e6..70ca67b7 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/IntegrationTest.java @@ -336,21 +336,21 @@ public class IntegrationTest { // getDataAccountCount according to blockhash for (int i = 0; i < ledgerHeight + 1; i++) { LedgerBlock expectBlock = ledgerOfNode0.getBlock(i); - long expectDataCount = ledgerOfNode0.getDataAccountSet(expectBlock).getTotalCount(); + long expectDataCount = ledgerOfNode0.getDataAccountSet(expectBlock).getTotal(); long actualDataCount = blockchainService.getDataAccountCount(ledgerHash, expectBlock.getHash()); } // getUserCount according to blockhash for (int i = 0; i < ledgerHeight + 1; i++) { LedgerBlock expectBlock = ledgerOfNode0.getBlock(i); - long expectUserCount = ledgerOfNode0.getUserAccountSet(expectBlock).getTotalCount(); + long expectUserCount = ledgerOfNode0.getUserAccountSet(expectBlock).getTotal(); long actualUserCount = blockchainService.getUserCount(ledgerHash, expectBlock.getHash()); } // getContractCount according to blockhash for (int i = 0; i < ledgerHeight + 1; i++) { LedgerBlock expectBlock = ledgerOfNode0.getBlock(i); - long expectContractCount = ledgerOfNode0.getContractAccountSet(expectBlock).getTotalCount(); + long expectContractCount = ledgerOfNode0.getContractAccountSet(expectBlock).getTotal(); long actualContractCount = blockchainService.getContractCount(ledgerHash, expectBlock.getHash()); } @@ -372,9 +372,9 @@ public class IntegrationTest { // expect block acount total LedgerBlock ledgerBlock = ledgerOfNode0.getBlock(i); expectTransactionTotal = ledgerOfNode0.getTransactionSet(ledgerBlock).getTotalCount(); - expectUserTotal = ledgerOfNode0.getUserAccountSet(ledgerBlock).getTotalCount(); - expectDataTotal = ledgerOfNode0.getDataAccountSet(ledgerBlock).getTotalCount(); - expectContractTotal = ledgerOfNode0.getContractAccountSet(ledgerBlock).getTotalCount(); + expectUserTotal = ledgerOfNode0.getUserAccountSet(ledgerBlock).getTotal(); + expectDataTotal = ledgerOfNode0.getDataAccountSet(ledgerBlock).getTotal(); + expectContractTotal = ledgerOfNode0.getContractAccountSet(ledgerBlock).getTotal(); } // getTransactionTotalCount @@ -579,7 +579,7 @@ public class IntegrationTest { Node node0 = context.getNode(0); LedgerQuery ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); LedgerBlock block = ledgerOfNode0.getBlock(txResp.getBlockHeight()); - byte[] contractCodeInDb = ledgerOfNode0.getContractAccountSet(block).getContract(contractDeployKey.getAddress()) + byte[] contractCodeInDb = ledgerOfNode0.getContractAccountSet(block).getAccount(contractDeployKey.getAddress()) .getChainCode(); txContentHash = ptx.getHash(); @@ -659,9 +659,9 @@ public class IntegrationTest { LedgerQuery ledgerOfNode0 = node0.getLedgerManager().getLedger(ledgerHash); LedgerBlock block = ledgerOfNode0.getBlock(txResp.getBlockHeight()); - BytesValue val1InDb = ledgerOfNode0.getDataAccountSet(block).getDataAccount(contractDataKey.getAddress()) + BytesValue val1InDb = ledgerOfNode0.getDataAccountSet(block).getAccount(contractDataKey.getAddress()) .getBytes("A"); - BytesValue val2InDb = ledgerOfNode0.getDataAccountSet(block).getDataAccount(contractDataKey.getAddress()) + BytesValue val2InDb = ledgerOfNode0.getDataAccountSet(block).getAccount(contractDataKey.getAddress()) .getBytes(KEY_TOTAL); } diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java index 33a9f9f4..36ef321f 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeTest.java @@ -117,19 +117,19 @@ public class LedgerInitializeTest { PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); - UserAccount user0_0 = userset0.getUser(address0); + UserAccount user0_0 = userset0.getAccount(address0); PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); - UserAccount user1_0 = userset0.getUser(address1); + UserAccount user1_0 = userset0.getAccount(address1); PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); - UserAccount user2_0 = userset0.getUser(address2); + UserAccount user2_0 = userset0.getAccount(address2); PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); - UserAccount user3_0 = userset0.getUser(address3); + UserAccount user3_0 = userset0.getAccount(address3); } public static LedgerInitProperties loadInitSetting() { diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java index f1ff6770..8ee650cc 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerInitializeWebTest.java @@ -305,19 +305,19 @@ public class LedgerInitializeWebTest { PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); - UserAccount user0_0 = userset0.getUser(address0); + UserAccount user0_0 = userset0.getAccount(address0); PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); - UserAccount user1_0 = userset0.getUser(address1); + UserAccount user1_0 = userset0.getAccount(address1); PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); - UserAccount user2_0 = userset0.getUser(address2); + UserAccount user2_0 = userset0.getAccount(address2); PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); - UserAccount user3_0 = userset0.getUser(address3); + UserAccount user3_0 = userset0.getAccount(address3); } public static LedgerInitProperties loadInitSetting_1() { diff --git a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java index ecc66287..c9d74a1d 100644 --- a/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java +++ b/source/test/test-integration/src/main/java/test/com/jd/blockchain/intgr/perf/LedgerPerformanceTest.java @@ -282,7 +282,7 @@ public class LedgerPerformanceTest { ConsoleUtils.info("\r\n\r\n================= 准备测试交易 [执行合约] ================="); LedgerBlock latestBlock = ledger.getLatestBlock(); - LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledger.getLedgerData(latestBlock); LedgerEditor newEditor = ledger.createNextBlock(); TransactionBatchProcessor txProc = new TransactionBatchProcessor(DEFAULT_SECURITY_MANAGER, newEditor, ledger, opHandler); @@ -315,7 +315,7 @@ public class LedgerPerformanceTest { long batchStartTs = System.currentTimeMillis(); for (int i = 0; i < batchCount; i++) { LedgerBlock latestBlock = ledger.getLatestBlock(); - LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledger.getLedgerData(latestBlock); if (statistic) { ConsoleUtils.info("------ 开始执行交易, 即将生成区块[高度:%s] ------", (latestBlock.getHeight() + 1)); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java index c20d8e04..9c3b3320 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationBase.java @@ -240,7 +240,7 @@ public class IntegrationBase { if (keyPairType == KeyPairType.DATAACCOUNT) { assertNotNull(ledgerRepository.getDataAccountSet(ledgerRepository.getLatestBlock()) - .getDataAccount(keyPair.getAddress())); + .getAccount(keyPair.getAddress())); } System.out.printf("validKeyPair end %s \r\n", index); } @@ -264,7 +264,7 @@ public class IntegrationBase { if (keyPairType == KeyPairType.DATAACCOUNT) { assertNotNull(ledgerRepository.getDataAccountSet(ledgerRepository.getLatestBlock()) - .getDataAccount(keyPair.getAddress())); + .getAccount(keyPair.getAddress())); } countDownLatch.countDown(); } @@ -527,7 +527,7 @@ public class IntegrationBase { LedgerBlock block = ledgerRepository.getBlock(txResp.getBlockHeight()); byte[] contractCodeInDb = ledgerRepository.getContractAccountSet(block) - .getContract(contractDeployKey.getAddress()).getChainCode(); + .getAccount(contractDeployKey.getAddress()).getChainCode(); assertArrayEquals(contractCode, contractCodeInDb); // execute the contract; diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java index ef9ad6b1..28efbdb2 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4Bftsmart.java @@ -153,7 +153,7 @@ public class IntegrationTest4Bftsmart { long participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); - long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotal(); System.out.printf("before add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); @@ -164,7 +164,7 @@ public class IntegrationTest4Bftsmart { participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); - userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotal(); System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java index d2520ca5..c8426253 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTest4MQ.java @@ -149,7 +149,7 @@ public class IntegrationTest4MQ { long participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); - long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + long userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotal(); System.out.printf("before add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); @@ -160,7 +160,7 @@ public class IntegrationTest4MQ { participantCount = ledgerRepository.getAdminInfo(ledgerRepository.retrieveLatestBlock()).getParticipantCount(); - userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotalCount(); + userCount = ledgerRepository.getUserAccountSet(ledgerRepository.retrieveLatestBlock()).getTotal(); System.out.printf("after add participant: participantCount = %d, userCount = %d\r\n", (int)participantCount, (int)userCount); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java index 51be6d8f..50ccb64c 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/IntegrationTestAll4Redis.java @@ -214,21 +214,21 @@ public class IntegrationTestAll4Redis { assertEquals(ledgerRepository.retrieveLatestBlockHeight(), txResp.getBlockHeight()); assertEquals("Value_A_0", ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getBytes("A").getValue().toUTF8String()); + .getAccount(dataKey.getAddress()).getBytes("A").getValue().toUTF8String()); assertEquals("Value_B_0", ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getBytes("B").getValue().toUTF8String()); + .getAccount(dataKey.getAddress()).getBytes("B").getValue().toUTF8String()); assertEquals("Value_C_0", ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getBytes("C").getValue().toUTF8String()); + .getAccount(dataKey.getAddress()).getBytes("C").getValue().toUTF8String()); assertEquals("Value_D_0", ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getBytes("D").getValue().toUTF8String()); + .getAccount(dataKey.getAddress()).getBytes("D").getValue().toUTF8String()); assertEquals(0, ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getDataVersion("A")); + .getAccount(dataKey.getAddress()).getDataVersion("A")); assertEquals(0, ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getDataVersion("B")); + .getAccount(dataKey.getAddress()).getDataVersion("B")); assertEquals(0, ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getDataVersion("C")); + .getAccount(dataKey.getAddress()).getDataVersion("C")); assertEquals(0, ledgerRepository.getDataAccountSet(ledgerRepository.retrieveLatestBlock()) - .getDataAccount(dataKey.getAddress()).getDataVersion("D")); + .getAccount(dataKey.getAddress()).getDataVersion("D")); return; } @@ -321,7 +321,7 @@ public class IntegrationTestAll4Redis { assertEquals(txResp.getContentHash(), transactionHash); assertEquals(txResp.getBlockHash(), ledgerRepository.getLatestBlockHash()); assertNotNull(ledgerRepository.getDataAccountSet(ledgerRepository.getLatestBlock()) - .getDataAccount(dataAccount.getAddress())); + .getAccount(dataAccount.getAddress())); return dataAccount; } @@ -383,7 +383,7 @@ public class IntegrationTestAll4Redis { txTpl.dataAccounts().register(contractDataKey.getIdentity()); // dataAccountSet.getDataAccount(dataAddress) DataAccount dataAccount = ledgerRepository.getDataAccountSet(ledgerRepository.getLatestBlock()) - .getDataAccount(contractDataKey.getAddress()); + .getAccount(contractDataKey.getAddress()); DataAccountKVSetOperation kvsetOP = txTpl.dataAccount(contractDataKey.getAddress()) .setText("A", "Value_A_0", -1).setText("B", "Value_B_0", -1) @@ -407,7 +407,7 @@ public class IntegrationTestAll4Redis { LedgerBlock block = ledgerRepository.getBlock(txResp.getBlockHeight()); byte[] contractCodeInDb = ledgerRepository.getContractAccountSet(block) - .getContract(contractDeployKey.getAddress()).getChainCode(); + .getAccount(contractDeployKey.getAddress()).getChainCode(); assertArrayEquals(contractCode, contractCodeInDb); txContentHash = ptx.getHash(); @@ -449,9 +449,9 @@ public class IntegrationTestAll4Redis { AsymmetricKeypair key = Crypto.getSignatureFunction("ED25519").generateKeypair(); PubKey pubKey = key.getPubKey(); Bytes dataAddress = AddressEncoding.generateAddress(pubKey); - assertEquals(dataAddress, dataAccountSet.getDataAccount(dataAddress).getAddress()); + assertEquals(dataAddress, dataAccountSet.getAccount(dataAddress).getAddress()); assertEquals("hello", - dataAccountSet.getDataAccount(dataAddress).getBytes(KEY_TOTAL, -1).getValue().toUTF8String()); + dataAccountSet.getAccount(dataAddress).getBytes(KEY_TOTAL, -1).getValue().toUTF8String()); // 验证userAccount,从合约内部赋值,然后外部验证;内部定义动态key,外部不便于得到,临时屏蔽; // UserAccountSet userAccountSet = @@ -478,9 +478,9 @@ public class IntegrationTestAll4Redis { // 验证结果; LedgerBlock block = ledgerRepository.getBlock(txResp.getBlockHeight()); - BytesValue val1InDb = ledgerRepository.getDataAccountSet(block).getDataAccount(contractDataKey.getAddress()) + BytesValue val1InDb = ledgerRepository.getDataAccountSet(block).getAccount(contractDataKey.getAddress()) .getBytes("A"); - BytesValue val2InDb = ledgerRepository.getDataAccountSet(block).getDataAccount(contractDataKey.getAddress()) + BytesValue val2InDb = ledgerRepository.getDataAccountSet(block).getAccount(contractDataKey.getAddress()) .getBytes(KEY_TOTAL); assertEquals("Value_A_0", val1InDb.getValue().toUTF8String()); assertEquals("total value,dataAccount", val2InDb.getValue().toUTF8String()); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java index cf30df83..217c0491 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeTest.java @@ -151,22 +151,22 @@ public class LedgerInitializeTest { PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); - UserAccount user0_0 = userset0.getUser(address0); + UserAccount user0_0 = userset0.getAccount(address0); assertNotNull(user0_0); PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); - UserAccount user1_0 = userset0.getUser(address1); + UserAccount user1_0 = userset0.getAccount(address1); assertNotNull(user1_0); PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); - UserAccount user2_0 = userset0.getUser(address2); + UserAccount user2_0 = userset0.getAccount(address2); assertNotNull(user2_0); PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); - UserAccount user3_0 = userset0.getUser(address3); + UserAccount user3_0 = userset0.getAccount(address3); assertNotNull(user3_0); } diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java index d8acc669..ec77fbee 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/initializer/LedgerInitializeWeb4Nodes.java @@ -158,24 +158,24 @@ public class LedgerInitializeWeb4Nodes { PubKey pubKey0 = KeyGenUtils.decodePubKey(PUB_KEYS[0]); Bytes address0 = AddressEncoding.generateAddress(pubKey0); System.out.printf("localNodeAddress0 = %s \r\n", address0.toBase58()); - UserAccount user0_0 = userset0.getUser(address0); + UserAccount user0_0 = userset0.getAccount(address0); assertNotNull(user0_0); PubKey pubKey1 = KeyGenUtils.decodePubKey(PUB_KEYS[1]); Bytes address1 = AddressEncoding.generateAddress(pubKey1); - UserAccount user1_0 = userset0.getUser(address1); + UserAccount user1_0 = userset0.getAccount(address1); assertNotNull(user1_0); System.out.printf("localNodeAddress1 = %s \r\n", address1.toBase58()); PubKey pubKey2 = KeyGenUtils.decodePubKey(PUB_KEYS[2]); Bytes address2 = AddressEncoding.generateAddress(pubKey2); - UserAccount user2_0 = userset0.getUser(address2); + UserAccount user2_0 = userset0.getAccount(address2); assertNotNull(user2_0); System.out.printf("localNodeAddress2 = %s \r\n", address2.toBase58()); PubKey pubKey3 = KeyGenUtils.decodePubKey(PUB_KEYS[3]); Bytes address3 = AddressEncoding.generateAddress(pubKey3); - UserAccount user3_0 = userset0.getUser(address3); + UserAccount user3_0 = userset0.getAccount(address3); assertNotNull(user3_0); System.out.printf("localNodeAddress3 = %s \r\n", address3.toBase58()); diff --git a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java index 5cea4d68..f9ca3833 100644 --- a/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java +++ b/source/test/test-integration/src/test/java/test/com/jd/blockchain/intgr/ledger/LedgerBlockGeneratingTest.java @@ -77,7 +77,7 @@ public class LedgerBlockGeneratingTest { LedgerBlock latestBlock = ledger.getLatestBlock(); assertEquals(height + i, latestBlock.getHeight()); - LedgerDataQuery previousDataSet = ledger.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledger.getLedgerData(latestBlock); ConsoleUtils.info("------ 开始执行交易, 即将生成区块[%s] ------", (latestBlock.getHeight() + 1)); long startTs = System.currentTimeMillis(); diff --git a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java index 8402a07a..87b3acc6 100644 --- a/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java +++ b/source/test/test-ledger/src/test/java/test/com/jd/blockchain/test/ledger/RolesAuthorizationTest.java @@ -127,7 +127,8 @@ public class RolesAuthorizationTest { new TransactionDefiner() { @Override public void define(TransactionBuilder txBuilder) { - txBuilder.security().roles().configure("NORMAL").enable(LedgerPermission.REGISTER_DATA_ACCOUNT) + txBuilder.security().roles().configure("NORMAL") + .enable(LedgerPermission.REGISTER_DATA_ACCOUNT) .disable(LedgerPermission.REGISTER_USER) .enable(TransactionPermission.CONTRACT_OPERATION); @@ -215,9 +216,9 @@ public class RolesAuthorizationTest { private void assertPredefineData(HashDigest ledgerHash, MemoryKVStorage storage) { LedgerManager ledgerManager = new LedgerManager(); LedgerRepository ledger = ledgerManager.register(ledgerHash, storage); - UserAccount newUser = ledger.getUserAccountSet().getUser(NEW_USER.getAddress()); + UserAccount newUser = ledger.getUserAccountSet().getAccount(NEW_USER.getAddress()); assertNotNull(newUser); - DataAccount dataAccount = ledger.getDataAccountSet().getDataAccount(DATA_ACCOUNT_ID.getAddress()); + DataAccount dataAccount = ledger.getDataAccountSet().getAccount(DATA_ACCOUNT_ID.getAddress()); assertNotNull(dataAccount); UserRoles userRoles = ledger.getAdminSettings().getAuthorizations().getUserRoles(NEW_USER.getAddress()); diff --git a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java index aeeef656..266f7973 100644 --- a/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java +++ b/source/tools/tools-mocker/src/main/java/com/jd/blockchain/mocker/MockerNodeContext.java @@ -440,7 +440,7 @@ public class MockerNodeContext implements BlockchainQueryService { public OperationResult[] txProcess(TransactionRequest txRequest) { LedgerEditor newEditor = ledgerRepository.createNextBlock(); LedgerBlock latestBlock = ledgerRepository.getLatestBlock(); - LedgerDataQuery previousDataSet = ledgerRepository.getDataSet(latestBlock); + LedgerDataQuery previousDataSet = ledgerRepository.getLedgerData(latestBlock); TransactionBatchProcessor txProc = new TransactionBatchProcessor(getSecurityManager(), newEditor, ledgerRepository, opHandler); TransactionResponse txResp = txProc.schedule(txRequest); From 06811e629ed604931aa32e8b586446ac20170f59 Mon Sep 17 00:00:00 2001 From: huanghaiquan Date: Thu, 19 Sep 2019 09:09:16 +0800 Subject: [PATCH 124/124] Refactored types of ledger core module; --- .../com/jd/blockchain/consts/DataCodes.java | 20 +++--- .../ledger/core/ContractAccount.java | 6 +- .../ledger/core/ContractAccountSet.java | 18 +++--- .../blockchain/ledger/core/DataAccount.java | 8 +-- .../ledger/core/DataAccountSet.java | 16 ++--- .../{BaseAccount.java => MerkleAccount.java} | 12 ++-- ...{AccountSet.java => MerkleAccountSet.java} | 62 ++++++++++--------- .../ledger/core/MerkleProvable.java | 6 +- .../blockchain/ledger/core/UserAccount.java | 4 +- .../ledger/core/UserAccountSet.java | 16 ++--- .../ledger/core/AccountSetTest.java | 12 ++-- .../ledger/core/BaseAccountTest.java | 4 +- .../ledger/core/LedgerAccountTest.java | 4 +- .../jd/blockchain/ledger/AccountHeader.java | 7 +-- .../jd/blockchain/ledger/MerkleSnapshot.java | 15 +++++ 15 files changed, 113 insertions(+), 97 deletions(-) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{BaseAccount.java => MerkleAccount.java} (91%) rename source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/{AccountSet.java => MerkleAccountSet.java} (81%) create mode 100644 source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/MerkleSnapshot.java diff --git a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java index 279daea0..e3e2506c 100644 --- a/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java +++ b/source/base/src/main/java/com/jd/blockchain/consts/DataCodes.java @@ -7,6 +7,8 @@ package com.jd.blockchain.consts; * */ public interface DataCodes { + + public static final int MERKLE_SNAPSHOT = 0x070; public static final int BYTES_VALUE = 0x080; @@ -57,13 +59,13 @@ public interface DataCodes { public static final int TX_RESPONSE = 0x360; public static final int TX_OP_RESULT = 0x370; - + public static final int TX_OP_ROLE_CONFIGURE = 0x371; - + public static final int TX_OP_ROLE_CONFIGURE_ENTRY = 0x372; - + public static final int TX_OP_USER_ROLES_AUTHORIZE = 0x373; - + public static final int TX_OP_USER_ROLE_AUTHORIZE_ENTRY = 0x374; // enum types of permissions; @@ -74,11 +76,11 @@ public interface DataCodes { public static final int PRIVILEGE_SET = 0x410; public static final int ROLE_SET = 0x411; - + public static final int SECURITY_INIT_SETTING = 0x420; public static final int SECURITY_ROLE_INIT_SETTING = 0x421; - + public static final int SECURITY_USER_AUTH_INIT_SETTING = 0x422; // contract types of metadata; @@ -103,7 +105,6 @@ public interface DataCodes { // // public static final int METADATA_CRYPTO_SETTING = 0x642; - // public static final int METADATA_CONSENSUS_NODE = 0x630; public static final int METADATA_CONSENSUS_SETTING = 0x631; @@ -113,10 +114,10 @@ public interface DataCodes { public static final int METADATA_PARTICIPANT_STATE_INFO = 0x641; public static final int METADATA_CRYPTO_SETTING = 0x642; - + public static final int METADATA_CRYPTO_SETTING_PROVIDER = 0x643; - // public static final int ACCOUNT = 0x700; +// public static final int ACCOUNT = 0x700; public static final int ACCOUNT_HEADER = 0x710; @@ -194,5 +195,4 @@ public interface DataCodes { public static final int CONSENSUS_MSGQUEUE_BLOCK_SETTINGS = CONSENSUS_MSGQUEUE | 0x05; - } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java index 9f4a8622..69dfac2c 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccount.java @@ -14,9 +14,9 @@ public class ContractAccount implements ContractInfo { private static final Bytes CHAIN_CODE_KEY = Bytes.fromString("CHAIN-CODE"); - private BaseAccount accBase; + private MerkleAccount accBase; - public ContractAccount(BaseAccount accBase) { + public ContractAccount(MerkleAccount accBase) { this.accBase = accBase; } @@ -57,7 +57,7 @@ public class ContractAccount implements ContractInfo { } public long getChaincodeVersion() { - return accBase.getKeyVersion(CHAIN_CODE_KEY); + return accBase.getVersion(CHAIN_CODE_KEY); } public long setProperty(Bytes key, String value, long version) { diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java index fc927d9e..5f4c7855 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/ContractAccountSet.java @@ -13,22 +13,22 @@ import com.jd.blockchain.utils.Transactional; public class ContractAccountSet implements Transactional, ContractAccountQuery { - private AccountSet accountSet; + private MerkleAccountSet accountSet; public ContractAccountSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, AccountAccessPolicy accessPolicy) { - accountSet = new AccountSet(cryptoSetting, prefix, exStorage, verStorage, accessPolicy); + accountSet = new MerkleAccountSet(cryptoSetting, prefix, exStorage, verStorage, accessPolicy); } public ContractAccountSet(HashDigest dataRootHash, CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly, AccountAccessPolicy accessPolicy) { - accountSet = new AccountSet(dataRootHash, cryptoSetting, prefix, exStorage, verStorage, readonly, accessPolicy); + accountSet = new MerkleAccountSet(dataRootHash, cryptoSetting, prefix, exStorage, verStorage, readonly, accessPolicy); } @Override public AccountHeader[] getHeaders(int fromIndex, int count) { - return accountSet.getAccounts(fromIndex, count); + return accountSet.getHeaders(fromIndex, count); } public boolean isReadonly() { @@ -51,7 +51,7 @@ public class ContractAccountSet implements Transactional, ContractAccountQuery { */ @Override public long getTotal() { - return accountSet.getTotalCount(); + return accountSet.getTotal(); } @Override @@ -66,7 +66,7 @@ public class ContractAccountSet implements Transactional, ContractAccountQuery { @Override public ContractAccount getAccount(Bytes address) { - BaseAccount accBase = accountSet.getAccount(address); + MerkleAccount accBase = accountSet.getAccount(address); return new ContractAccount(accBase); } @@ -77,7 +77,7 @@ public class ContractAccountSet implements Transactional, ContractAccountQuery { @Override public ContractAccount getAccount(Bytes address, long version) { - BaseAccount accBase = accountSet.getAccount(address, version); + MerkleAccount accBase = accountSet.getAccount(address, version); return new ContractAccount(accBase); } @@ -92,7 +92,7 @@ public class ContractAccountSet implements Transactional, ContractAccountQuery { */ public ContractAccount deploy(Bytes address, PubKey pubKey, DigitalSignature addressSignature, byte[] chaincode) { // TODO: 校验和记录合约地址签名; - BaseAccount accBase = accountSet.register(address, pubKey); + MerkleAccount accBase = accountSet.register(address, pubKey); ContractAccount contractAcc = new ContractAccount(accBase); contractAcc.setChaincode(chaincode, -1); return contractAcc; @@ -107,7 +107,7 @@ public class ContractAccountSet implements Transactional, ContractAccountQuery { * @return 返回链码的新版本号; */ public long update(Bytes address, byte[] chaincode, long version) { - BaseAccount accBase = accountSet.getAccount(address); + MerkleAccount accBase = accountSet.getAccount(address); ContractAccount contractAcc = new ContractAccount(accBase); return contractAcc.setChaincode(chaincode, version); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java index cee3c29e..afaab416 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccount.java @@ -13,9 +13,9 @@ import com.jd.blockchain.utils.Bytes; public class DataAccount implements AccountHeader, MerkleProvable { - private BaseAccount baseAccount; + private MerkleAccount baseAccount; - public DataAccount(BaseAccount accBase) { + public DataAccount(MerkleAccount accBase) { this.baseAccount = accBase; } @@ -135,7 +135,7 @@ public class DataAccount implements AccountHeader, MerkleProvable { * @return */ public long getDataVersion(String key) { - return baseAccount.getKeyVersion(Bytes.fromString(key)); + return baseAccount.getVersion(Bytes.fromString(key)); } /** @@ -146,7 +146,7 @@ public class DataAccount implements AccountHeader, MerkleProvable { * @return */ public long getDataVersion(Bytes key) { - return baseAccount.getKeyVersion(key); + return baseAccount.getVersion(key); } /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java index 8a78f6f0..f693211b 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/DataAccountSet.java @@ -13,22 +13,22 @@ import com.jd.blockchain.utils.Transactional; public class DataAccountSet implements Transactional, DataAccountQuery { - private AccountSet accountSet; + private MerkleAccountSet accountSet; public DataAccountSet(CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, AccountAccessPolicy accessPolicy) { - accountSet = new AccountSet(cryptoSetting, prefix, exStorage, verStorage, accessPolicy); + accountSet = new MerkleAccountSet(cryptoSetting, prefix, exStorage, verStorage, accessPolicy); } public DataAccountSet(HashDigest dataRootHash, CryptoSetting cryptoSetting, String prefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly, AccountAccessPolicy accessPolicy) { - accountSet = new AccountSet(dataRootHash, cryptoSetting, prefix, exStorage, verStorage, readonly, accessPolicy); + accountSet = new MerkleAccountSet(dataRootHash, cryptoSetting, prefix, exStorage, verStorage, readonly, accessPolicy); } @Override public AccountHeader[] getHeaders(int fromIndex, int count) { - return accountSet.getAccounts(fromIndex, count); + return accountSet.getHeaders(fromIndex, count); } public boolean isReadonly() { @@ -46,7 +46,7 @@ public class DataAccountSet implements Transactional, DataAccountQuery { @Override public long getTotal() { - return accountSet.getTotalCount(); + return accountSet.getTotal(); } @Override @@ -64,7 +64,7 @@ public class DataAccountSet implements Transactional, DataAccountQuery { public DataAccount register(Bytes address, PubKey pubKey, DigitalSignature addressSignature) { // TODO: 未实现对地址签名的校验和记录; - BaseAccount accBase = accountSet.register(address, pubKey); + MerkleAccount accBase = accountSet.register(address, pubKey); return new DataAccount(accBase); } @@ -82,7 +82,7 @@ public class DataAccountSet implements Transactional, DataAccountQuery { */ @Override public DataAccount getAccount(Bytes address) { - BaseAccount accBase = accountSet.getAccount(address); + MerkleAccount accBase = accountSet.getAccount(address); if (accBase == null) { return null; } @@ -91,7 +91,7 @@ public class DataAccountSet implements Transactional, DataAccountQuery { @Override public DataAccount getAccount(Bytes address, long version) { - BaseAccount accBase = accountSet.getAccount(address, version); + MerkleAccount accBase = accountSet.getAccount(address, version); return new DataAccount(accBase); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleAccount.java similarity index 91% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleAccount.java index 1c9b778f..49ef5753 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/BaseAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleAccount.java @@ -20,7 +20,7 @@ import com.jd.blockchain.utils.Transactional; * @author huanghaiquan * */ -public class BaseAccount implements AccountHeader, MerkleProvable, Transactional { +public class MerkleAccount implements AccountHeader, MerkleProvable, Transactional { private BlockchainIdentity bcid; @@ -38,7 +38,7 @@ public class BaseAccount implements AccountHeader, MerkleProvable, Transactional * @param address * @param pubKey */ - public BaseAccount(Bytes address, PubKey pubKey, CryptoSetting cryptoSetting, String keyPrefix, + public MerkleAccount(Bytes address, PubKey pubKey, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage) { this(address, pubKey, null, cryptoSetting, keyPrefix, exStorage, verStorage, false); } @@ -58,7 +58,7 @@ public class BaseAccount implements AccountHeader, MerkleProvable, Transactional * @param verStorage * @param accessPolicy */ - public BaseAccount(BlockchainIdentity bcid, CryptoSetting cryptoSetting, String keyPrefix, + public MerkleAccount(BlockchainIdentity bcid, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage) { this(bcid, null, cryptoSetting, keyPrefix, exStorage, verStorage, false); } @@ -78,13 +78,13 @@ public class BaseAccount implements AccountHeader, MerkleProvable, Transactional * @param readonly * @param accessPolicy */ - public BaseAccount(Bytes address, PubKey pubKey, HashDigest dataRootHash, CryptoSetting cryptoSetting, + public MerkleAccount(Bytes address, PubKey pubKey, HashDigest dataRootHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly) { this(new BlockchainIdentityData(address, pubKey), dataRootHash, cryptoSetting, keyPrefix, exStorage, verStorage, readonly); } - public BaseAccount(BlockchainIdentity bcid, HashDigest dataRootHash, CryptoSetting cryptoSetting, String keyPrefix, + public MerkleAccount(BlockchainIdentity bcid, HashDigest dataRootHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly) { this.bcid = bcid; this.dataset = new MerkleDataSet(dataRootHash, cryptoSetting, keyPrefix, exStorage, verStorage, readonly); @@ -169,7 +169,7 @@ public class BaseAccount implements AccountHeader, MerkleProvable, Transactional * @param key * @return */ - public long getKeyVersion(Bytes key) { + public long getVersion(Bytes key) { return dataset.getVersion(key); } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleAccountSet.java similarity index 81% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleAccountSet.java index dc4a59cd..d3e507ba 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleAccountSet.java @@ -13,14 +13,16 @@ import com.jd.blockchain.ledger.BytesValue; import com.jd.blockchain.ledger.CryptoSetting; import com.jd.blockchain.ledger.LedgerException; import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.ledger.MerkleSnapshot; import com.jd.blockchain.storage.service.ExPolicyKVStorage; import com.jd.blockchain.storage.service.VersioningKVStorage; import com.jd.blockchain.utils.Bytes; import com.jd.blockchain.utils.Transactional; -public class AccountSet implements Transactional, MerkleProvable { +public class MerkleAccountSet implements Transactional, MerkleProvable, AccountQuery { static { + DataContractRegistry.register(MerkleSnapshot.class); DataContractRegistry.register(AccountHeader.class); } @@ -34,7 +36,7 @@ public class AccountSet implements Transactional, MerkleProvable { * */ // TODO:未考虑大数据量时,由于缺少过期策略,会导致内存溢出的问题; - private Map latestAccountsCache = new HashMap<>(); + private Map latestAccountsCache = new HashMap<>(); private ExPolicyKVStorage baseExStorage; @@ -49,18 +51,19 @@ public class AccountSet implements Transactional, MerkleProvable { public boolean isReadonly() { return merkleDataset.isReadonly(); } - + void setReadonly() { merkleDataset.setReadonly(); } - public AccountSet(CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, + public MerkleAccountSet(CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, AccountAccessPolicy accessPolicy) { this(null, cryptoSetting, keyPrefix, exStorage, verStorage, false, accessPolicy); } - public AccountSet(HashDigest rootHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, - VersioningKVStorage verStorage, boolean readonly, AccountAccessPolicy accessPolicy) { + public MerkleAccountSet(HashDigest rootHash, CryptoSetting cryptoSetting, String keyPrefix, + ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly, + AccountAccessPolicy accessPolicy) { this.keyPrefix = keyPrefix; this.cryptoSetting = cryptoSetting; this.baseExStorage = exStorage; @@ -80,7 +83,7 @@ public class AccountSet implements Transactional, MerkleProvable { return merkleDataset.getProof(key); } - public AccountHeader[] getAccounts(int fromIndex, int count) { + public AccountHeader[] getHeaders(int fromIndex, int count) { byte[][] results = merkleDataset.getLatestValues(fromIndex, count); AccountHeader[] accounts = new AccountHeader[results.length]; @@ -110,17 +113,22 @@ public class AccountSet implements Transactional, MerkleProvable { * * @return */ - public long getTotalCount() { + public long getTotal() { return merkleDataset.getDataCount(); } + @Override + public MerkleAccount getAccount(String address) { + return getAccount(Bytes.fromBase58(address)); + } + /** * 返回最新版本的 Account; * * @param address * @return */ - public BaseAccount getAccount(Bytes address) { + public MerkleAccount getAccount(Bytes address) { return this.getAccount(address, -1); } @@ -149,7 +157,7 @@ public class AccountSet implements Transactional, MerkleProvable { * @return */ public long getVersion(Bytes address) { - VersioningAccount acc = latestAccountsCache.get(address); + InnerVersioningAccount acc = latestAccountsCache.get(address); if (acc != null) { // 已注册尚未提交,也返回 -1; return acc.version == -1 ? 0 : acc.version; @@ -163,15 +171,13 @@ public class AccountSet implements Transactional, MerkleProvable { * * 只有最新版本的账户才能可写的,其它都是只读; * - * @param address - * 账户地址; - * @param version - * 账户版本;如果指定为 -1,则返回最新版本; + * @param address 账户地址; + * @param version 账户版本;如果指定为 -1,则返回最新版本; * @return */ - public BaseAccount getAccount(Bytes address, long version) { + public MerkleAccount getAccount(Bytes address, long version) { version = version < 0 ? -1 : version; - VersioningAccount acc = latestAccountsCache.get(address); + InnerVersioningAccount acc = latestAccountsCache.get(address); if (acc != null && version == -1) { return acc; } else if (acc != null && acc.version == version) { @@ -233,14 +239,14 @@ public class AccountSet implements Transactional, MerkleProvable { * @param pubKey 公钥; * @return 注册成功的账户对象; */ - public BaseAccount register(Bytes address, PubKey pubKey) { + public MerkleAccount register(Bytes address, PubKey pubKey) { if (isReadonly()) { throw new IllegalArgumentException("This AccountSet is readonly!"); } verifyAddressEncoding(address, pubKey); - VersioningAccount cachedAcc = latestAccountsCache.get(address); + InnerVersioningAccount cachedAcc = latestAccountsCache.get(address); if (cachedAcc != null) { if (cachedAcc.version < 0) { // 同一个新账户已经注册,但尚未提交,所以重复注册不会引起任何变化; @@ -267,7 +273,7 @@ public class AccountSet implements Transactional, MerkleProvable { // accExStorage, accVerStorage); String prefix = keyPrefix + address; - VersioningAccount acc = createInstance(address, pubKey, cryptoSetting, prefix, baseExStorage, baseVerStorage, + InnerVersioningAccount acc = createInstance(address, pubKey, cryptoSetting, prefix, baseExStorage, baseVerStorage, -1); latestAccountsCache.put(address, acc); updated = true; @@ -282,15 +288,15 @@ public class AccountSet implements Transactional, MerkleProvable { } } - private VersioningAccount createInstance(Bytes address, PubKey pubKey, CryptoSetting cryptoSetting, + private InnerVersioningAccount createInstance(Bytes address, PubKey pubKey, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, long version) { - return new VersioningAccount(address, pubKey, cryptoSetting, keyPrefix, exStorage, verStorage, version); + return new InnerVersioningAccount(address, pubKey, cryptoSetting, keyPrefix, exStorage, verStorage, version); } - private VersioningAccount deserialize(byte[] bytes, CryptoSetting cryptoSetting, String keyPrefix, + private InnerVersioningAccount deserialize(byte[] bytes, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly, long version) { AccountHeader accInfo = BinaryProtocol.decode(bytes); - return new VersioningAccount(accInfo.getAddress(), accInfo.getPubKey(), accInfo.getRootHash(), cryptoSetting, + return new InnerVersioningAccount(accInfo.getAddress(), accInfo.getPubKey(), accInfo.getRootHash(), cryptoSetting, keyPrefix, exStorage, verStorage, readonly, version); } @@ -309,7 +315,7 @@ public class AccountSet implements Transactional, MerkleProvable { return; } try { - for (VersioningAccount acc : latestAccountsCache.values()) { + for (InnerVersioningAccount acc : latestAccountsCache.values()) { // updated or new created; if (acc.isUpdated() || acc.version < 0) { // 提交更改,更新哈希; @@ -337,7 +343,7 @@ public class AccountSet implements Transactional, MerkleProvable { Bytes[] addresses = new Bytes[latestAccountsCache.size()]; latestAccountsCache.keySet().toArray(addresses); for (Bytes address : addresses) { - VersioningAccount acc = latestAccountsCache.remove(address); + InnerVersioningAccount acc = latestAccountsCache.remove(address); // cancel; if (acc.isUpdated()) { acc.cancel(); @@ -375,7 +381,7 @@ public class AccountSet implements Transactional, MerkleProvable { } - private class VersioningAccount extends BaseAccount { + private class InnerVersioningAccount extends MerkleAccount { // private final BaseAccount account; @@ -386,14 +392,14 @@ public class AccountSet implements Transactional, MerkleProvable { // this.version = version; // } - public VersioningAccount(Bytes address, PubKey pubKey, HashDigest rootHash, CryptoSetting cryptoSetting, + public InnerVersioningAccount(Bytes address, PubKey pubKey, HashDigest rootHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly, long version) { super(address, pubKey, rootHash, cryptoSetting, keyPrefix, exStorage, verStorage, readonly); this.version = version; } - public VersioningAccount(Bytes address, PubKey pubKey, CryptoSetting cryptoSetting, String keyPrefix, + public InnerVersioningAccount(Bytes address, PubKey pubKey, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, long version) { super(address, pubKey, cryptoSetting, keyPrefix, exStorage, verStorage); this.version = version; diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java index f778279f..b41e6856 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/MerkleProvable.java @@ -1,12 +1,10 @@ package com.jd.blockchain.ledger.core; -import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.ledger.MerkleProof; +import com.jd.blockchain.ledger.MerkleSnapshot; import com.jd.blockchain.utils.Bytes; -public interface MerkleProvable { - - HashDigest getRootHash(); +public interface MerkleProvable extends MerkleSnapshot { /** * Get the merkle proof of the latest version of specified key;
        diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java index b9cc88fd..49743f90 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccount.java @@ -19,7 +19,7 @@ public class UserAccount implements UserInfo { private static final Bytes DATA_PUB_KEY = Bytes.fromString("DATA-PUBKEY"); - private BaseAccount baseAccount; + private MerkleAccount baseAccount; @Override public Bytes getAddress() { @@ -36,7 +36,7 @@ public class UserAccount implements UserInfo { return baseAccount.getRootHash(); } - public UserAccount(BaseAccount baseAccount) { + public UserAccount(MerkleAccount baseAccount) { this.baseAccount = baseAccount; } diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java index 440d7fdb..a2e25356 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/UserAccountSet.java @@ -17,23 +17,23 @@ import com.jd.blockchain.utils.Transactional; */ public class UserAccountSet implements Transactional, UserAccountQuery { - private AccountSet accountSet; + private MerkleAccountSet accountSet; public UserAccountSet(CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage simpleStorage, VersioningKVStorage versioningStorage, AccountAccessPolicy accessPolicy) { - accountSet = new AccountSet(cryptoSetting, keyPrefix, simpleStorage, versioningStorage, accessPolicy); + accountSet = new MerkleAccountSet(cryptoSetting, keyPrefix, simpleStorage, versioningStorage, accessPolicy); } public UserAccountSet(HashDigest dataRootHash, CryptoSetting cryptoSetting, String keyPrefix, ExPolicyKVStorage exStorage, VersioningKVStorage verStorage, boolean readonly, AccountAccessPolicy accessPolicy) { - accountSet = new AccountSet(dataRootHash, cryptoSetting, keyPrefix, exStorage, verStorage, readonly, + accountSet = new MerkleAccountSet(dataRootHash, cryptoSetting, keyPrefix, exStorage, verStorage, readonly, accessPolicy); } @Override public AccountHeader[] getHeaders(int fromIndex, int count) { - return accountSet.getAccounts(fromIndex,count); + return accountSet.getHeaders(fromIndex,count); } /** @@ -43,7 +43,7 @@ public class UserAccountSet implements Transactional, UserAccountQuery { */ @Override public long getTotal() { - return accountSet.getTotalCount(); + return accountSet.getTotal(); } public boolean isReadonly() { @@ -71,7 +71,7 @@ public class UserAccountSet implements Transactional, UserAccountQuery { @Override public UserAccount getAccount(Bytes address) { - BaseAccount baseAccount = accountSet.getAccount(address); + MerkleAccount baseAccount = accountSet.getAccount(address); return new UserAccount(baseAccount); } @@ -82,7 +82,7 @@ public class UserAccountSet implements Transactional, UserAccountQuery { @Override public UserAccount getAccount(Bytes address, long version) { - BaseAccount baseAccount = accountSet.getAccount(address, version); + MerkleAccount baseAccount = accountSet.getAccount(address, version); return new UserAccount(baseAccount); } @@ -100,7 +100,7 @@ public class UserAccountSet implements Transactional, UserAccountQuery { * @return 注册成功的用户对象; */ public UserAccount register(Bytes address, PubKey pubKey) { - BaseAccount baseAccount = accountSet.register(address, pubKey); + MerkleAccount baseAccount = accountSet.register(address, pubKey); return new UserAccount(baseAccount); } diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java index d3e565a5..812a8758 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/AccountSetTest.java @@ -14,8 +14,8 @@ import com.jd.blockchain.crypto.service.classic.ClassicCryptoService; import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.core.AccountSet; -import com.jd.blockchain.ledger.core.BaseAccount; +import com.jd.blockchain.ledger.core.MerkleAccountSet; +import com.jd.blockchain.ledger.core.MerkleAccount; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.ledger.core.OpeningAccessPolicy; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; @@ -43,12 +43,12 @@ public class AccountSetTest { cryptoConf.setHashAlgorithm(ClassicAlgorithm.SHA256); String keyPrefix = ""; - AccountSet accset = new AccountSet(cryptoConf, keyPrefix, storage, storage, accessPolicy); + MerkleAccountSet accset = new MerkleAccountSet(cryptoConf, keyPrefix, storage, storage, accessPolicy); BlockchainKeypair userKey = BlockchainKeyGenerator.getInstance().generate(); accset.register(userKey.getAddress(), userKey.getPubKey()); - BaseAccount userAcc = accset.getAccount(userKey.getAddress()); + MerkleAccount userAcc = accset.getAccount(userKey.getAddress()); assertNotNull(userAcc); assertTrue(accset.contains(userKey.getAddress())); @@ -56,8 +56,8 @@ public class AccountSetTest { HashDigest rootHash = accset.getRootHash(); assertNotNull(rootHash); - AccountSet reloadAccSet = new AccountSet(rootHash, cryptoConf, keyPrefix, storage, storage, true, accessPolicy); - BaseAccount reloadUserAcc = reloadAccSet.getAccount(userKey.getAddress()); + MerkleAccountSet reloadAccSet = new MerkleAccountSet(rootHash, cryptoConf, keyPrefix, storage, storage, true, accessPolicy); + MerkleAccount reloadUserAcc = reloadAccSet.getAccount(userKey.getAddress()); assertNotNull(reloadUserAcc); assertTrue(reloadAccSet.contains(userKey.getAddress())); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java index d02f9b11..51c8232b 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/BaseAccountTest.java @@ -13,7 +13,7 @@ import com.jd.blockchain.crypto.service.sm.SMCryptoService; import com.jd.blockchain.ledger.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; import com.jd.blockchain.ledger.BytesData; -import com.jd.blockchain.ledger.core.BaseAccount; +import com.jd.blockchain.ledger.core.MerkleAccount; import com.jd.blockchain.ledger.core.CryptoConfig; import com.jd.blockchain.storage.service.utils.MemoryKVStorage; import com.jd.blockchain.utils.Bytes; @@ -48,7 +48,7 @@ public class BaseAccountTest { BlockchainKeypair bck = BlockchainKeyGenerator.getInstance().generate(); // 新建账户; - BaseAccount baseAccount = new BaseAccount(bck.getIdentity(), cryptoConf, keyPrefix, testStorage, testStorage); + MerkleAccount baseAccount = new MerkleAccount(bck.getIdentity(), cryptoConf, keyPrefix, testStorage, testStorage); assertFalse(baseAccount.isUpdated());// 空的账户; assertFalse(baseAccount.isReadonly()); diff --git a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java index 27dcc438..a50cd41e 100644 --- a/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java +++ b/source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/core/LedgerAccountTest.java @@ -15,7 +15,7 @@ import com.jd.blockchain.crypto.service.classic.ClassicAlgorithm; import com.jd.blockchain.crypto.service.sm.SMAlgorithm; import com.jd.blockchain.ledger.AccountHeader; import com.jd.blockchain.ledger.UserInfo; -import com.jd.blockchain.ledger.core.AccountSet; +import com.jd.blockchain.ledger.core.MerkleAccountSet; import com.jd.blockchain.utils.Bytes; /** @@ -44,7 +44,7 @@ public class LedgerAccountTest { String address = "xxxxxxxxxxxx"; PubKey pubKey = new PubKey(SMAlgorithm.SM2, rawDigestBytes); HashDigest hashDigest = new HashDigest(ClassicAlgorithm.SHA256, rawDigestBytes); - AccountSet.AccountHeaderData accountHeaderData = new AccountSet.AccountHeaderData(Bytes.fromString(address), + MerkleAccountSet.AccountHeaderData accountHeaderData = new MerkleAccountSet.AccountHeaderData(Bytes.fromString(address), pubKey, hashDigest); // encode and decode diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java index 42dc25a0..16f2794b 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/AccountHeader.java @@ -4,20 +4,17 @@ import com.jd.blockchain.binaryproto.DataContract; import com.jd.blockchain.binaryproto.DataField; import com.jd.blockchain.binaryproto.PrimitiveType; import com.jd.blockchain.consts.DataCodes; -import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.PubKey; import com.jd.blockchain.utils.Bytes; @DataContract(code= DataCodes.ACCOUNT_HEADER) -public interface AccountHeader { +public interface AccountHeader extends MerkleSnapshot{ @DataField(order=1, primitiveType = PrimitiveType.BYTES) Bytes getAddress(); @DataField(order=2, primitiveType = PrimitiveType.BYTES) PubKey getPubKey(); - - @DataField(order=3, primitiveType = PrimitiveType.BYTES) - HashDigest getRootHash(); + } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/MerkleSnapshot.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/MerkleSnapshot.java new file mode 100644 index 00000000..1e195559 --- /dev/null +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/MerkleSnapshot.java @@ -0,0 +1,15 @@ +package com.jd.blockchain.ledger; + +import com.jd.blockchain.binaryproto.DataContract; +import com.jd.blockchain.binaryproto.DataField; +import com.jd.blockchain.binaryproto.PrimitiveType; +import com.jd.blockchain.consts.DataCodes; +import com.jd.blockchain.crypto.HashDigest; + +@DataContract(code = DataCodes.MERKLE_SNAPSHOT) +public interface MerkleSnapshot { + + @DataField(order = 0, primitiveType = PrimitiveType.BYTES) + HashDigest getRootHash(); + +}

        jzX1}4nhS~2A`}ma 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

      jzX1}4nhS~2A`}ma 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

    jzX1}4nhS~2A`}ma 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