@@ -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.<br> | |||
* | |||
* The value of the key will be updated only if it's latest version equals the | |||
* specified version argument. <br> | |||
* If the key doesn't exist, the version checking will be ignored, and key will | |||
* be created with a new sequence number as id. <br> | |||
* It also could specify the version argument to -1 to ignore the version | |||
* checking. | |||
* <p> | |||
* If updating is performed, the version of the key increase by 1. <br> | |||
* If creating is performed, the version of the key initialize by 0. <br> | |||
* | |||
* @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. <br> | |||
* If the key is new created success, then return 0; <br> | |||
* If the key is updated success, then return the new version;<br> | |||
* 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.<br> | |||
* | |||
* The value of the key will be updated only if it's latest version equals the | |||
* specified version argument. <br> | |||
* If the key doesn't exist, the version checking will be ignored, and key will | |||
* be created with a new sequence number as id. <br> | |||
* It also could specify the version argument to -1 to ignore the version | |||
* checking. | |||
* <p> | |||
* If updating is performed, the version of the key increase by 1. <br> | |||
* If creating is performed, the version of the key initialize by 0. <br> | |||
* | |||
* @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. <br> | |||
* If the key is new created success, then return 0; <br> | |||
* If the key is updated success, then return the new version;<br> | |||
* 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.<br> | |||
* | |||
* The value of the key will be updated only if it's latest version equals the | |||
* specified version argument. <br> | |||
* If the key doesn't exist, the version checking will be ignored, and key will | |||
* be created with a new sequence number as id. <br> | |||
* It also could specify the version argument to -1 to ignore the version | |||
* checking. | |||
* <p> | |||
* If updating is performed, the version of the key increase by 1. <br> | |||
* If creating is performed, the version of the key initialize by 0. <br> | |||
* | |||
* @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. <br> | |||
* If the key is new created success, then return 0; <br> | |||
* If the key is updated success, then return the new version;<br> | |||
* 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; | |||
} | |||
@@ -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(); | |||
} | |||
@@ -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; | |||
} | |||
@@ -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()); | |||
} | |||
/** | |||
* 验证在合约方法中写入数据账户时,如果版本校验失败是否会引发异常而导致回滚;<br> | |||
* 期待正确的表现是引发异常而回滚当前交易; | |||
*/ | |||
@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); | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 项; | |||
@@ -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(); | |||
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -38,6 +38,11 @@ public enum TransactionState { | |||
* 合约不存在; | |||
*/ | |||
CONTRACT_DOES_NOT_EXIST((byte) 0x04), | |||
/** | |||
* 数据写入时版本冲突; | |||
*/ | |||
DATA_VERSION_CONFLICT((byte) 0x05), | |||
/** | |||
* 由于在错误的账本上执行交易而被丢弃; | |||
@@ -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); | |||