Browse Source

Completed test case for contract invoking;

tags/1.0.0
huanghaiquan 6 years ago
parent
commit
2fcf1fb18c
9 changed files with 181 additions and 90 deletions
  1. +17
    -4
      source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java
  2. +2
    -2
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java
  3. +7
    -13
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java
  4. +29
    -0
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java
  5. +24
    -56
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java
  6. +78
    -10
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java
  7. +9
    -0
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContract.java
  8. +5
    -5
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractType.java
  9. +10
    -0
      source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractTypeTest.java

+ 17
- 4
source/contract/contract-jvm/src/main/java/com/jd/blockchain/contract/jvm/JavaContractCode.java View File

@@ -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() {


+ 2
- 2
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/DefaultOperationHandleRegisteration.java View File

@@ -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());
}

/**


source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/ContractEventSendOperationHandle.java → source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/AbtractContractEventHandle.java View File

@@ -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);


}

+ 29
- 0
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/handles/JVMContractEventSendOperationHandle.java View File

@@ -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;
}

}

+ 24
- 56
source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingHandle.java View File

@@ -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);
}

}

+ 78
- 10
source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/ContractInvokingTest.java View File

@@ -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) {


+ 9
- 0
source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TestContract.java View File

@@ -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);
}

+ 5
- 5
source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractType.java View File

@@ -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
- 0
source/ledger/ledger-model/src/test/java/test/com/jd/blockchain/ledger/data/ContractTypeTest.java View File

@@ -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);
}

}

Loading…
Cancel
Save