code: package servers;
  
  import blockchain.data.BCS;
  import com.google.protobuf.ByteString;
  import communication.CommLayer;
  import das.wrb.WRB;
  import proto.types.block.*;
  import proto.types.transaction.*;
  import java.security.SecureRandom;
  import java.util.*;
  
  
  import static blockchain.Utils.createBlockHeader;
  import static java.lang.String.format;
  
  class ByzToyServer extends ToyBaseServer {
  
      private final static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ByzToyServer.class);
      private boolean fullByz = false;
      private List<List<Integer>> groups = new ArrayList<>();
      private int delayTime;
      private final Queue<Block> byzProposed = new LinkedList<>();
  
  
      ByzToyServer(int id, int worker, int n, int f, int maxTx, CommLayer comm) {
          super(id, worker, n, f, maxTx, comm);
          groups.add(new ArrayList<>());
          for (int i = 0 ; i < n ; i++) {
              groups.get(0).add(i); // At the beginning there is no byzantine behaviour
          }
      }
  
      @Override
      void commSendLogic() throws InterruptedException {
          while (!stopped.get()) {
              Block b1;
              Block b2;
              synchronized (blocksForPropose) {
                  while (blocksForPropose.isEmpty()) {
                      blocksForPropose.wait();
                  }
                  b1 = blocksForPropose.poll();
                  if (b1.getDataCount() == 0) continue;
                  b2 = getByzBlock(b1);
                  synchronized (byzProposed) {
                      synchronized (proposedBlocks) {
                          comm.send(worker, b1, groups.get(0).stream().mapToInt(i->i).toArray());
                          proposedBlocks.add(b1);
                          if (groups.size() > 1) {
                              comm.send(worker, b2, groups.get(1).stream().mapToInt(i->i).toArray());
                              byzProposed.add(b2);
                          }
  
  
                      }
                  }
  
              }
  
          }
      }
  
      BlockHeader leaderImpl() throws InterruptedException {
          addTransactionsToCurrBlock();
  
          if (currLeader != getID()) {
              return null;
          }
          if (delayTime > 0) {
              Random rand = new Random();
              int x = rand.nextInt(delayTime);
  //            int x = maxTime;
              logger.info(format("[#%d] sleeps for %d ms", getID(), x));
              Thread.sleep(x);
          }
          synchronized (byzProposed) {
              WRB.WRBSend(getHeaderForCurrentBlock(BCS.nbGetBlock(worker, currHeight - 1).getHeader(),
                      currHeight, cidSeries, cid), groups.get(0).stream().mapToInt(i->i).toArray());
  
  
              if (groups.size() > 1 && byzProposed.size() > 0) {
                  Block byz = byzProposed.element();
                  WRB.WRBSend(createBlockHeader(byz, BCS.nbGetBlock(worker, currHeight - 1).getHeader(), getID(),
                          currHeight, cidSeries, cid, worker, byz.getId()),  groups.get(1).stream().mapToInt(j->j).toArray());
              }
          }
  
          return null;
      }
  
      @Override
      void removeFromPendings(BlockHeader recHeader, Block recBlock) {
          super.removeFromPendings(recHeader, recBlock);
          if (currLeader == getID() && !recHeader.getEmpty()) {
              logger.debug(format("[#%d-C[%d]] byz nullifies currBlock [sender=%d] [height=%d] [cidSeries=%d ; cid=%d]",
                      getID(), worker, recBlock.getHeader().getBid().getPid(), currHeight, cidSeries, cid));
              synchronized (byzProposed) {
                  byzProposed.poll();
              }
          }
      }
  
      @Override
      void resetState() {
          super.resetState();
          synchronized (byzProposed) {
              while (byzProposed.poll() != null);
          }
      }
  
  
      void setByzSetting() {
          LinkedList<Integer> ids = new LinkedList<>();
          for (int i =  0 ; i < n ; i++) {
              ids.add(i);
          }
          logger.info("Setting byz");
          synchronized (byzProposed) {
              this.fullByz = true;
              groups = new ArrayList<>();
              groups.add(new ArrayList<>());
              Random rand = new Random();
              for (int i = 0 ; i < n/2 ; i++) {
                  int index = rand.nextInt(ids.size());
                  groups.get(0).add(ids.get(index));
                  ids.remove(index);
              }
              this.groups.add(ids);
              logger.info("Byzantine group are " + groups);
              synchronized (proposedBlocks) {
                  while (proposedBlocks.poll() != null);
              }
              while (byzProposed.poll() != null);
          }
      }
  
      void setAsyncParam(int maxTime) {
          this.delayTime = maxTime;
  
      }
  
      private Block getByzBlock(Block b) {
          SecureRandom random = new SecureRandom();
          byte[] byzTx = new byte[40];
          int bid = random.nextInt();
          return b.toBuilder().addData(Transaction.newBuilder()
                  .setId(TxID.newBuilder()
                          .setProposerID(getID())
                          .setBid(bid)
                          .setTxNum(1)
                          .setChannel(worker))
                          .setData(ByteString.copyFrom(byzTx))
                  .build()).build();
      }
  
      @Override
      void potentialBehaviourForSync() throws InterruptedException {
          if (delayTime > 0) {
              logger.debug(format("[#%d-%d] potentialBehaviourForSync sleeps for [%d]", getID(), worker, delayTime));
              Thread.sleep(delayTime);
          }
      }
  
  }
  