Browse Source

Implemented transaction rollback mechanism;

tags/1.0.1
huanghaiquan 5 years ago
parent
commit
c727a35626
24 changed files with 782 additions and 350 deletions
  1. +1
    -0
      source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java
  2. +2
    -2
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/AccountSet.java
  3. +4
    -2
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerEditor.java
  4. +1
    -1
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java
  5. +51
    -0
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SettingContext.java
  6. +13
    -1
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerBlockData.java
  7. +12
    -12
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerRepositoryImpl.java
  8. +22
    -44
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java
  9. +321
    -180
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java
  10. +42
    -62
      source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java
  11. +1
    -1
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java
  12. +4
    -2
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java
  13. +39
    -0
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerTestUtils.java
  14. +6
    -0
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/MerkleDataSetTest.java
  15. +133
    -2
      source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java
  16. +3
    -0
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockBody.java
  17. +33
    -0
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockRollbackException.java
  18. +35
    -0
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/IllegalTransactionException.java
  19. +1
    -1
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContentBody.java
  20. +0
    -22
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionException.java
  21. +16
    -0
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRollbackException.java
  22. +39
    -15
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java
  23. +1
    -1
      source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxContentBlob.java
  24. +2
    -2
      source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/VersioningKVStorage.java

+ 1
- 0
source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/service/BftsmartNodeServer.java View File

@@ -243,6 +243,7 @@ public class BftsmartNodeServer extends DefaultRecoverable implements NodeServer
messageHandle.commitBatch(realmName, batchId);
} catch (Exception e) {
// todo 需要处理应答码 404
LOGGER.error("Error occurred while processing ordered messages! --" + e.getMessage(), e);
messageHandle.rollbackBatch(realmName, batchId, TransactionState.CONSENSUS_ERROR.CODE);
}



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

@@ -333,9 +333,9 @@ public class AccountSet implements Transactional, MerkleProvable {
if (!updated) {
return;
}
String[] addresses = new String[latestAccountsCache.size()];
Bytes[] addresses = new Bytes[latestAccountsCache.size()];
latestAccountsCache.keySet().toArray(addresses);
for (String address : addresses) {
for (Bytes address : addresses) {
VersioningAccount acc = latestAccountsCache.remove(address);
// cancel;
if (acc.isUpdated()) {


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

@@ -50,9 +50,11 @@ public interface LedgerEditor {
* 每一次事务性的账本写入操作在提交后,都会记录该事务相关的系统全局快照,以交易对象 {@link LedgerTransaction} 进行保存;
* <p>
*
* 注:方法不解析、不执行交易中的操作;
*
* @param txRequest
*
* 注:方法不解析、不执行交易中的操作;<p>
*
* @param txRequest 交易请求;
* @return
*/
LedgerTransactionContext newTransaction(TransactionRequest txRequest);


+ 1
- 1
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/LedgerTransactionContext.java View File

@@ -24,7 +24,7 @@ public interface LedgerTransactionContext {
*
* @return
*/
TransactionRequest getRequestTX();
TransactionRequest getTransactionRequest();

/**
* 提交对账本数据的修改,以指定的交易状态提交交易;


+ 51
- 0
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/SettingContext.java View File

@@ -0,0 +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;
}
}

}

+ 13
- 1
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerBlockData.java View File

@@ -33,7 +33,9 @@ public class LedgerBlockData implements LedgerBlock {
// private HashDigest contractPrivilegeHash;

private HashDigest transactionSetHash;

private long timestamp;
public LedgerBlockData() {
}

@@ -155,4 +157,14 @@ public class LedgerBlockData implements LedgerBlock {
this.ledgerHash = ledgerHash;
}

public long getTimestamp() {
return timestamp;
}

public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}

}

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

@@ -15,6 +15,7 @@ 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.storage.service.ExPolicyKVStorage;
@@ -77,7 +78,7 @@ public class LedgerRepositoryImpl implements LedgerRepository {
this.ledgerIndexKey = encodeLedgerIndexKey(ledgerHash);

if (getLatestBlockHeight() < 0) {
throw new LedgerException("Ledger doesn't exist!");
throw new RuntimeException("Ledger doesn't exist!");
}
}

@@ -205,15 +206,14 @@ public class LedgerRepositoryImpl implements LedgerRepository {
LedgerBlockData block = new LedgerBlockData(deserialize(blockBytes));

if (!blockHash.equals(block.getHash())) {
throw new LedgerException("Block hash not equals to it's storage key!");
throw new RuntimeException("Block hash not equals to it's storage key!");
}

// verify hash;
// boolean requiredVerifyHash =
// adminAccount.getMetadata().getSetting().getCryptoSetting().getAutoVerifyHash();
// TODO: 未实现从配置中加载是否校验 Hash 的设置;
boolean requiredVerifyHash = false;
if (requiredVerifyHash) {
if (SettingContext.queryingSettings().verifyHash()) {
byte[] blockBodyBytes = null;
if (block.getHeight() == 0) {
// 计算创世区块的 hash 时,不包括 ledgerHash 字段;
@@ -227,14 +227,14 @@ public class LedgerRepositoryImpl implements LedgerRepository {
HashFunction hashFunc = Crypto.getHashFunction(blockHash.getAlgorithm());
boolean pass = hashFunc.verify(blockHash, blockBodyBytes);
if (!pass) {
throw new LedgerException("Block hash verification fail!");
throw new RuntimeException("Block hash verification fail!");
}
}

// verify height;
HashDigest indexedHash = getBlockHash(block.getHeight());
if (indexedHash == null || !indexedHash.equals(blockHash)) {
throw new LedgerException(
throw new RuntimeException(
"Illegal ledger state in storage that ledger height index doesn't match it's block data in height["
+ block.getHeight() + "] and block hash[" + Base58Utils.encode(blockHash.toBytes())
+ "] !");
@@ -394,15 +394,15 @@ public class LedgerRepositoryImpl implements LedgerRepository {
@Override
public synchronized LedgerEditor createNextBlock() {
if (closed) {
throw new LedgerException("Ledger repository has been closed!");
throw new RuntimeException("Ledger repository has been closed!");
}
if (this.nextBlockEditor != null) {
throw new LedgerException(
throw new RuntimeException(
"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(ledgerHash,
getAdminInfo().getMetadata().getSetting(), previousBlock, keyPrefix, exPolicyStorage,
LedgerTransactionalEditor editor = LedgerTransactionalEditor.createEditor(previousBlock,
getAdminInfo().getMetadata().getSetting(), keyPrefix, exPolicyStorage,
versioningStorage);
NewBlockCommittingMonitor committingMonitor = new NewBlockCommittingMonitor(editor, this);
this.nextBlockEditor = committingMonitor;
@@ -420,7 +420,7 @@ public class LedgerRepositoryImpl implements LedgerRepository {
return;
}
if (this.nextBlockEditor != null) {
throw new LedgerException("A new block is in process, cann't close the ledger repository!");
throw new RuntimeException("A new block is in process, cann't close the ledger repository!");
}
closed = true;
}
@@ -600,7 +600,7 @@ public class LedgerRepositoryImpl implements LedgerRepository {
public void commit() {
try {
editor.commit();
LedgerBlock latestBlock = editor.getNewlyBlock();
LedgerBlock latestBlock = editor.getCurrentBlock();
ledgerRepo.latestState = new LedgerState(latestBlock);
} finally {
ledgerRepo.nextBlockEditor = null;


+ 22
- 44
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionData.java View File

@@ -1,8 +1,5 @@
package com.jd.blockchain.ledger.core.impl;

import java.util.Arrays;
import java.util.Comparator;

import com.jd.blockchain.crypto.HashDigest;
import com.jd.blockchain.ledger.DigitalSignature;
import com.jd.blockchain.ledger.LedgerTransaction;
@@ -29,48 +26,27 @@ public class LedgerTransactionData implements LedgerTransaction {

private OperationResult[] operationResults;

// private HashDigest adminAccountHash;
//
// private HashDigest userAccountSetHash;
//
// private HashDigest dataAccountSetHash;
//
// private HashDigest contractAccountSetHash;

/**
* Declare a private no-arguments constructor for deserializing purpose;
*/
@SuppressWarnings("unused")
private LedgerTransactionData() {
// this.txSnapshot = new TransactionStagedSnapshot();
}

/**
* @param blockHeight
* 区块链高度;
* @param txReq
* 交易请求;
* @param execState
* 执行状态;
* @param txSnapshot
* 交易级的系统快照;
* @param blockHeight 区块链高度;
* @param txReq 交易请求;
* @param execState 执行状态;
* @param txSnapshot 交易级的系统快照;
*/
public LedgerTransactionData(long blockHeight, TransactionRequest txReq, TransactionState execState,
TransactionStagedSnapshot txSnapshot, OperationResult... opResults) {
this.blockHeight = blockHeight;
// this.txSnapshot = txSnapshot == null ? new TransactionStagedSnapshot() : txSnapshot;
this.txSnapshot = txSnapshot;
this.transactionContent = txReq.getTransactionContent();
this.endpointSignatures = txReq.getEndpointSignatures();
this.nodeSignatures = txReq.getNodeSignatures();
this.executionState = execState;
if (opResults != null) {
Arrays.sort(opResults, new Comparator<OperationResult>() {
@Override
public int compare(OperationResult o1, OperationResult o2) {
return o1.getIndex() - o2.getIndex();
}
});
}
this.operationResults = opResults;
}

@@ -116,17 +92,17 @@ public class LedgerTransactionData implements LedgerTransaction {

@Override
public HashDigest getUserAccountSetHash() {
return txSnapshot == null ? null :txSnapshot.getUserAccountSetHash();
return txSnapshot == null ? null : txSnapshot.getUserAccountSetHash();
}

@Override
public HashDigest getDataAccountSetHash() {
return txSnapshot == null ? null :txSnapshot.getDataAccountSetHash();
return txSnapshot == null ? null : txSnapshot.getDataAccountSetHash();
}

@Override
public HashDigest getContractAccountSetHash() {
return txSnapshot == null ? null :txSnapshot.getContractAccountSetHash();
return txSnapshot == null ? null : txSnapshot.getContractAccountSetHash();
}

public void setTxSnapshot(TransactionStagedSnapshot txSnapshot) {
@@ -140,20 +116,22 @@ public class LedgerTransactionData implements LedgerTransaction {
this.transactionContent = content;
}

public void setEndpointSignatures(Object[] participantSignatures) {
int length = participantSignatures.length;
this.endpointSignatures = new DigitalSignature[length];
for (int i = 0; i < length; i++) {
this.endpointSignatures[i] = (DigitalSignature) participantSignatures[i];
}
public void setEndpointSignatures(DigitalSignature[] participantSignatures) {
this.endpointSignatures = participantSignatures;
// int length = participantSignatures.length;
// this.endpointSignatures = new DigitalSignature[length];
// for (int i = 0; i < length; i++) {
// this.endpointSignatures[i] = (DigitalSignature) participantSignatures[i];
// }
}

public void setNodeSignatures(Object[] nodeSignatures) {
int length = nodeSignatures.length;
this.nodeSignatures = new DigitalSignature[length];
for (int i = 0; i < length; i++) {
this.nodeSignatures[i] = (DigitalSignature) nodeSignatures[i];
}
public void setNodeSignatures(DigitalSignature[] nodeSignatures) {
this.nodeSignatures = nodeSignatures;
// int length = nodeSignatures.length;
// this.nodeSignatures = new DigitalSignature[length];
// for (int i = 0; i < length; i++) {
// this.nodeSignatures[i] = (DigitalSignature) nodeSignatures[i];
// }
}

public void setExecutionState(TransactionState executionState) {


+ 321
- 180
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/LedgerTransactionalEditor.java View File

@@ -1,19 +1,35 @@
package com.jd.blockchain.ledger.core.impl;

import java.util.List;
import java.util.Stack;

import com.jd.blockchain.binaryproto.BinaryProtocol;
import com.jd.blockchain.crypto.Crypto;
import com.jd.blockchain.crypto.HashDigest;
import com.jd.blockchain.ledger.*;
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;
import com.jd.blockchain.ledger.LedgerInitSetting;
import com.jd.blockchain.ledger.LedgerSetting;
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.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.storage.service.ExPolicyKVStorage;
import com.jd.blockchain.storage.service.VersioningKVStorage;
import com.jd.blockchain.storage.service.utils.BufferedKVStorage;
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;

@@ -35,9 +51,9 @@ public class LedgerTransactionalEditor implements LedgerEditor {

private CryptoSetting cryptoSetting;

private LedgerBlockData newlyBlock;
private LedgerBlockData currentBlock;

private Stack<StagedSnapshot> stagedSnapshots = new Stack<>();
// private Stack<StagedSnapshot> stagedSnapshots = new Stack<>();

private boolean prepared = false;

@@ -45,43 +61,70 @@ public class LedgerTransactionalEditor implements LedgerEditor {

private boolean committed = false;

// private BufferedKVStorage baseStorage;
private BufferedKVStorage bufferedStorage;
private StagedSnapshot startingPoint;

/**
* 最近一个交易上下文
* 当前区块的存储
*/
private LedgerDataContext lastTxCtx;
private BufferedKVStorage baseStorage;

private LedgerDataContext newTxCtx;
/**
* 上一个交易的上下文;
*/
// private LedgerTransactionContextImpl previousTxCtx;

private TxSnapshot previousTxSnapshot;

/**
* 当前交易的上下文;
*/
private volatile LedgerTransactionContextImpl currentTxCtx;

private LedgerTransactionalEditor(HashDigest ledgerHash, CryptoSetting cryptoSetting, LedgerBlockData newlyBlock,
/**
* @param ledgerHash
* @param cryptoSetting
* @param currentBlock
* @param startingPoint
* @param ledgerKeyPrefix
* @param bufferedStorage
* @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能;
*/
private LedgerTransactionalEditor(HashDigest ledgerHash, CryptoSetting cryptoSetting, LedgerBlockData currentBlock,
StagedSnapshot startingPoint, String ledgerKeyPrefix, BufferedKVStorage bufferedStorage) {
this.ledgerHash = ledgerHash;
this.ledgerKeyPrefix = ledgerKeyPrefix;
this.cryptoSetting = cryptoSetting;
this.newlyBlock = newlyBlock;
this.bufferedStorage = bufferedStorage;
this.currentBlock = currentBlock;
this.baseStorage = bufferedStorage;

this.startingPoint = startingPoint;

this.stagedSnapshots.push(startingPoint);
// this.stagedSnapshots.push(startingPoint);
}

/**
* 创建账本新区块的编辑器;
*
* @param ledgerHash
* @param ledgerSetting
* @param previousBlock
* @param ledgerKeyPrefix
* @param ledgerExStorage
* @param ledgerVerStorage
* @param ledgerHash 账本哈希;
* @param ledgerSetting 账本设置;
* @param previousBlock 前置区块;
* @param ledgerKeyPrefix 账本数据前缀;
* @param ledgerExStorage 账本数据存储;
* @param ledgerVerStorage 账本数据版本化存储;
* @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能;
* @return
*/
public static LedgerTransactionalEditor createEditor(HashDigest ledgerHash, LedgerSetting ledgerSetting,
LedgerBlock previousBlock, String ledgerKeyPrefix, ExPolicyKVStorage ledgerExStorage,
VersioningKVStorage ledgerVerStorage) {
public static LedgerTransactionalEditor createEditor(LedgerBlock previousBlock, LedgerSetting ledgerSetting,
String ledgerKeyPrefix, ExPolicyKVStorage ledgerExStorage, VersioningKVStorage ledgerVerStorage) {
// new block;
LedgerBlockData currBlock = new LedgerBlockData(previousBlock.getHeight() + 1, previousBlock.getLedgerHash(),
HashDigest ledgerHash = previousBlock.getLedgerHash();
if (ledgerHash == null) {
ledgerHash = previousBlock.getHash();
}
if (ledgerHash == null) {
throw new IllegalArgumentException("Illegal previous block was specified!");
}
LedgerBlockData currBlock = new LedgerBlockData(previousBlock.getHeight() + 1, ledgerHash,
previousBlock.getHash());

// init storage;
@@ -101,6 +144,7 @@ public class LedgerTransactionalEditor implements LedgerEditor {
* @param ledgerKeyPrefix
* @param ledgerExStorage
* @param ledgerVerStorage
* @param verifyTx 是否校验交易请求;当外部调用者在调用前已经实施了验证时,将次参数设置为 false 能够提升性能;
* @return
*/
public static LedgerTransactionalEditor createEditor(LedgerInitSetting initSetting, String ledgerKeyPrefix,
@@ -114,29 +158,21 @@ public class LedgerTransactionalEditor implements LedgerEditor {
}

private void commitTxSnapshot(TxSnapshot snapshot) {
lastTxCtx = newTxCtx;
newTxCtx = null;
stagedSnapshots.push(snapshot);
previousTxSnapshot = snapshot;
currentTxCtx = null;
}

private void rollbackNewTx() {
newTxCtx = null;
private void rollbackCurrentTx() {
currentTxCtx = null;
}

// public LedgerDataSet getLatestDataSet() {
// if (lastTxCtx == null) {
// return null;
// }
// return lastTxCtx.getDataSet();
// }

LedgerBlock getNewlyBlock() {
return newlyBlock;
LedgerBlock getCurrentBlock() {
return currentBlock;
}

@Override
public long getBlockHeight() {
return newlyBlock.getHeight();
return currentBlock.getHeight();
}

@Override
@@ -161,134 +197,182 @@ 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 (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(),
signature.getPubKey())) {
return false;
}
}
}
DigitalSignature[] nodeSignatures = request.getNodeSignatures();
if (nodeSignatures != null) {
for (DigitalSignature signature : nodeSignatures) {
if (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(),
signature.getPubKey())) {
return false;
}
}
}
return true;
}

@Override
public LedgerTransactionContext newTransaction(TransactionRequest txRequest) {
// 验证账本是否;
if (!isRequestedLedger(txRequest)) {
throw new LedgerException("This ledger is not the target ledger of transaction request["
+ txRequest.getTransactionContent().getHash() + "]!");
public synchronized LedgerTransactionContext newTransaction(TransactionRequest txRequest) {
if (SettingContext.txSettings().verifyLedger() && !isRequestedLedger(txRequest)) {
throw new IllegalTransactionException(
"Transaction request is dispatched to a wrong ledger! --[TxHash="
+ txRequest.getTransactionContent().getHash() + "]!",
TransactionState.IGNORED_BY_WRONG_LEDGER);
}

// TODO: 把验签和创建交易并行化;
if (SettingContext.txSettings().verifySignature() && !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="
+ txRequest.getTransactionContent().getHash() + "]!");
}

// 检查状态是否允许创建新的交易请求;;
checkState();

BufferedKVStorage txBuffStorage = null;
// init storage of new transaction;
BufferedKVStorage txBufferedStorage = new BufferedKVStorage(baseStorage, baseStorage, false);

LedgerDataSetImpl txDataset = null;
TransactionSet txset = null;
if (lastTxCtx == null) {
// init storage of new transaction;
// txBuffStorage = new BufferedKVStorage(bufferedStorage, bufferedStorage,
// false);
txBuffStorage = bufferedStorage;

if (previousTxSnapshot == null) {
// load the starting point of the new transaction;
StagedSnapshot previousSnapshot = stagedSnapshots.peek();
if (previousSnapshot instanceof GenesisSnapshot) {
if (startingPoint instanceof GenesisSnapshot) {
// 准备生成创世区块;
GenesisSnapshot snpht = (GenesisSnapshot) previousSnapshot;
txDataset = LedgerRepositoryImpl.newDataSet(snpht.initSetting, ledgerKeyPrefix, txBuffStorage,
txBuffStorage);
GenesisSnapshot snpht = (GenesisSnapshot) startingPoint;
txDataset = LedgerRepositoryImpl.newDataSet(snpht.initSetting, ledgerKeyPrefix, txBufferedStorage,
txBufferedStorage);
txset = LedgerRepositoryImpl.newTransactionSet(txDataset.getAdminAccount().getSetting(),
ledgerKeyPrefix, txBuffStorage, txBuffStorage);
} else {
ledgerKeyPrefix, txBufferedStorage, txBufferedStorage);
} else if (startingPoint instanceof TxSnapshot) {
// 新的区块;
// TxSnapshot; reload dataset and txset;
TxSnapshot snpht = (TxSnapshot) previousSnapshot;
TxSnapshot snpht = (TxSnapshot) startingPoint;
// load dataset;
txDataset = LedgerRepositoryImpl.loadDataSet(snpht.dataSnapshot, ledgerKeyPrefix, txBuffStorage,
txBuffStorage, false);
txDataset = LedgerRepositoryImpl.loadDataSet(snpht.dataSnapshot, ledgerKeyPrefix, txBufferedStorage,
txBufferedStorage, false);

// load tx set;
txset = LedgerRepositoryImpl.loadTransactionSet(snpht.transactionSetHash, this.cryptoSetting,
ledgerKeyPrefix, txBuffStorage, txBuffStorage, false);
// load txset;
txset = LedgerRepositoryImpl.loadTransactionSet(snpht.txsetHash, this.cryptoSetting, ledgerKeyPrefix,
txBufferedStorage, txBufferedStorage, false);
} else {
// Unreachable;
throw new IllegalStateException("Unreachable code was accidentally executed!");
}

lastTxCtx = new LedgerDataContext(txDataset, txset, txBuffStorage);
} else {
// Reuse previous object to optimize performance;
txBuffStorage = lastTxCtx.storage;
txDataset = lastTxCtx.dataset;
txset = lastTxCtx.txset;
// load dataset;
txDataset = LedgerRepositoryImpl.loadDataSet(previousTxSnapshot.dataSnapshot, ledgerKeyPrefix,
txBufferedStorage, txBufferedStorage, false);

// load txset;
txset = LedgerRepositoryImpl.loadTransactionSet(previousTxSnapshot.txsetHash, this.cryptoSetting,
ledgerKeyPrefix, txBufferedStorage, txBufferedStorage, false);
}

// newTxCtx = new LedgerTransactionContextImpl(newlyBlock.getHeight(),
// txRequest, txDataset, txset, txBuffStorage,
// this);
// return newTxCtx;
currentTxCtx = new LedgerTransactionContextImpl(txRequest, txDataset, txset, txBufferedStorage, this);

return new LedgerTransactionContextImpl(newlyBlock.getHeight(), txRequest, txDataset, txset, txBuffStorage,
this);
return currentTxCtx;
}

@Override
public LedgerBlock prepare() {
checkState();

if (newTxCtx != null) {
throw new IllegalStateException("There is a opening transaction which isn't committed or rollbacked!");
if (currentTxCtx != null) {
// 有进行中的交易尚未提交或回滚;
throw new IllegalStateException(
"There is an ongoing transaction that has been not committed or rolled back!");
}
if (lastTxCtx == null) {
// Genesis;
throw new IllegalStateException("No transaction to prepare!");
if (previousTxSnapshot == null) {
// 当前区块没有加入过交易,不允许产生空区块;
throw new IllegalStateException(
"There is no transaction in the current block, and no empty blocks is allowed!");
}

// do commit when transaction isolation level is BLOCK;
lastTxCtx.dataset.commit();
lastTxCtx.txset.commit();
currentBlock.setAdminAccountHash(previousTxSnapshot.getAdminAccountHash());
currentBlock.setUserAccountSetHash(previousTxSnapshot.getUserAccountSetHash());
currentBlock.setDataAccountSetHash(previousTxSnapshot.getDataAccountSetHash());
currentBlock.setContractAccountSetHash(previousTxSnapshot.getContractAccountSetHash());
currentBlock.setTransactionSetHash(previousTxSnapshot.getTransactionSetHash());

newlyBlock.setAdminAccountHash(lastTxCtx.dataset.getAdminAccount().getHash());
newlyBlock.setContractAccountSetHash(lastTxCtx.dataset.getContractAccountSet().getRootHash());
newlyBlock.setDataAccountSetHash(lastTxCtx.dataset.getDataAccountSet().getRootHash());
newlyBlock.setUserAccountSetHash(lastTxCtx.dataset.getUserAccountSet().getRootHash());
newlyBlock.setTransactionSetHash(lastTxCtx.txset.getRootHash());
// TODO: 根据所有交易的时间戳的平均值来生成区块的时间戳;
// long timestamp =
// currentBlock.setTimestamp(timestamp);

// compute block hash;
byte[] blockBodyBytes = BinaryProtocol.encode(newlyBlock, BlockBody.class);
byte[] blockBodyBytes = BinaryProtocol.encode(currentBlock, BlockBody.class);
HashDigest blockHash = Crypto.getHashFunction(cryptoSetting.getHashAlgorithm()).hash(blockBodyBytes);
newlyBlock.setHash(blockHash);
if (newlyBlock.getLedgerHash() == null) {
// init GenesisBlock's ledger hash;
newlyBlock.setLedgerHash(blockHash);
}
currentBlock.setHash(blockHash);

// if (currentBlock.getLedgerHash() == null) {
// // init GenesisBlock's ledger hash;
// currentBlock.setLedgerHash(blockHash);
// }

// persist block bytes;
// only one version per block;
byte[] blockBytes = BinaryProtocol.encode(newlyBlock, LedgerBlock.class);
Bytes blockStorageKey = LedgerRepositoryImpl.encodeBlockStorageKey(newlyBlock.getHash());
long v = bufferedStorage.set(blockStorageKey, blockBytes, -1);
byte[] blockBytes = BinaryProtocol.encode(currentBlock, LedgerBlock.class);
Bytes blockStorageKey = LedgerRepositoryImpl.encodeBlockStorageKey(currentBlock.getHash());
long v = baseStorage.set(blockStorageKey, blockBytes, -1);
if (v < 0) {
throw new IllegalStateException(
"Block already exist! --[BlockHash=" + Base58Utils.encode(newlyBlock.getHash().toBytes()) + "]");
"Block already exist! --[BlockHash=" + Base58Utils.encode(currentBlock.getHash().toBytes()) + "]");
}

// persist block hash to ledger index;
HashDigest ledgerHash = newlyBlock.getLedgerHash();
HashDigest ledgerHash = currentBlock.getLedgerHash();
if (ledgerHash == null) {
ledgerHash = blockHash;
}
Bytes ledgerIndexKey = LedgerRepositoryImpl.encodeLedgerIndexKey(ledgerHash);
long expectedVersion = newlyBlock.getHeight() - 1;
v = bufferedStorage.set(ledgerIndexKey, newlyBlock.getHash().toBytes(), expectedVersion);
long expectedVersion = currentBlock.getHeight() - 1;
v = baseStorage.set(ledgerIndexKey, currentBlock.getHash().toBytes(), expectedVersion);
if (v < 0) {
throw new IllegalStateException("Index of BlockHash already exist! --[BlockHash="
+ Base58Utils.encode(newlyBlock.getHash().toBytes()) + "]");
throw new IllegalStateException(
String.format("Index of BlockHash already exist! --[BlockHeight=%s][BlockHash=%s]",
currentBlock.getHeight(), currentBlock.getHash()));
}

prepared = true;
return newlyBlock;
return currentBlock;
}

@Override
public void commit() {
if (committed) {
throw new IllegalStateException("LedgerEditor had been committed!");
throw new IllegalStateException("The current block has been committed!");
}
if (canceled) {
throw new IllegalStateException("LedgerEditor had been canceled!");
throw new IllegalStateException("The current block has been canceled!");
}
if (!prepared) {
// 未就绪;
throw new IllegalStateException("LedgerEditor has not prepared!");
throw new IllegalStateException("The current block is not ready yet!");
}

bufferedStorage.flush();
baseStorage.flush();

committed = true;
}
@@ -296,38 +380,47 @@ public class LedgerTransactionalEditor implements LedgerEditor {
@Override
public void cancel() {
if (committed) {
throw new IllegalStateException("LedgerEditor had been committed!");
throw new IllegalStateException("The current block has been committed!");
}
if (canceled) {
return;
}

canceled = true;
// if (newTxCtx != null) {
// newTxCtx.rollback();
// newTxCtx = null;
// }
bufferedStorage.cancel();

baseStorage.cancel();
}

private void checkState() {
if (prepared) {
throw new IllegalStateException("LedgerEditor has been prepared!");
throw new IllegalStateException("The current block is ready!");
}
if (committed) {
throw new IllegalStateException("LedgerEditor has been committed!");
throw new IllegalStateException("The current block has been committed!");
}
if (canceled) {
throw new IllegalStateException("LedgerEditor has been canceled!");
throw new IllegalStateException("The current block has been canceled!");
}
}

// --------------------------- inner type --------------------------

/**
* 用于暂存交易上下文数据的快照对象;
*
* @author huanghaiquan
*
*/
private static interface StagedSnapshot {

}

/**
* 创世区块的快照对象;
*
* @author huanghaiquan
*
*/
private static class GenesisSnapshot implements StagedSnapshot {

private LedgerInitSetting initSetting;
@@ -337,6 +430,12 @@ public class LedgerTransactionalEditor implements LedgerEditor {
}
}

/**
* 交易执行完毕后的快照对象;
*
* @author huanghaiquan
*
*/
private static class TxSnapshot implements StagedSnapshot {

/**
@@ -347,58 +446,90 @@ public class LedgerTransactionalEditor implements LedgerEditor {
/**
* 交易集合的快照(根哈希);
*/
private HashDigest transactionSetHash;
private HashDigest txsetHash;

public TxSnapshot(LedgerDataSnapshot dataSnapshot, HashDigest txSetHash) {
this.dataSnapshot = dataSnapshot;
this.transactionSetHash = txSetHash;
public HashDigest getAdminAccountHash() {
return dataSnapshot.getAdminAccountHash();
}

}
private static class LedgerDataContext {
public HashDigest getUserAccountSetHash() {
return dataSnapshot.getUserAccountSetHash();
}

protected LedgerDataSetImpl dataset;
public HashDigest getDataAccountSetHash() {
return dataSnapshot.getDataAccountSetHash();
}

protected TransactionSet txset;
public HashDigest getContractAccountSetHash() {
return dataSnapshot.getContractAccountSetHash();
}

protected BufferedKVStorage storage;
public HashDigest getTransactionSetHash() {
return txsetHash;
}

public LedgerDataContext(LedgerDataSetImpl dataset, TransactionSet txset, BufferedKVStorage storage) {
this.dataset = dataset;
this.txset = txset;
this.storage = storage;
public TxSnapshot(LedgerDataSnapshot dataSnapshot, HashDigest txsetHash) {
this.dataSnapshot = dataSnapshot;
this.txsetHash = txsetHash;
}

}

private static class LedgerTransactionContextImpl extends LedgerDataContext implements LedgerTransactionContext {
// /**
// * 账本的数据上下文;
// *
// * @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;
// }
//
// }

private long blockHeight;
/**
* 交易的上下文;
*
* @author huanghaiquan
*
*/
private static class LedgerTransactionContextImpl implements LedgerTransactionContext {

private LedgerTransactionalEditor editor;
private LedgerTransactionalEditor blockEditor;

private TransactionRequest txRequest;

// private LedgerDataSetImpl dataset;
//
// private TransactionSet txset;
//
// private BufferedKVStorage storage;
private LedgerDataSetImpl dataset;
private TransactionSet txset;
private BufferedKVStorage storage;

private boolean committed = false;

private boolean rollbacked = false;

private LedgerTransactionContextImpl(long blockHeight, TransactionRequest txRequest, LedgerDataSetImpl dataset,
private LedgerTransaction transaction;

private HashDigest txRootHash;

private LedgerTransactionContextImpl(TransactionRequest txRequest, LedgerDataSetImpl dataset,
TransactionSet txset, BufferedKVStorage storage, LedgerTransactionalEditor editor) {
super(dataset, txset, storage);
this.txRequest = txRequest;
// this.dataset = dataset;
// this.txset = txset;
// this.storage = storage;
this.editor = editor;
this.blockHeight = blockHeight;
this.dataset = dataset;
this.txset = txset;
this.storage = storage;
this.blockEditor = editor;
}

@Override
@@ -407,7 +538,7 @@ public class LedgerTransactionalEditor implements LedgerEditor {
}

@Override
public TransactionRequest getRequestTX() {
public TransactionRequest getTransactionRequest() {
return txRequest;
}

@@ -421,24 +552,28 @@ public class LedgerTransactionalEditor implements LedgerEditor {
checkTxState();

// capture snapshot
// this.dataset.commit();
// TransactionStagedSnapshot txDataSnapshot = takeSnapshot();
// LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest,
// txResult, txDataSnapshot);
LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest, txResult, null,
operationResultArray(operationResults));
this.txset.add(tx);
// this.txset.commit();
// this.storage.flush();
this.dataset.commit();
TransactionStagedSnapshot txDataSnapshot = takeDataSnapshot();
LedgerTransactionData tx;
try {
tx = new LedgerTransactionData(blockEditor.getBlockHeight(), txRequest, txResult, txDataSnapshot,
operationResultArray(operationResults));
this.txset.add(tx);
this.txset.commit();
} catch (Exception e) {
throw new TransactionRollbackException(e.getMessage(), e);
}

// TODO: 未处理出错时 dataset 和 txset 的内部状态恢复,有可能出现不一致的情况;
try {
this.storage.flush();
} catch (Exception e) {
throw new BlockRollbackException(e.getMessage(), e);
}

// put snapshot into stack;
// TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash());
// editor.commitTxSnapshot(snapshot);
TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash());
blockEditor.commitTxSnapshot(snapshot);

committed = true;
return tx;
@@ -454,29 +589,35 @@ public class LedgerTransactionalEditor implements LedgerEditor {
checkTxState();

// 未处理
// dataset.cancel();

// TransactionStagedSnapshot txDataSnapshot = takeSnapshot();
// LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest,
// txResult, txDataSnapshot);
LedgerTransactionData tx = new LedgerTransactionData(blockHeight, txRequest, txResult, null,
operationResultArray(operationResults));
this.txset.add(tx);
// this.txset.commit();

// this.storage.flush();
dataset.cancel();

TransactionStagedSnapshot txDataSnapshot = takeDataSnapshot();

LedgerTransactionData tx;
try {
tx = new LedgerTransactionData(blockEditor.getBlockHeight(), txRequest, txResult, txDataSnapshot,
operationResultArray(operationResults));
this.txset.add(tx);
this.txset.commit();
} catch (Exception e) {
throw new TransactionRollbackException(e.getMessage(), e);
}

// TODO: 未处理出错时 dataset 和 txset 的内部状态恢复,有可能出现不一致的情况;
try {
this.storage.flush();
} catch (Exception e) {
throw new BlockRollbackException(e.getMessage(), e);
}

// put snapshot into stack;
// TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash());
// editor.commitTxSnapshot(snapshot);
TxSnapshot snapshot = new TxSnapshot(txDataSnapshot, txset.getRootHash());
blockEditor.commitTxSnapshot(snapshot);

committed = true;
return tx;
}

private TransactionStagedSnapshot takeSnapshot() {
private TransactionStagedSnapshot takeDataSnapshot() {
TransactionStagedSnapshot txDataSnapshot = new TransactionStagedSnapshot();
txDataSnapshot.setAdminAccountHash(dataset.getAdminAccount().getHash());
txDataSnapshot.setContractAccountSetHash(dataset.getContractAccountSet().getRootHash());
@@ -500,22 +641,22 @@ public class LedgerTransactionalEditor implements LedgerEditor {
return;
}
if (this.committed) {
throw new IllegalStateException("Transaction had been committed!");
throw new IllegalStateException("This transaction had been committed!");
}
// dataset.cancel();
// storage.cancel();
dataset.cancel();
storage.cancel();

// editor.rollbackNewTx();
blockEditor.rollbackCurrentTx();

rollbacked = true;
}

private void checkTxState() {
if (this.committed) {
throw new IllegalStateException("Transaction had been committed!");
throw new IllegalStateException("This transaction had been committed!");
}
if (this.rollbacked) {
throw new IllegalStateException("Transaction had been rollbacked!");
throw new IllegalStateException("This transaction had been rollbacked!");
}
}
}


+ 42
- 62
source/ledger/ledger-core/src/main/java/com/jd/blockchain/ledger/core/impl/TransactionBatchProcessor.java View File

@@ -8,18 +8,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jd.blockchain.crypto.HashDigest;
import com.jd.blockchain.ledger.BlockRollbackException;
import com.jd.blockchain.ledger.BytesValue;
import com.jd.blockchain.ledger.ContractDoesNotExistException;
import com.jd.blockchain.ledger.DataAccountDoesNotExistException;
import com.jd.blockchain.ledger.DigitalSignature;
import com.jd.blockchain.ledger.IllegalTransactionException;
import com.jd.blockchain.ledger.LedgerBlock;
import com.jd.blockchain.ledger.LedgerException;
import com.jd.blockchain.ledger.Operation;
import com.jd.blockchain.ledger.OperationResult;
import com.jd.blockchain.ledger.OperationResultData;
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.LedgerDataSet;
@@ -31,8 +32,6 @@ 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;
import com.jd.blockchain.transaction.TxBuilder;
import com.jd.blockchain.transaction.TxRequestBuilder;
import com.jd.blockchain.transaction.TxResponseMessage;
import com.jd.blockchain.utils.Bytes;

@@ -70,18 +69,6 @@ public class TransactionBatchProcessor implements TransactionBatchProcess {
this.ledgerService = ledgerService;
}

private boolean isRequestedLedger(TransactionRequest txRequest) {
HashDigest currLedgerHash = newBlockEditor.getLedgerHash();
HashDigest reqLedgerHash = txRequest.getTransactionContent().getLedgerHash();
if (currLedgerHash == reqLedgerHash) {
return true;
}
if (currLedgerHash == null || reqLedgerHash == null) {
return false;
}
return currLedgerHash.equals(reqLedgerHash);
}

/*
* (non-Javadoc)
*
@@ -93,19 +80,6 @@ public class TransactionBatchProcessor implements TransactionBatchProcess {
public TransactionResponse schedule(TransactionRequest request) {
TransactionResponse resp;
try {
if (!isRequestedLedger(request)) {
// 抛弃不属于当前账本的交易请求;
resp = discard(request, TransactionState.DISCARD_BY_WRONG_LEDGER);
responseList.add(resp);
return resp;
}
if (!verifyTxContent(request)) {
// 抛弃哈希和签名校验失败的交易请求;
resp = discard(request, TransactionState.DISCARD_BY_WRONG_CONTENT_SIGNATURE);
responseList.add(resp);
return resp;
}

LOGGER.debug("Start handling transaction... --[BlockHeight={}][RequestHash={}][TxHash={}]",
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash());
// 创建交易上下文;
@@ -118,19 +92,34 @@ public class TransactionBatchProcessor implements TransactionBatchProcess {
LOGGER.debug("Complete handling transaction. --[BlockHeight={}][RequestHash={}][TxHash={}]",
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash());

responseList.add(resp);
return resp;
} catch (IllegalTransactionException e) {
// 抛弃发生处理异常的交易请求;
resp = discard(request, e.getTxState());
LOGGER.error(String.format(
"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(),
e.getMessage()), e);
throw e;
} catch (Exception e) {
// 抛弃发生处理异常的交易请求;
resp = discard(request, TransactionState.SYSTEM_ERROR);
LOGGER.error(String.format(
"Discard transaction rollback caused by the system exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s",
"Ignore transaction caused by the system exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s",
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(),
e.getMessage()), e);

responseList.add(resp);
return resp;
}

responseList.add(resp);
return resp;
}

/**
@@ -186,6 +175,23 @@ public class TransactionBatchProcessor implements TransactionBatchProcess {
// 提交交易(事务);
result = TransactionState.SUCCESS;
txCtx.commit(result, operationResults);
} catch (TransactionRollbackException e) {
result = TransactionState.IGNORED_BY_TX_FULL_ROLLBACK;
txCtx.rollback();
LOGGER.error(String.format(
"Transaction was full rolled back! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s",
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(),
e.getMessage()), e);
} catch (BlockRollbackException e) {
result = TransactionState.IGNORED_BY_BLOCK_FULL_ROLLBACK;
txCtx.rollback();
LOGGER.error(
String.format("Transaction was rolled back! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s",
newBlockEditor.getBlockHeight(), request.getHash(),
request.getTransactionContent().getHash(), e.getMessage()),
e);
// 重新抛出由上层错误处理;
throw e;
} catch (LedgerException e) {
// TODO: 识别更详细的异常类型以及执行对应的处理;
result = TransactionState.LEDGER_ERROR;
@@ -198,14 +204,14 @@ public class TransactionBatchProcessor implements TransactionBatchProcess {
}
txCtx.discardAndCommit(result, operationResults);
LOGGER.error(String.format(
"Transaction rollback caused by the ledger exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s",
"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",
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(
"Transaction rollback caused by the system exception! --[BlockHeight=%s][RequestHash=%s][TxHash=%s] --%s",
"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",
newBlockEditor.getBlockHeight(), request.getHash(), request.getTransactionContent().getHash(),
e.getMessage()), e);
}
@@ -218,32 +224,6 @@ public class TransactionBatchProcessor implements TransactionBatchProcess {
return resp;
}

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 (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(),
signature.getPubKey())) {
return false;
}
}
}
DigitalSignature[] nodeSignatures = request.getNodeSignatures();
if (nodeSignatures != null) {
for (DigitalSignature signature : nodeSignatures) {
if (!TxRequestBuilder.verifyHashSignature(txContent.getHash(), signature.getDigest(),
signature.getPubKey())) {
return false;
}
}
}
return true;
}

/**
* 直接丢弃交易;
*


+ 1
- 1
source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerEditorTest.java View File

@@ -138,7 +138,7 @@ public class LedgerEditorTest {

LedgerTransaction tx = genisisTxCtx.commit(TransactionState.SUCCESS);

TransactionRequest genesisTxReq = genisisTxCtx.getRequestTX();
TransactionRequest genesisTxReq = genisisTxCtx.getTransactionRequest();
assertEquals(genesisTxReq.getTransactionContent().getHash(), tx.getTransactionContent().getHash());
assertEquals(0, tx.getBlockHeight());



+ 4
- 2
source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/LedgerManagerTest.java View File

@@ -116,7 +116,8 @@ public class LedgerManagerTest {
assertEquals(0, genesisBlock.getHeight());
assertNotNull(genesisBlock.getHash());
assertNull(genesisBlock.getPreviousHash());
assertEquals(ledgerHash, genesisBlock.getLedgerHash());
// 创世区块的账本hash 为null;创世区块本身的哈希就代表了账本的哈希;
assertNull(genesisBlock.getLedgerHash());

// 提交数据,写入存储;
ldgEdt.commit();
@@ -131,7 +132,8 @@ public class LedgerManagerTest {
LedgerBlock latestBlock = reloadLedgerRepo.getLatestBlock();
assertEquals(0, latestBlock.getHeight());
assertEquals(ledgerHash, latestBlock.getHash());
assertEquals(ledgerHash, latestBlock.getLedgerHash());
// 创世区块的账本hash 为null;创世区块本身的哈希就代表了账本的哈希;
assertNull(latestBlock.getLedgerHash());

LedgerEditor editor1 = reloadLedgerRepo.createNextBlock();



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

@@ -24,6 +24,7 @@ import com.jd.blockchain.transaction.ConsensusParticipantData;
import com.jd.blockchain.transaction.LedgerInitSettingData;
import com.jd.blockchain.transaction.TransactionService;
import com.jd.blockchain.transaction.TxBuilder;
import com.jd.blockchain.utils.Bytes;
import com.jd.blockchain.utils.io.BytesUtils;
import com.jd.blockchain.utils.net.NetworkAddress;

@@ -124,6 +125,44 @@ public class LedgerTestUtils {

return txReqBuilder.buildRequest();
}
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) {
txReqBuilder.signAsEndpoint(signer);
}
}
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) {
TxBuilder txBuilder = new TxBuilder(ledgerHash);
txBuilder.dataAccount(dataAccountAddress).setText(key, value, version);
TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest();
if (signers != null) {
for (BlockchainKeypair signer : signers) {
txReqBuilder.signAsEndpoint(signer);
}
}
if (nodeKeypair != null) {
txReqBuilder.signAsNode(nodeKeypair);
}
return txReqBuilder.buildRequest();
}

/**
* @param userKeypair 要注册的用户key;


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

@@ -58,6 +58,7 @@ public class MerkleDataSetTest {
mds.setValue("C", "C".getBytes(), -1);

mds.commit();
HashDigest root1 = mds.getRootHash();

// 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量;
// 所以:3 项;
@@ -68,6 +69,8 @@ public class MerkleDataSetTest {
mds.setValue("B", "B".getBytes(), 0);
mds.setValue("C", "C".getBytes(), 0);
mds.commit();
HashDigest root2 = mds.getRootHash();
assertNotEquals(root1, root2);

// Version changed only;仅仅增加 merkle 节点,此时 Merkle 树只有 1 层路径节点,因此只更新2个数据节点和 1
// 个路径节点;(注:版本值是在同一个 key 下按序列保存的);
@@ -76,6 +79,9 @@ public class MerkleDataSetTest {

mds.setValue("D", "DValue".getBytes(), -1);
mds.commit();
HashDigest root3 = mds.getRootHash();
assertNotEquals(root2, root3);
assertNotEquals(root1, root3);

// New key added, include 1 versioning kv, 1 sn key, 2 merkle nodes;
// String[] keys = StringUtils.toStringArray(storage.keySet());


+ 133
- 2
source/ledger/ledger-core/src/test/java/test/com/jd/blockchain/ledger/TransactionBatchProcessorTest.java View File

@@ -12,6 +12,8 @@ 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.DataAccountRegisterOperation;
import com.jd.blockchain.ledger.EndpointRequest;
import com.jd.blockchain.ledger.LedgerBlock;
import com.jd.blockchain.ledger.LedgerInitSetting;
@@ -23,6 +25,7 @@ import com.jd.blockchain.ledger.TransactionRequest;
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.LedgerEditor;
import com.jd.blockchain.ledger.core.LedgerRepository;
@@ -44,6 +47,7 @@ public class TransactionBatchProcessorTest {
DataContractRegistry.register(EndpointRequest.class);
DataContractRegistry.register(TransactionResponse.class);
DataContractRegistry.register(UserRegisterOperation.class);
DataContractRegistry.register(DataAccountRegisterOperation.class);
}

private static final String LEDGER_KEY_PREFIX = "LDG://";
@@ -223,7 +227,7 @@ public class TransactionBatchProcessorTest {
.get(transactionRequest2.getTransactionContent().getHash());
LedgerTransaction tx3 = ledgerRepo.getTransactionSet()
.get(transactionRequest3.getTransactionContent().getHash());
assertNotNull(tx1);
assertEquals(TransactionState.SUCCESS, tx1.getExecutionState());
assertNotNull(tx2);
@@ -240,6 +244,131 @@ public class TransactionBatchProcessorTest {
assertTrue(existUser3);
}

@Test
public void testTxRollbackByVersionsConfliction() {
final MemoryKVStorage STORAGE = new MemoryKVStorage();

// 初始化账本到指定的存储库;
ledgerHash = initLedger(STORAGE, parti0, parti1, parti2, parti3);

// 加载账本;
LedgerManager ledgerManager = new LedgerManager();
LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, STORAGE);

// 验证参与方账户的存在;
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock());
UserAccount user0 = previousBlockDataset.getUserAccountSet().getUser(parti0.getAddress());
assertNotNull(user0);
boolean partiRegistered = previousBlockDataset.getUserAccountSet().contains(parti0.getAddress());
assertTrue(partiRegistered);

// 注册数据账户;
// 生成新区块;
LedgerEditor newBlockEditor = ledgerRepo.createNextBlock();

OperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration();
TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset,
opReg, ledgerManager);

BlockchainKeypair dataAccountKeypair = BlockchainKeyGenerator.getInstance().generate();
TransactionRequest transactionRequest1 = LedgerTestUtils.createTxRequest_DataAccountReg(dataAccountKeypair,
ledgerHash, parti0, parti0);
TransactionResponse txResp1 = txbatchProcessor.schedule(transactionRequest1);
LedgerBlock newBlock = newBlockEditor.prepare();
newBlockEditor.commit();

assertEquals(TransactionState.SUCCESS, txResp1.getExecutionState());
DataAccount dataAccount = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress());
assertNotNull(dataAccount);

// 正确写入 KV 数据;
TransactionRequest txreq1 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(),
"K1", "V-1-1", -1, ledgerHash, parti0, parti0);
TransactionRequest txreq2 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(),
"K2", "V-2-1", -1, ledgerHash, parti0, parti0);
TransactionRequest txreq3 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(),
"K3", "V-3-1", -1, ledgerHash, parti0, parti0);
TransactionRequest txreq4 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(),
"K1", "V-1-2", 0, ledgerHash, parti0, parti0);

newBlockEditor = ledgerRepo.createNextBlock();
previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock());
txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager);

txbatchProcessor.schedule(txreq1);
txbatchProcessor.schedule(txreq2);
txbatchProcessor.schedule(txreq3);
txbatchProcessor.schedule(txreq4);

newBlock = newBlockEditor.prepare();
newBlockEditor.commit();

BytesValue v1_0 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1",
0);
BytesValue v1_1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1",
1);
BytesValue v2 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K2",
0);
BytesValue v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3",
0);

assertNotNull(v1_0);
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());

// 提交多笔数据写入的交易,包含存在数据版本冲突的交易,验证交易是否正确回滚;

TransactionRequest txreq5 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(),
"K3", "V-3-2", 0, ledgerHash, parti0, parti0);
// 指定冲突的版本号,正确的应该是版本1;
TransactionRequest txreq6 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(),
"K1", "V-1-3", 0, ledgerHash, parti0, parti0);

newBlockEditor = ledgerRepo.createNextBlock();
previousBlockDataset = ledgerRepo.getDataSet(ledgerRepo.getLatestBlock());
txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager);

txbatchProcessor.schedule(txreq5);
txbatchProcessor.schedule(txreq6);

newBlock = newBlockEditor.prepare();
newBlockEditor.commit();

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");
assertEquals(1, k1_version);
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());
assertEquals("V-3-2", v3.getValue().toUTF8String());

// // 验证正确性;
// ledgerManager = new LedgerManager();
// ledgerRepo = ledgerManager.register(ledgerHash, STORAGE);
//
// LedgerBlock latestBlock = ledgerRepo.getLatestBlock();
// assertEquals(newBlock.getHash(), latestBlock.getHash());
// assertEquals(1, newBlock.getHeight());
//
// LedgerTransaction tx1 = ledgerRepo.getTransactionSet()
// .get(transactionRequest1.getTransactionContent().getHash());
//
// assertNotNull(tx1);
// assertEquals(TransactionState.SUCCESS, tx1.getExecutionState());

}

private HashDigest initLedger(MemoryKVStorage storage, BlockchainKeypair... partiKeys) {
// 创建初始化配置;
LedgerInitSetting initSetting = LedgerTestUtils.createLedgerInitSetting(partiKeys);
@@ -269,7 +398,9 @@ public class TransactionBatchProcessorTest {
assertNotNull(block.getHash());
assertNull(block.getPreviousHash());

assertEquals(block.getHash(), block.getLedgerHash());
// 创世区块的账本哈希为 null;
assertNull(block.getLedgerHash());
assertNotNull(block.getHash());

// 提交数据,写入存储;
ldgEdt.commit();


+ 3
- 0
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockBody.java View File

@@ -20,4 +20,7 @@ public interface BlockBody extends LedgerDataSnapshot{

@DataField(order=5, primitiveType = PrimitiveType.BYTES)
HashDigest getTransactionSetHash();
@DataField(order=6, primitiveType = PrimitiveType.INT64)
long getTimestamp();
}

+ 33
- 0
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/BlockRollbackException.java View File

@@ -0,0 +1,33 @@
package com.jd.blockchain.ledger;
public class BlockRollbackException extends LedgerException {
private static final long serialVersionUID = 3583192000738807503L;
private TransactionState state;
public BlockRollbackException(String message) {
this(TransactionState.SYSTEM_ERROR, message);
}
public BlockRollbackException(TransactionState state, String message) {
super(message);
assert TransactionState.SUCCESS != state;
this.state = state;
}
public BlockRollbackException(String message, Throwable cause) {
this(TransactionState.SYSTEM_ERROR, message, cause);
}
public BlockRollbackException(TransactionState state, String message, Throwable cause) {
super(message, cause);
assert TransactionState.SUCCESS != state;
this.state = state;
}
public TransactionState getState() {
return state;
}
}

+ 35
- 0
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/IllegalTransactionException.java View File

@@ -0,0 +1,35 @@
package com.jd.blockchain.ledger;

public class IllegalTransactionException extends RuntimeException {

private static final long serialVersionUID = 6348921847690512944L;

private TransactionState txState;

public IllegalTransactionException(String message) {
super(message);
this.txState = TransactionState.SYSTEM_ERROR;
}
public IllegalTransactionException(String message, TransactionState txState) {
super(message);
assert TransactionState.SUCCESS != txState;
this.txState = txState;
}

public IllegalTransactionException(String message, Throwable cause) {
super(message, cause);
this.txState = TransactionState.SYSTEM_ERROR;
}

public IllegalTransactionException(String message, Throwable cause, TransactionState txState) {
super(message, cause);
assert TransactionState.SUCCESS != txState;
this.txState = txState;
}

public TransactionState getTxState() {
return txState;
}

}

+ 1
- 1
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionContentBody.java View File

@@ -40,6 +40,6 @@ public interface TransactionContentBody {
* @return
*/
@DataField(order = 3, primitiveType = PrimitiveType.INT64)
long getTime();
long getTimestamp();

}

+ 0
- 22
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionException.java View File

@@ -1,22 +0,0 @@
package com.jd.blockchain.ledger;
public class TransactionException extends Exception {
private static final long serialVersionUID = 3583192000738807503L;
private TransactionState state;
public TransactionException(TransactionState state) {
this.state = state;
}
public TransactionException(TransactionState state, String message) {
super(message);
this.state = state;
}
public TransactionState getState() {
return state;
}
}

+ 16
- 0
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionRollbackException.java View File

@@ -0,0 +1,16 @@
package com.jd.blockchain.ledger;
public class TransactionRollbackException extends RuntimeException {
private static final long serialVersionUID = -1223140447229570029L;
public TransactionRollbackException(String message) {
super(message);
}
public TransactionRollbackException(String message, Throwable cause) {
super(message, cause);
}
}

+ 39
- 15
source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java View File

@@ -20,39 +20,58 @@ public enum TransactionState {
SUCCESS((byte) 0),
/**
* 共识错误;
* 账本错误;
*/
CONSENSUS_ERROR((byte) 1),
LEDGER_ERROR((byte) 0x01),
/**
* 账本错误;
* 数据账户不存在;
*/
DATA_ACCOUNT_DOES_NOT_EXIST((byte) 0x02),
/**
* 用户不存在;
*/
USER_DOES_NOT_EXIST((byte) 0x03),
/**
* 合约不存在;
*/
LEDGER_ERROR((byte) 2),
CONTRACT_DOES_NOT_EXIST((byte) 0x04),
/**
* 由于在错误的账本上执行交易而被丢弃;
*/
DISCARD_BY_WRONG_LEDGER((byte) 3),
IGNORED_BY_WRONG_LEDGER((byte) 0x40),
/**
* 由于交易内容的验签失败而丢弃;
*/
DISCARD_BY_WRONG_CONTENT_SIGNATURE((byte) 4),
IGNORED_BY_WRONG_CONTENT_SIGNATURE((byte) 0x41),
/**
* 数据账户不存在
* 由于交易内容的验签失败而丢弃
*/
DATA_ACCOUNT_DOES_NOT_EXIST((byte) 5),
IGNORED_BY_CONFLICTING_STATE((byte) 0x42),
/**
* 用户不存在;
* 由于交易的整体回滚而丢弃;
* <p>
*
* 注: “整体回滚”是指把交易引入的数据更改以及交易记录本身全部都回滚;<br>
* “部分回滚”是指把交易引入的数据更改回滚了,但是交易记录本身以及相应的“交易结果({@link TransactionState})”都会提交;<br>
*/
USER_DOES_NOT_EXIST((byte) 6),
IGNORED_BY_TX_FULL_ROLLBACK((byte) 0x43),
/**
* 合约不存在;
* 由于区块的整体回滚而丢弃;
* <p>
*
* 注: “整体回滚”是指把交易引入的数据更改以及交易记录本身全部都回滚;<br>
*
* “部分回滚”是指把交易引入的数据更改回滚了,但是交易记录本身以及相应的“交易结果({@link TransactionState})”都会提交;<br>
*/
CONTRACT_DOES_NOT_EXIST((byte) 6),
IGNORED_BY_BLOCK_FULL_ROLLBACK((byte) 0x44),
/**
* 系统错误;
@@ -62,7 +81,12 @@ public enum TransactionState {
/**
* 超时;
*/
TIMEOUT((byte) 0x81);
TIMEOUT((byte) 0x81),
/**
* 共识错误;
*/
CONSENSUS_ERROR((byte) 0x82);
@EnumField(type = PrimitiveType.INT8)
public final byte CODE;


+ 1
- 1
source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/TxContentBlob.java View File

@@ -84,7 +84,7 @@ public class TxContentBlob implements TransactionContent {
}
@Override
public long getTime() {
public long getTimestamp() {
return time;
}


+ 2
- 2
source/storage/storage-service/src/main/java/com/jd/blockchain/storage/service/VersioningKVStorage.java View File

@@ -59,8 +59,8 @@ public interface VersioningKVStorage extends BatchStorageService {
/**
* Update the value of the key;<br>
*
* If key exist, and the specified version equals to latest , then the value is
* updated and version is increased by 1;<br>
* If key exist, and the specified version equals to it's latest version, then the value will be
* updated and version will be increased by 1;<br>
* If key not exist, and the specified version is -1, then the value will be
* created and initialized it's version by 0; <br>
*


Loading…
Cancel
Save