@@ -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() { | |||
@@ -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()); | |||
} | |||
/** | |||
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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<Bytes, Object> contractInstances = new ConcurrentHashMap<Bytes, Object>(); | |||
private Map<Bytes, ContractCode> contractInstances = new ConcurrentHashMap<Bytes, ContractCode>(); | |||
@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 <T> ContractCode setup(Bytes address, Class<T> contractIntf, T instance) { | |||
ContractCodeInstance<T> contract = new ContractCodeInstance<T>(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<String> contractReturn) { | |||
private static class ContractCodeInstance<T> 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<T> 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); | |||
} | |||
} |
@@ -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) { | |||
@@ -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); | |||
} |
@@ -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); | |||
@@ -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); | |||
} | |||
} |