/**
Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bftsmart.tom.server.defaultservices;
import bftsmart.consensus.messages.ConsensusMessage;
import bftsmart.reconfiguration.ServerViewController;
import bftsmart.statemanagement.ApplicationState;
import bftsmart.tom.core.messages.TOMMessage;
import bftsmart.tom.leaderchange.CertifiedDecision;
import bftsmart.tom.util.BatchBuilder;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Set;
/**
* This class represents a state transfered from a replica to another. The state associated with the last
* checkpoint together with all the batches of messages received do far, comprises the sender's
* current state
*
* @author Joao Sousa
*/
public class DefaultApplicationState implements ApplicationState {
private static final long serialVersionUID = 6771081456095596363L;
protected byte[] state; // State associated with the last checkpoint
protected byte[] stateHash; // Hash of the state associated with the last checkpoint
protected int lastCID = -1; // Consensus ID for the last messages batch delivered to the application
protected boolean hasState; // indicates if the replica really had the requested state
private CommandsInfo[] messageBatches; // batches received since the last checkpoint.
private int lastCheckpointCID; // Consensus ID for the last checkpoint
private byte[] logHash;
private int pid;
/**
* Constructs a TansferableState
* This constructor should be used when there is a valid state to construct the object with
* @param messageBatches Batches received since the last checkpoint.
* @param state State associated with the last checkpoint
* @param stateHash Hash of the state associated with the last checkpoint
*/
public DefaultApplicationState(CommandsInfo[] messageBatches, int lastCheckpointCID, int lastCID, byte[] state, byte[] stateHash, int pid) {
this.messageBatches = messageBatches; // batches received since the last checkpoint.
this.lastCheckpointCID = lastCheckpointCID; // Consensus ID for the last checkpoint
this.lastCID = lastCID; // Consensus ID for the last messages batch delivered to the application
this.state = state; // State associated with the last checkpoint
this.stateHash = stateHash;
this.hasState = true;
this.pid = pid;
}
public DefaultApplicationState(CommandsInfo[] messageBatches, byte[] logHash, int lastCheckpointCID, int lastCID, byte[] state, byte[] stateHash, int pid) {
this(messageBatches, lastCheckpointCID, lastCID, state, stateHash, pid);
this.logHash = logHash;
}
/**
* Constructs a TansferableState
* This constructor should be used when there isn't a valid state to construct the object with
*/
public DefaultApplicationState() {
this.messageBatches = null; // batches received since the last checkpoint.
this.lastCheckpointCID = -1; // Consensus ID for the last checkpoint
this.lastCID = -1;
this.state = null; // State associated with the last checkpoint
this.stateHash = null;
this.hasState = false;
this.pid = -1;
}
@Override
public void setSerializedState(byte[] state) {
this.state = state;
}
@Override
public byte[] getSerializedState() {
return state;
}
/**
* Indicates if the TransferableState object has a valid state
* @return true if it has a valid state, false otherwise
*/
@Override
public boolean hasState() {
return hasState;
}
/**
* Retrieves the consensus ID for the last messages batch delivered to the application
* @return Consensus ID for the last messages batch delivered to the application
*/
@Override
public int getLastCID() {
return lastCID;
}
/**
* Retrieves the certified decision for the last consensus present in this object
* @param controller
* @return The certified decision for the last consensus present in this object
*/
@Override
public CertifiedDecision getCertifiedDecision(ServerViewController controller) {
CommandsInfo ci = getMessageBatch(getLastCID());
if (ci != null && ci.msgCtx[0].getProof() != null) { // do I have a proof for the consensus?
Set<ConsensusMessage> proof = ci.msgCtx[0].getProof();
LinkedList<TOMMessage> requests = new LinkedList<>();
//Recreate all TOMMessages ordered in the consensus
for (int i = 0; i < ci.commands.length; i++) {
requests.add(ci.msgCtx[i].recreateTOMMessage(ci.commands[i]));
}
//Serialize the TOMMessages to re-create the proposed value
BatchBuilder bb = new BatchBuilder(0);
byte[] value = bb.makeBatch(requests, ci.msgCtx[0].getNumOfNonces(),
ci.msgCtx[0].getSeed(), ci.msgCtx[0].getTimestamp(), controller);
//Assemble and return the certified decision
return new CertifiedDecision(pid, getLastCID(), value, proof);
}
else return null; // there was no proof for the consensus
}
/**
* Retrieves the state associated with the last checkpoint
* @return State associated with the last checkpoint
*/
public byte[] getState() {
return state;
}
/**
* Retrieves the hash of the state associated with the last checkpoint
* @return Hash of the state associated with the last checkpoint
*/
@Override
public byte[] getStateHash() {
return stateHash;
}
/**
* Sets the state associated with the last checkpoint
* @param state State associated with the last checkpoint
*/
public void setState(byte[] state) {
this.state = state;
}
/**
* Retrieves all batches of messages
* @return Batch of messages
*/
public CommandsInfo[] getMessageBatches() {
return messageBatches;
}
public void setMessageBatches(CommandsInfo[] messageBatches) {
this.messageBatches = messageBatches;
}
/**
* Retrieves the specified batch of messages
* @param cid Consensus ID associated with the batch to be fetched
* @return The batch of messages associated with the batch correspondent consensus ID
*/
public CommandsInfo getMessageBatch(int cid) {
if (messageBatches != null && cid >= lastCheckpointCID && cid <= lastCID) {
return messageBatches[cid - lastCheckpointCID - 1];
}
else return null;
}
/**
* Retrieves the consensus ID for the last checkpoint
* @return Consensus ID for the last checkpoint, or -1 if no checkpoint was yet executed
*/
public int getLastCheckpointCID() {
return lastCheckpointCID;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DefaultApplicationState) {
DefaultApplicationState tState = (DefaultApplicationState) obj;
if ((this.messageBatches != null && tState.messageBatches == null) ||
(this.messageBatches == null && tState.messageBatches != null)) {
//System.out.println("[DefaultApplicationState] returing FALSE1!");
return false;
}
if (this.messageBatches != null && tState.messageBatches != null) {
if (this.messageBatches.length != tState.messageBatches.length) {
//System.out.println("[DefaultApplicationState] returing FALSE2!");
return false;
}
for (int i = 0; i < this.messageBatches.length; i++) {
if (this.messageBatches[i] == null && tState.messageBatches[i] != null) {
//System.out.println("[DefaultApplicationState] returing FALSE3!");
return false;
}
if (this.messageBatches[i] != null && tState.messageBatches[i] == null) {
//System.out.println("[DefaultApplicationState] returing FALSE4!");
return false;
}
if (!(this.messageBatches[i] == null && tState.messageBatches[i] == null) &&
(!this.messageBatches[i].equals(tState.messageBatches[i]))) {
//System.out.println("[DefaultApplicationState] returing FALSE5!" + (this.messageBatches[i] == null) + " " + (tState.messageBatches[i] == null));
return false;
}
}
}
return (Arrays.equals(this.stateHash, tState.stateHash) &&
tState.lastCheckpointCID == this.lastCheckpointCID &&
tState.lastCID == this.lastCID && tState.hasState == this.hasState);
}
//System.out.println("[DefaultApplicationState] returing FALSE!");
return false;
}
@Override
public int hashCode() {
int hash = 1;
hash = hash * 31 + this.lastCheckpointCID;
hash = hash * 31 + this.lastCID;
hash = hash * 31 + (this.hasState ? 1 : 0);
if (this.stateHash != null) {
for (int i = 0; i < this.stateHash.length; i++) hash = hash * 31 + (int) this.stateHash[i];
} else {
hash = hash * 31 + 0;
}
if (this.messageBatches != null) {
for (int i = 0; i < this.messageBatches.length; i++) {
if (this.messageBatches[i] != null) {
hash = hash * 31 + this.messageBatches[i].hashCode();
} else {
hash = hash * 31 + 0;
}
}
} else {
hash = hash * 31 + 0;
}
return hash;
}
}