Coverage Summary for Class: NativeContract (co.rsk.pcc)

Class Class, % Method, % Line, %
NativeContract 0% (0/1) 0% (0/10) 0% (0/77)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2019 RSK Labs Ltd. 4  * 5  * This program is free software: you can redistribute it and/or modify 6  * it under the terms of the GNU Lesser General Public License as published by 7  * the Free Software Foundation, either version 3 of the License, or 8  * (at your option) any later version. 9  * 10  * This program is distributed in the hope that it will be useful, 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13  * GNU Lesser General Public License for more details. 14  * 15  * You should have received a copy of the GNU Lesser General Public License 16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 17  */ 18  19 package co.rsk.pcc; 20  21 import co.rsk.core.RskAddress; 22 import co.rsk.panic.PanicProcessor; 23 import co.rsk.pcc.exception.NativeContractIllegalArgumentException; 24 import org.ethereum.config.blockchain.upgrades.ActivationConfig; 25 import org.ethereum.core.Block; 26 import org.ethereum.core.Repository; 27 import org.ethereum.core.Transaction; 28 import org.ethereum.db.BlockStore; 29 import org.ethereum.db.ReceiptStore; 30 import org.ethereum.util.ByteUtil; 31 import org.ethereum.vm.LogInfo; 32 import org.ethereum.vm.PrecompiledContracts; 33 import org.ethereum.vm.exception.VMException; 34 import org.slf4j.Logger; 35 import org.slf4j.LoggerFactory; 36  37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.Optional; 40  41 /** 42  * This class contains common behavior for a RSK native contract. 43  * 44  * @author Ariel Mendelzon 45  */ 46 public abstract class NativeContract extends PrecompiledContracts.PrecompiledContract { 47  private static final Logger logger = LoggerFactory.getLogger(NativeContract.class); 48  private static final PanicProcessor panicProcessor = new PanicProcessor(); 49  50  private final ActivationConfig activationConfig; 51  52  private ExecutionEnvironment executionEnvironment; 53  54  public NativeContract(ActivationConfig activationConfig, RskAddress contractAddress) { 55  this.contractAddress = contractAddress; 56  this.activationConfig = activationConfig; 57  } 58  59  public ExecutionEnvironment getExecutionEnvironment() { 60  return executionEnvironment; 61  } 62  63  public abstract List<NativeMethod> getMethods(); 64  65  public abstract Optional<NativeMethod> getDefaultMethod(); 66  67  public void before() { 68  // Can be overriden to provide logic that executes right before each method execution 69  } 70  71  public void after() { 72  // Can be overriden to provide logic that executes right after each method execution 73  } 74  75  @Override 76  public void init(Transaction tx, Block executionBlock, Repository repository, BlockStore blockStore, ReceiptStore receiptStore, List<LogInfo> logs) { 77  super.init(tx, executionBlock, repository, blockStore, receiptStore, logs); 78  79  executionEnvironment = new ExecutionEnvironment( 80  activationConfig, 81  tx, 82  executionBlock, 83  repository, 84  blockStore, 85  receiptStore, 86  logs 87  ); 88  } 89  90  @Override 91  public long getGasForData(byte[] data) { 92  // Preliminary validation: we need an execution environment 93  if (executionEnvironment == null) { 94  throw new RuntimeException("Execution environment is null"); 95  } 96  97  Optional<NativeMethod.WithArguments> methodWithArguments = parseData(data); 98  99  if (!methodWithArguments.isPresent()) { 100  // TODO: Define whether we should log or even do something different in this case. 101  // TODO: I reckon this is fine since execution doesn't actually go ahead and 102  // TODO: inexistent method logging happens within parseData anyway. 103  return 0L; 104  } 105  106  return methodWithArguments.get().getGas(); 107  } 108  109  @Override 110  public byte[] execute(byte[] data) throws VMException { 111  try 112  { 113  // Preliminary validation: we need an execution environment 114  if (executionEnvironment == null) { 115  throw new VMException("Execution environment is null"); 116  } 117  118  // Preliminary validation: the transaction on which we execute cannot be null 119  if (executionEnvironment.getTransaction() == null) { 120  throw new VMException("RSK Transaction is null"); 121  } 122  123  Optional<NativeMethod.WithArguments> methodWithArguments = parseData(data); 124  125  // No function found with the given data? => halt! 126  if (!methodWithArguments.isPresent()) { 127  String errorMessage = String.format("Invalid data given: %s.", ByteUtil.toHexString(data)); 128  logger.info(errorMessage); 129  throw new NativeContractIllegalArgumentException(errorMessage); 130  } 131  132  NativeMethod method = methodWithArguments.get().getMethod(); 133  134  // If this is not a local call, then first check whether the function 135  // allows for non-local calls 136  137  if (!executionEnvironment.isLocalCall() && method.onlyAllowsLocalCalls()) { 138  String errorMessage = String.format("Non-local-call to %s. Returning without execution.", method.getName()); 139  logger.info(errorMessage); 140  throw new NativeContractIllegalArgumentException(errorMessage); 141  } 142  143  before(); 144  145  Object result; 146  try { 147  result = methodWithArguments.get().execute(); 148  } catch (NativeContractIllegalArgumentException ex) { 149  String errorMessage = String.format("Error executing: %s", method.getName()); 150  logger.warn(errorMessage, ex); 151  throw new NativeContractIllegalArgumentException(errorMessage, ex); 152  } 153  154  after(); 155  156  // Special cases: 157  // - null => null 158  // - empty Optional<?> => null 159  // - nonempty Optional<?> => encoded ? 160  // Note: this is hacky, but ultimately very short and to the point. 161  if (result == null) { 162  return null; 163  } else if (result.getClass() == Optional.class) { 164  Optional<?> optionalResult = (Optional<?>) result; 165  if (!optionalResult.isPresent()) { 166  return null; 167  } else { 168  result = optionalResult.get(); 169  } 170  } 171  172  return method.getFunction().encodeOutputs(result); 173  } catch (Exception ex) { 174  logger.error(ex.getMessage(), ex); 175  panicProcessor.panic("nativecontractexecute", ex.getMessage()); 176  throw new VMException(String.format("Exception executing native contract: %s", ex.getMessage()), ex); 177  } 178  } 179  180  private Optional<NativeMethod.WithArguments> parseData(byte[] data) { 181  if (data != null && (data.length >= 1 && data.length <= 3)) { 182  logger.warn("Invalid function signature {}.", ByteUtil.toHexString(data)); 183  return Optional.empty(); 184  } 185  186  if (data == null || data.length == 0) { 187  if (getDefaultMethod().isPresent()) { 188  return Optional.of(this.getDefaultMethod().get().new WithArguments(new Object[]{}, data)); 189  } 190  return Optional.empty(); 191  } else { 192  byte[] encodedSignature = Arrays.copyOfRange(data, 0, 4); 193  Optional<NativeMethod> maybeMethod = getMethods().stream() 194  .filter(m -> 195  Arrays.equals( 196  encodedSignature, 197  m.getFunction().encodeSignature() 198  ) 199  ).findFirst(); 200  201  if (!maybeMethod.isPresent()) { 202  logger.warn("Invalid function signature {}.", ByteUtil.toHexString(encodedSignature)); 203  return Optional.empty(); 204  } 205  206  NativeMethod method = maybeMethod.get(); 207  208  if (!method.isEnabled()) { 209  logger.warn("'{}' is not enabled", method.getName()); 210  return Optional.empty(); 211  } 212  213  try { 214  Object[] arguments = method.getFunction().decode(data); 215  return Optional.of(method.new WithArguments(arguments, data)); 216  } catch (Exception e) { 217  logger.warn(String.format("Invalid arguments %s for function %s.", ByteUtil.toHexString(data), ByteUtil.toHexString(encodedSignature)), e); 218  return Optional.empty(); 219  } 220  } 221  } 222 }