github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/sdk/examples/intkey_java/IntegerKeyHandler.java (about)

     1  /* Copyright 2016, 2017 Intel Corporation
     2   Licensed under the Apache License, Version 2.0 (the "License");
     3   you may not use this file except in compliance with the License.
     4   You may obtain a copy of the License at
     5  
     6       http://www.apache.org/licenses/LICENSE-2.0
     7  
     8   Unless required by applicable law or agreed to in writing, software
     9   distributed under the License is distributed on an "AS IS" BASIS,
    10   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11   See the License for the specific language governing permissions and
    12   limitations under the License.
    13  ------------------------------------------------------------------------------*/
    14  
    15  package sawtooth.examples.intkey;
    16  
    17  import com.google.protobuf.ByteString;
    18  
    19  import co.nstant.in.cbor.CborBuilder;
    20  import co.nstant.in.cbor.CborDecoder;
    21  import co.nstant.in.cbor.CborEncoder;
    22  import co.nstant.in.cbor.CborException;
    23  import co.nstant.in.cbor.model.DataItem;
    24  
    25  import sawtooth.sdk.processor.State;
    26  import sawtooth.sdk.processor.TransactionHandler;
    27  import sawtooth.sdk.processor.Utils;
    28  import sawtooth.sdk.processor.exceptions.InternalError;
    29  import sawtooth.sdk.processor.exceptions.InvalidTransactionException;
    30  import sawtooth.sdk.protobuf.TpProcessRequest;
    31  
    32  import java.io.ByteArrayInputStream;
    33  import java.io.ByteArrayOutputStream;
    34  import java.io.IOException;
    35  import java.io.UnsupportedEncodingException;
    36  import java.util.AbstractMap;
    37  import java.util.ArrayList;
    38  import java.util.Arrays;
    39  import java.util.Collection;
    40  import java.util.HashMap;
    41  import java.util.Map;
    42  import java.util.logging.Logger;
    43  
    44  
    45  public class IntegerKeyHandler implements TransactionHandler {
    46  
    47    private final Logger logger = Logger.getLogger(IntegerKeyHandler.class.getName());
    48    private String intkeyNameSpace;
    49  
    50    private static final long MIN_VALUE = 0;
    51    private static final long MAX_VALUE = 4294967295L;
    52    private static final long MAX_NAME_LENGTH = 20;
    53  
    54    /**
    55     * constructor.
    56     */
    57    public IntegerKeyHandler() {
    58      try {
    59        this.intkeyNameSpace = Utils.hash512(
    60                this.transactionFamilyName().getBytes("UTF-8")).substring(0, 6);
    61      } catch (UnsupportedEncodingException usee) {
    62        usee.printStackTrace();
    63        this.intkeyNameSpace = "";
    64      }
    65    }
    66  
    67    @Override
    68    public String transactionFamilyName() {
    69      return "intkey";
    70    }
    71  
    72    @Override
    73    public String getVersion() {
    74      return "1.0";
    75    }
    76  
    77    @Override
    78    public Collection<String> getNameSpaces() {
    79      ArrayList<String> namespaces = new ArrayList<String>();
    80      namespaces.add(this.intkeyNameSpace);
    81      return namespaces;
    82    }
    83  
    84    /**
    85     * Helper function to decode the Payload of a transaction.
    86     * Convert the co.nstant.in.cbor.model.Map to a HashMap.
    87     */
    88    public Map<String, String> decodePayload(byte[] bytes) throws CborException {
    89      ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
    90      co.nstant.in.cbor.model.Map data =
    91          (co.nstant.in.cbor.model.Map) new CborDecoder(bais).decodeNext();
    92      DataItem[] keys = data.getKeys().toArray(new DataItem[0]);
    93      Map<String, String> result = new HashMap();
    94      for (int i = 0; i < keys.length; i++) {
    95        result.put(
    96            keys[i].toString(),
    97            data.get(keys[i]).toString());
    98      }
    99      return result;
   100    }
   101  
   102    /**
   103     * Helper function to decode State retrieved from the address of the name.
   104     * Convert the co.nstant.in.cbor.model.Map to a HashMap.
   105     */
   106    public Map<String, Long> decodeState(byte[] bytes) throws CborException {
   107      ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
   108      co.nstant.in.cbor.model.Map data =
   109          (co.nstant.in.cbor.model.Map) new CborDecoder(bais).decodeNext();
   110      DataItem[] keys = data.getKeys().toArray(new DataItem[0]);
   111      Map<String, Long> result = new HashMap();
   112      for (int i = 0; i < keys.length; i++) {
   113        result.put(
   114            keys[i].toString(),
   115            Long.decode(data.get(keys[i]).toString()));
   116      }
   117      return result;
   118    }
   119  
   120    /**
   121     * Helper function to encode the State that will be stored at the address of
   122     * the name.
   123     */
   124    public Map.Entry<String, ByteString> encodeState(String address, String name, Long value)
   125        throws CborException {
   126      ByteArrayOutputStream boas = new ByteArrayOutputStream();
   127      new CborEncoder(boas).encode(new CborBuilder()
   128          .addMap()
   129          .put(name, value)
   130          .end()
   131          .build());
   132  
   133      return new AbstractMap.SimpleEntry<String, ByteString>(
   134          address,
   135          ByteString.copyFrom(boas.toByteArray()));
   136    }
   137  
   138    @Override
   139    public void apply(TpProcessRequest transactionRequest,
   140                      State state) throws InvalidTransactionException, InternalError {
   141      /*
   142       * IntKey state will be stored at an address of the name
   143       * with the key being the name and the value an integer. so { "foo": 20, "bar": 26}
   144       * would be a possibility if the hashing algorithm hashes foo and bar to the
   145       * same address
   146       */
   147      try {
   148        if (transactionRequest.getPayload().size() == 0) {
   149          throw new InvalidTransactionException("Payload is required.");
   150        }
   151  
   152        Map updateMap = this.decodePayload(transactionRequest.getPayload().toByteArray());
   153  
   154        // validate name
   155        String name = updateMap.get("Name").toString();
   156  
   157        if (name.length() == 0) {
   158          throw new InvalidTransactionException("Name is required");
   159        }
   160  
   161        if (name.length() > MAX_NAME_LENGTH) {
   162          throw new InvalidTransactionException(
   163            "Name must be a string of no more than "
   164            + Long.toString(MAX_NAME_LENGTH) + " characters");
   165        }
   166  
   167        // validate verb
   168        String verb = updateMap.get("Verb").toString();
   169  
   170        if (verb.length() == 0) {
   171          throw new InvalidTransactionException("Verb is required");
   172        }
   173  
   174        if (!Arrays.asList("set", "dec", "inc").contains(verb)) {
   175          throw new InvalidTransactionException(
   176            "Verb must be set, inc, or dec, not " + verb);
   177        }
   178  
   179        // validate value
   180        Long value = null;
   181  
   182        try {
   183          value = Long.decode(updateMap.get("Value").toString());
   184        } catch (NumberFormatException ex) {
   185          throw new InvalidTransactionException(
   186            "Value must be an integer");
   187        }
   188  
   189        if (value == null) {
   190          throw new InvalidTransactionException("Value is required");
   191        }
   192  
   193        if (value > MAX_VALUE || value < MIN_VALUE) {
   194          throw new InvalidTransactionException(
   195            "Value must be an integer "
   196            + "no less than " + Long.toString(MIN_VALUE)
   197            + " and no greater than " + Long.toString(MAX_VALUE));
   198        }
   199  
   200        String address = null;
   201  
   202        try {
   203          String hashedName = Utils.hash512(name.getBytes("UTF-8"));
   204          address = this.intkeyNameSpace + hashedName.substring(hashedName.length() - 64);
   205        } catch (UnsupportedEncodingException usee) {
   206          usee.printStackTrace();
   207          throw new InternalError("Internal Error, " + usee.toString());
   208        }
   209  
   210        Collection<String> addresses = new ArrayList<String>(0);
   211  
   212        if (verb.equals("set")) {
   213          // The ByteString is cbor encoded dict/hashmap
   214          Map<String, ByteString> possibleAddressValues = state.getState(Arrays.asList(address));
   215          byte[] stateValueRep = possibleAddressValues.get(address).toByteArray();
   216          Map<String, Long> stateValue = null;
   217          if (stateValueRep.length > 0) {
   218            stateValue = this.decodeState(stateValueRep);
   219            if (stateValue.containsKey(name)) {
   220              throw new InvalidTransactionException("Verb is set but Name already in state, "
   221                      + "Name: " + name + " Value: " + stateValue.get(name).toString());
   222            }
   223          }
   224  
   225          if (value < 0) {
   226            throw new InvalidTransactionException("Verb is set but Value is less than 0");
   227          }
   228  
   229          // 'set' passes checks so store it in the state
   230          Map.Entry<String, ByteString> entry = this.encodeState(address, name, value);
   231  
   232          Collection<Map.Entry<String, ByteString>> addressValues = Arrays.asList(entry);
   233          addresses = state.setState(addressValues);
   234        }
   235        if (verb.equals("inc")) {
   236          Map<String, ByteString> possibleValues = state.getState(Arrays.asList(address));
   237          byte[] stateValueRep = possibleValues.get(address).toByteArray();
   238          if (stateValueRep.length == 0) {
   239            throw new InvalidTransactionException("Verb is inc but Name is not in state");
   240          }
   241          Map<String, Long> stateValue = this.decodeState(stateValueRep);
   242          if (!stateValue.containsKey(name)) {
   243            throw new InvalidTransactionException("Verb is inc but Name is not in state");
   244          }
   245          if (stateValue.get(name) + value > MAX_VALUE) {
   246            throw new InvalidTransactionException(
   247              "Inc would set Value to greater than " + Long.toString(MAX_VALUE));
   248          }
   249          // Increment the value in state by value
   250          Map.Entry<String, ByteString> entry =
   251              this.encodeState(address, name, stateValue.get(name) + value);
   252          Collection<Map.Entry<String, ByteString>> addressValues = Arrays.asList(entry);
   253          addresses = state.setState(addressValues);
   254        }
   255        if (verb.equals("dec")) {
   256          Map<String, ByteString> possibleAddressResult = state.getState(Arrays.asList(address));
   257          byte[] stateValueRep = possibleAddressResult.get(address).toByteArray();
   258  
   259          if (stateValueRep.length == 0) {
   260            throw new InvalidTransactionException("Verb is dec but Name is not in state");
   261          }
   262          Map<String, Long> stateValue = this.decodeState(stateValueRep);
   263          if (!stateValue.containsKey(name)) {
   264            throw new InvalidTransactionException("Verb is dec but Name is not in state");
   265          }
   266          if (stateValue.get(name) - value < MIN_VALUE) {
   267            throw new InvalidTransactionException(
   268              "Dec would set Value to less than " + Long.toString(MIN_VALUE));
   269          }
   270  
   271          // Decrement the value in state by value
   272          Map.Entry<String, ByteString> entry =
   273              this.encodeState(address, name, stateValue.get(name) - value);
   274  
   275          Collection<Map.Entry<String, ByteString>> addressValues = Arrays.asList(entry);
   276          addresses = state.setState(addressValues);
   277        }
   278        // if the 'set', 'inc', or 'dec' set to state didn't work
   279        if (addresses.size() == 0) {
   280          throw new InternalError("State error!.");
   281        }
   282        logger.info("Verb: " + verb + " Name: " + name + " value: " + value);
   283  
   284      } catch (CborException ce) {
   285        throw new InternalError("Cbor Error" + ce.toString());
   286      }
   287    }
   288  }