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 }