diff --git a/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java index 5e62a296..20f97863 100644 --- a/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java +++ b/source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java @@ -20,7 +20,6 @@ import com.jd.blockchain.utils.Bytes; */ public class JavaContractCode extends AbstractContractCode { private static final Logger LOGGER = LoggerFactory.getLogger(JavaContractCode.class); - private Module codeModule; private Bytes address; private long version; @@ -42,8 +41,9 @@ public class JavaContractCode extends AbstractContractCode { if (annoContract != null) { if (contractInterface == null) { contractInterface = itf; - }else { - throw new ContractException("One contract definition is only allowed to implement one contract type!"); + } else { + throw new ContractException( + "One contract definition is only allowed to implement one contract type!"); } } } @@ -66,7 +66,20 @@ public class JavaContractCode extends AbstractContractCode { @Override public byte[] processEvent(ContractEventContext eventContext) { - return codeModule.call(new ContractExecution(eventContext)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Start processing event[%s] of contract[%s]...", eventContext.getEvent(), address.toString()); + } + try { + return codeModule.call(new ContractExecution(eventContext)); + } catch (Exception ex) { + LOGGER.error(String.format("Error occurred while processing event[%s] of contract[%s]! --%s", + eventContext.getEvent(), address.toString(), ex.getMessage()), ex); + throw ex; + } finally { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("End processing event[%s] of contract[%s]. ", eventContext.getEvent(), address.toString()); + } + } } protected Object getContractInstance() { 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 2ffcf2fd..1966c716 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 @@ -8,7 +8,7 @@ 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.ContractEventSendOperationHandle; +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; @@ -30,7 +30,7 @@ public class DefaultOperationHandleRegisteration implements OperationHandleRegis opHandles.add(new DataAccountRegisterOperationHandle()); opHandles.add(new UserRegisterOperationHandle()); opHandles.add(new ContractCodeDeployOperationHandle()); - opHandles.add(new ContractEventSendOperationHandle()); + opHandles.add(new JVMContractEventSendOperationHandle()); } /** diff --git a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractEventSendOperationHandle.java b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java similarity index 85% rename from source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractEventSendOperationHandle.java rename to source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java index 14b5b5d4..56a24db1 100644 --- a/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractEventSendOperationHandle.java +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java @@ -21,13 +21,7 @@ import com.jd.blockchain.ledger.core.impl.LedgerQueryService; import com.jd.blockchain.ledger.core.impl.OperationHandleContext; @Service -public class ContractEventSendOperationHandle implements OperationHandle { - - private static final ContractEngine JVM_ENGINE; - - static { - JVM_ENGINE = ContractServiceProviders.getProvider(CONTRACT_SERVICE_PROVIDER).getEngine(); - } +public abstract class AbtractContractEventHandle implements OperationHandle { @Override public boolean support(Class operationType) { @@ -63,15 +57,15 @@ public class ContractEventSendOperationHandle implements OperationHandle { localContractEventContext.setArgs(contractOP.getArgs()).setTransactionRequest(requestContext.getRequest()) .setLedgerContext(ledgerContext); - ContractCode contractCode = JVM_ENGINE.getContract(contract.getAddress(), contract.getChaincodeVersion()); - if (contractCode == null) { - // 装载合约; - contractCode = JVM_ENGINE.setupContract(contract.getAddress(), contract.getChaincodeVersion(), - contract.getChainCode()); - } + + // 装载合约; + ContractCode contractCode = loadContractCode(contract); // 处理合约事件; return contractCode.processEvent(localContractEventContext); } + + protected abstract ContractCode loadContractCode(ContractAccount contract); + } 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 new file mode 100644 index 00000000..ea1b40b7 --- /dev/null +++ b/source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java @@ -0,0 +1,29 @@ +package com.jd.blockchain.ledger.core.impl.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; + +public class JVMContractEventSendOperationHandle extends AbtractContractEventHandle { + + private static final ContractEngine JVM_ENGINE; + + static { + JVM_ENGINE = ContractServiceProviders.getProvider(CONTRACT_SERVICE_PROVIDER).getEngine(); + } + + @Override + 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()); + } + 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 959af606..c0a31321 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 @@ -1,82 +1,50 @@ package test.com.jd.blockchain.ledger; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import com.jd.blockchain.contract.LocalContractEventContext; +import com.jd.blockchain.contract.ContractType; import com.jd.blockchain.contract.engine.ContractCode; -import com.jd.blockchain.ledger.ContractEventSendOperation; -import com.jd.blockchain.ledger.LedgerException; -import com.jd.blockchain.ledger.Operation; +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.ContractAccountSet; -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.LedgerQueryService; -import com.jd.blockchain.ledger.core.impl.OperationHandleContext; -import com.jd.blockchain.ledger.core.impl.handles.ContractLedgerContext; +import com.jd.blockchain.ledger.core.impl.handles.AbtractContractEventHandle; import com.jd.blockchain.utils.Bytes; -public class ContractInvokingHandle implements OperationHandle { +public class ContractInvokingHandle extends AbtractContractEventHandle { - private Map contractInstances = new ConcurrentHashMap(); + private Map contractInstances = new ConcurrentHashMap(); @Override - public byte[] process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService) { - process(op, dataset, requestContext, previousBlockDataset, opHandleContext, ledgerService, null); - - // TODO: return value; - return null; + protected ContractCode loadContractCode(ContractAccount contract) { + return contractInstances.get(contract.getAddress()); } - @Override - public boolean support(Class operationType) { - return ContractEventSendOperation.class.isAssignableFrom(operationType); + public ContractCode setup(Bytes address, Class contractIntf, T instance) { + ContractCodeInstance contract = new ContractCodeInstance(address, 0, contractIntf, instance); + contractInstances.put(address, contract); + return contract; } - public void process(Operation op, LedgerDataSet dataset, TransactionRequestContext requestContext, - LedgerDataSet previousBlockDataset, OperationHandleContext opHandleContext, LedgerService ledgerService, - CompletableFuture contractReturn) { + private static class ContractCodeInstance extends AbstractContractCode { - ContractEventSendOperation contractOP = (ContractEventSendOperation) op; - // 先从账本校验合约的有效性; - // 注意:必须在前一个区块的数据集中进行校验,因为那是经过共识的数据;从当前新区块链数据集校验则会带来攻击风险:未经共识的合约得到执行; - ContractAccountSet contractSet = previousBlockDataset.getContractAccountSet(); - if (!contractSet.contains(contractOP.getContractAddress())) { - throw new LedgerException(String.format("Contract was not registered! --[ContractAddress=%s]", - contractOP.getContractAddress())); - } + private T instance; - // 创建合约的账本上下文实例; - LedgerQueryService queryService = new LedgerQueryService(ledgerService); - ContractLedgerContext ledgerContext = new ContractLedgerContext(queryService, opHandleContext); - - // 先检查合约引擎是否已经加载合约;如果未加载,再从账本中读取合约代码并装载到引擎中执行; - ContractAccount contract = contractSet.getContract(contractOP.getContractAddress()); - if (contract == null) { - throw new LedgerException(String.format("Contract was not registered! --[ContractAddress=%s]", - contractOP.getContractAddress())); + public ContractCodeInstance(Bytes address, long version, Class delaredInterface, T instance) { + super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); + this.instance = instance; } - // 创建合约上下文; - LocalContractEventContext localContractEventContext = new LocalContractEventContext( - requestContext.getRequest().getTransactionContent().getLedgerHash(), contractOP.getEvent()); - localContractEventContext.setArgs(contractOP.getArgs()).setTransactionRequest(requestContext.getRequest()) - .setLedgerContext(ledgerContext); + private static ContractDefinition resolveContractDefinition(Class declaredIntf, Class implementedClass) { + ContractType contractType = ContractType.resolve(declaredIntf); + return new ContractDefinition(contractType, implementedClass); + } - ContractCode contractCode = JVM_ENGINE.getContract(contract.getAddress(), contract.getChaincodeVersion()); - if (contractCode == null) { - // 装载合约; - contractCode = JVM_ENGINE.setupContract(contract.getAddress(), contract.getChaincodeVersion(), - contract.getChainCode()); + @Override + protected T getContractInstance() { + return instance; } - // 处理合约事件; - contractCode.processEvent(localContractEventContext, contractReturn); } } 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 08d9f381..8191e4d6 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,21 +1,37 @@ package test.com.jd.blockchain.ledger; -import static org.junit.Assert.*; +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.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.BlockchainKeyGenerator; import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.ledger.BytesValue; +import com.jd.blockchain.ledger.BytesValueEntry; 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; @@ -27,9 +43,11 @@ 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.TxBuilder; +import com.jd.blockchain.utils.Bytes; public class ContractInvokingTest { static { @@ -44,7 +62,6 @@ public class ContractInvokingTest { private static final String LEDGER_KEY_PREFIX = "LDG://"; - private BlockchainKeypair parti0 = BlockchainKeyGenerator.getInstance().generate(); private BlockchainKeypair parti1 = BlockchainKeyGenerator.getInstance().generate(); private BlockchainKeypair parti2 = BlockchainKeyGenerator.getInstance().generate(); @@ -56,18 +73,69 @@ public class ContractInvokingTest { @Test public void test() { // 初始化账本到指定的存储库; - HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); + HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); // 重新加载账本; LedgerManager ledgerManager = new LedgerManager(); LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); - - OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); - - //构建基于接口调用合约的交易请求; + + // 创建合约处理器; + ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); + + // 创建和加载合约实例; + BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); + Bytes contractAddress = contractKey.getAddress(); + TestContract contractInstance = Mockito.mock(TestContract.class); + contractInvokingHandle.setup(contractAddress, TestContract.class, contractInstance); + + // 注册合约处理器; + DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); + opReg.insertAsTopPriority(contractInvokingHandle); + + // 创建新区块的交易处理器; + LedgerBlock preBlock = ledgerRepo.getLatestBlock(); + LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock()); + LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); + TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, + opReg, ledgerManager); + + // 构建基于接口调用合约的交易请求,用于测试合约调用; + Random rand = new Random(); TxBuilder txBuilder = new TxBuilder(ledgerHash); -// txBuilder.contract(address, contractIntf) - + TestContract contractProxy = txBuilder.contract(contractAddress, TestContract.class); + + String asset = "AK"; + long issueAmount = rand.nextLong(); + when(contractInstance.issue(anyString(), anyLong())).thenReturn(issueAmount); + contractProxy.issue(asset, issueAmount); + + TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); + txReqBuilder.signAsEndpoint(parti0); + txReqBuilder.signAsNode(parti0); + TransactionRequest txReq = txReqBuilder.buildRequest(); + + TransactionResponse resp = txbatchProcessor.schedule(txReq); + verify(contractInstance, times(1)).issue(asset, issueAmount); + OperationResult[] opResults = resp.getContractReturn(); + assertEquals(1, opResults.length); + assertEquals(0, opResults[0].getIndex()); + + byte[] retnBytes = BinaryProtocol.encode(BytesValueEntry.fromInt64(issueAmount), BytesValue.class); + assertArrayEquals(retnBytes, opResults[0].getResult()); + + // 提交区块; + TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); + txResultHandle.commit(); + + LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); + assertEquals(preBlock.getHeight() + 1, latestBlock.getHeight()); + assertEquals(resp.getBlockHeight(), latestBlock.getHeight()); + assertEquals(resp.getBlockHash(), latestBlock.getHash()); + + // 再验证一次结果; + assertEquals(1, opResults.length); + assertEquals(0, opResults[0].getIndex()); + assertArrayEquals(retnBytes, opResults[0].getResult()); } private HashDigest initLedger(MemoryKVStorage storage, BlockchainKeypair... partiKeys) { 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/TestContract.java index ed56f49f..4d9a5ccd 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/TestContract.java @@ -1,5 +1,9 @@ package test.com.jd.blockchain.ledger; +import com.jd.blockchain.contract.Contract; +import com.jd.blockchain.contract.ContractEvent; + +@Contract public interface TestContract { /** @@ -9,6 +13,7 @@ public interface TestContract { * @param amount 本次发行的资产数量; * @return 资产总量; */ + @ContractEvent(name = "issue") long issue(String asset, long amount); /** @@ -17,6 +22,7 @@ public interface TestContract { * @param asset * @return */ + @ContractEvent(name = "get-amount") long getAmount(String asset); /** @@ -26,6 +32,7 @@ public interface TestContract { * @param asset * @return */ + @ContractEvent(name = "get-balance") long getBalance(String address, String asset); /** @@ -35,6 +42,7 @@ public interface TestContract { * @param asset * @param amount */ + @ContractEvent(name = "assign") void assign(String address, String asset, int amount); /** @@ -45,5 +53,6 @@ public interface TestContract { * @param asset * @param amount */ + @ContractEvent(name = "transfer") void transfer(String fromAddress, String toAddress, String asset, long amount); } diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractType.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractType.java index 80546a84..96ec1fc3 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractType.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractType.java @@ -65,22 +65,22 @@ public class ContractType { /** * 解析合约的声明; * - * @param contractDelaredInterface 声明合约的接口类型; + * @param delaredInterface 声明合约的接口类型; * @return */ - public static ContractType resolve(Class contractDelaredInterface) { + public static ContractType resolve(Class delaredInterface) { ContractType contractType = new ContractType(); - Annotation annotation = contractDelaredInterface.getDeclaredAnnotation(Contract.class); + Annotation annotation = delaredInterface.getDeclaredAnnotation(Contract.class); // contains: @Contract? boolean isContractType = annotation != null ? true : false; if (!isContractType) { - throw new IllegalDataException("is not Contract Type, becaust there is not @Contract."); + throw new IllegalDataException("The specified type is not annotated by @Contract!"); } // contractIntf contains @Contract and @ContractEvent; - Method[] classMethods = contractDelaredInterface.getDeclaredMethods(); + Method[] classMethods = delaredInterface.getDeclaredMethods(); for (Method method : classMethods) { // if current method contains @ContractEvent,then put it in this map; ContractEvent contractEvent = method.getAnnotation(ContractEvent.class); 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/data/ContractTypeTest.java index a1f50f2b..77430442 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/data/ContractTypeTest.java @@ -10,6 +10,7 @@ import java.util.Set; import org.junit.Test; +import com.jd.blockchain.contract.ContractException; import com.jd.blockchain.contract.ContractType; public class ContractTypeTest { @@ -54,6 +55,15 @@ public class ContractTypeTest { Method toStringMethod = NormalContractImpl.class.getMethod("toString"); assertNull(contractType.getEvent(toStringMethod)); assertNull(contractType.getHandleMethod("NotExist")); + + //解析非合约声明接口类型时,应该引发异常 ContractException; + ContractException ex = null; + try { + ContractType.resolve(NormalContractImpl.class); + } catch (ContractException e) { + ex = e; + } + assertNotNull(ex); } }