Coverage Summary for Class: BridgeSupport (co.rsk.peg)
Class |
Method, %
|
Line, %
|
BridgeSupport |
0%
(0/140)
|
0%
(0/1220)
|
BridgeSupport$1 |
0%
(0/1)
|
0%
(0/1)
|
BridgeSupport$TxType |
0%
(0/1)
|
0%
(0/5)
|
Total |
0%
(0/142)
|
0%
(0/1226)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 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.peg;
20
21 import co.rsk.bitcoinj.core.Address;
22 import co.rsk.bitcoinj.core.AddressFormatException;
23 import co.rsk.bitcoinj.core.BtcBlock;
24 import co.rsk.bitcoinj.core.BtcBlockChain;
25 import co.rsk.bitcoinj.core.BtcECKey;
26 import co.rsk.bitcoinj.core.BtcTransaction;
27 import co.rsk.bitcoinj.core.CheckpointManager;
28 import co.rsk.bitcoinj.core.Coin;
29 import co.rsk.bitcoinj.core.Context;
30 import co.rsk.bitcoinj.core.InsufficientMoneyException;
31 import co.rsk.bitcoinj.core.NetworkParameters;
32 import co.rsk.bitcoinj.core.PartialMerkleTree;
33 import co.rsk.bitcoinj.core.Sha256Hash;
34 import co.rsk.bitcoinj.core.StoredBlock;
35 import co.rsk.bitcoinj.core.TransactionInput;
36 import co.rsk.bitcoinj.core.TransactionOutput;
37 import co.rsk.bitcoinj.core.UTXO;
38 import co.rsk.bitcoinj.core.UTXOProviderException;
39 import co.rsk.bitcoinj.core.VerificationException;
40 import co.rsk.bitcoinj.crypto.TransactionSignature;
41 import co.rsk.bitcoinj.script.FastBridgeRedeemScriptParser;
42 import co.rsk.bitcoinj.script.Script;
43 import co.rsk.bitcoinj.script.ScriptBuilder;
44 import co.rsk.bitcoinj.script.ScriptChunk;
45 import co.rsk.bitcoinj.store.BlockStoreException;
46 import co.rsk.bitcoinj.wallet.SendRequest;
47 import co.rsk.bitcoinj.wallet.Wallet;
48 import co.rsk.config.BridgeConstants;
49 import co.rsk.core.RskAddress;
50 import co.rsk.crypto.Keccak256;
51 import co.rsk.panic.PanicProcessor;
52 import co.rsk.peg.bitcoin.CoinbaseInformation;
53 import co.rsk.peg.bitcoin.MerkleBranch;
54 import co.rsk.peg.bitcoin.RskAllowUnconfirmedCoinSelector;
55 import co.rsk.peg.btcLockSender.BtcLockSender.TxSenderAddressType;
56 import co.rsk.peg.btcLockSender.BtcLockSenderProvider;
57 import co.rsk.peg.fastbridge.FastBridgeFederationInformation;
58 import co.rsk.peg.pegininstructions.PeginInstructionsException;
59 import co.rsk.peg.pegininstructions.PeginInstructionsProvider;
60 import co.rsk.peg.utils.*;
61 import co.rsk.peg.whitelist.LockWhitelist;
62 import co.rsk.peg.whitelist.LockWhitelistEntry;
63 import co.rsk.peg.whitelist.OneOffWhiteListEntry;
64 import co.rsk.peg.whitelist.UnlimitedWhiteListEntry;
65 import co.rsk.rpc.modules.trace.CallType;
66 import co.rsk.rpc.modules.trace.ProgramSubtrace;
67 import com.google.common.annotations.VisibleForTesting;
68 import java.io.IOException;
69 import java.io.InputStream;
70 import java.math.BigInteger;
71 import java.time.Instant;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.Objects;
78 import java.util.Optional;
79 import java.util.Set;
80 import java.util.stream.Collectors;
81 import javax.annotation.Nullable;
82 import org.apache.commons.lang3.tuple.Pair;
83 import org.bouncycastle.util.encoders.Hex;
84 import org.ethereum.config.blockchain.upgrades.ActivationConfig;
85 import org.ethereum.config.blockchain.upgrades.ConsensusRule;
86 import org.ethereum.core.Block;
87 import org.ethereum.core.Repository;
88 import org.ethereum.core.Transaction;
89 import org.ethereum.crypto.ECKey;
90 import org.ethereum.crypto.HashUtil;
91 import org.ethereum.util.ByteUtil;
92 import org.ethereum.vm.DataWord;
93 import org.ethereum.vm.PrecompiledContracts;
94 import org.ethereum.vm.exception.VMException;
95 import org.ethereum.vm.program.InternalTransaction;
96 import org.ethereum.vm.program.Program;
97 import org.ethereum.vm.program.ProgramResult;
98 import org.ethereum.vm.program.invoke.TransferInvoke;
99 import org.slf4j.Logger;
100 import org.slf4j.LoggerFactory;
101
102 import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize;
103 import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP186;
104 import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP219;
105
106 /**
107 * Helper class to move funds from btc to rsk and rsk to btc
108 * @author Oscar Guindzberg
109 */
110 public class BridgeSupport {
111 public static final RskAddress BURN_ADDRESS = new RskAddress("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
112
113 public static final int MAX_RELEASE_ITERATIONS = 30;
114
115 public static final Integer FEDERATION_CHANGE_GENERIC_ERROR_CODE = -10;
116 public static final Integer LOCK_WHITELIST_GENERIC_ERROR_CODE = -10;
117 public static final Integer LOCK_WHITELIST_INVALID_ADDRESS_FORMAT_ERROR_CODE = -2;
118 public static final Integer LOCK_WHITELIST_ALREADY_EXISTS_ERROR_CODE = -1;
119 public static final Integer LOCK_WHITELIST_UNKNOWN_ERROR_CODE = 0;
120 public static final Integer LOCK_WHITELIST_SUCCESS_CODE = 1;
121 public static final Integer FEE_PER_KB_GENERIC_ERROR_CODE = -10;
122 public static final Integer NEGATIVE_FEE_PER_KB_ERROR_CODE = -1;
123 public static final Integer EXCESSIVE_FEE_PER_KB_ERROR_CODE = -2;
124
125 public static final Integer BTC_TRANSACTION_CONFIRMATION_INEXISTENT_BLOCK_HASH_ERROR_CODE = -1;
126 public static final Integer BTC_TRANSACTION_CONFIRMATION_BLOCK_NOT_IN_BEST_CHAIN_ERROR_CODE = -2;
127 public static final Integer BTC_TRANSACTION_CONFIRMATION_INCONSISTENT_BLOCK_ERROR_CODE = -3;
128 public static final Integer BTC_TRANSACTION_CONFIRMATION_BLOCK_TOO_OLD_ERROR_CODE = -4;
129 public static final Integer BTC_TRANSACTION_CONFIRMATION_INVALID_MERKLE_BRANCH_ERROR_CODE = -5;
130
131 public static final long FAST_BRIDGE_REFUNDED_USER_ERROR_CODE = -100;
132 public static final long FAST_BRIDGE_REFUNDED_LP_ERROR_CODE = -200;
133 public static final long FAST_BRIDGE_UNPROCESSABLE_TX_NOT_CONTRACT_ERROR_CODE = -300;
134 public static final long FAST_BRIDGE_UNPROCESSABLE_TX_INVALID_SENDER_ERROR_CODE = -301;
135 public static final long FAST_BRIDGE_UNPROCESSABLE_TX_ALREADY_PROCESSED_ERROR_CODE = -302;
136 public static final long FAST_BRIDGE_UNPROCESSABLE_TX_VALIDATIONS_ERROR = -303;
137 public static final long FAST_BRIDGE_UNPROCESSABLE_TX_VALUE_ZERO_ERROR = -304;
138 public static final long FAST_BRIDGE_GENERIC_ERROR = -900;
139
140 public static final Integer RECEIVE_HEADER_CALLED_TOO_SOON = -1;
141 public static final Integer RECEIVE_HEADER_BLOCK_TOO_OLD = -2;
142 public static final Integer RECEIVE_HEADER_CANT_FOUND_PREVIOUS_BLOCK = -3;
143 public static final Integer RECEIVE_HEADER_BLOCK_PREVIOUSLY_SAVED = -4;
144 public static final Integer RECEIVE_HEADER_UNEXPECTED_EXCEPTION = -99;
145
146 // Enough depth to be able to search backwards one month worth of blocks
147 // (6 blocks/hour, 24 hours/day, 30 days/month)
148 public static final Integer BTC_TRANSACTION_CONFIRMATION_MAX_DEPTH = 4320;
149
150 private static final Logger logger = LoggerFactory.getLogger("BridgeSupport");
151 private static final PanicProcessor panicProcessor = new PanicProcessor();
152
153 private static final String INVALID_ADDRESS_FORMAT_MESSAGE = "invalid address format";
154
155 private final List<String> FEDERATION_CHANGE_FUNCTIONS = Collections.unmodifiableList(Arrays.asList(
156 "create",
157 "add",
158 "add-multi",
159 "commit",
160 "rollback"));
161
162 private final BridgeConstants bridgeConstants;
163 private final BridgeStorageProvider provider;
164 private final Repository rskRepository;
165 private final BridgeEventLogger eventLogger;
166 private final List<ProgramSubtrace> subtraces = new ArrayList<>();
167 private final BtcLockSenderProvider btcLockSenderProvider;
168 private final PeginInstructionsProvider peginInstructionsProvider;
169
170 private final FederationSupport federationSupport;
171
172 private final Context btcContext;
173 private final BtcBlockStoreWithCache.Factory btcBlockStoreFactory;
174 private BtcBlockStoreWithCache btcBlockStore;
175 private BtcBlockChain btcBlockChain;
176 private final org.ethereum.core.Block rskExecutionBlock;
177 private final ActivationConfig.ForBlock activations;
178
179 protected enum TxType {
180 PEGIN,
181 PEGOUT,
182 MIGRATION,
183 UNKNOWN
184 }
185
186 public BridgeSupport(
187 BridgeConstants bridgeConstants,
188 BridgeStorageProvider provider,
189 BridgeEventLogger eventLogger,
190 BtcLockSenderProvider btcLockSenderProvider,
191 PeginInstructionsProvider peginInstructionsProvider,
192 Repository repository,
193 Block executionBlock,
194 Context btcContext,
195 FederationSupport federationSupport,
196 BtcBlockStoreWithCache.Factory btcBlockStoreFactory,
197 ActivationConfig.ForBlock activations) {
198 this.rskRepository = repository;
199 this.provider = provider;
200 this.rskExecutionBlock = executionBlock;
201 this.bridgeConstants = bridgeConstants;
202 this.eventLogger = eventLogger;
203 this.btcLockSenderProvider = btcLockSenderProvider;
204 this.peginInstructionsProvider = peginInstructionsProvider;
205 this.btcContext = btcContext;
206 this.federationSupport = federationSupport;
207 this.btcBlockStoreFactory = btcBlockStoreFactory;
208 this.activations = activations;
209 }
210
211 public List<ProgramSubtrace> getSubtraces() {
212 return Collections.unmodifiableList(this.subtraces);
213 }
214
215 @VisibleForTesting
216 InputStream getCheckPoints() {
217 InputStream checkpoints = BridgeSupport.class.getResourceAsStream("/rskbitcoincheckpoints/" + bridgeConstants.getBtcParams().getId() + ".checkpoints");
218 if (checkpoints == null) {
219 // If we don't have a custom checkpoints file, try to use bitcoinj's default checkpoints for that network
220 checkpoints = BridgeSupport.class.getResourceAsStream("/" + bridgeConstants.getBtcParams().getId() + ".checkpoints");
221 }
222 return checkpoints;
223 }
224
225 @VisibleForTesting
226 ActivationConfig.ForBlock getActivations() {
227 return this.activations;
228 }
229
230 public void save() throws IOException {
231 provider.save();
232 }
233
234 /**
235 * Receives an array of serialized Bitcoin block headers and adds them to the internal BlockChain structure.
236 * @param headers The bitcoin headers
237 */
238 public void receiveHeaders(BtcBlock[] headers) throws IOException, BlockStoreException {
239 if (headers.length > 0) {
240 logger.debug("Received {} headers. First {}, last {}.", headers.length, headers[0].getHash(), headers[headers.length - 1].getHash());
241 } else {
242 logger.warn("Received 0 headers");
243 }
244
245 Context.propagate(btcContext);
246 this.ensureBtcBlockChain();
247 for (int i = 0; i < headers.length; i++) {
248 try {
249 btcBlockChain.add(headers[i]);
250 } catch (Exception e) {
251 // If we tray to add an orphan header bitcoinj throws an exception
252 // This catches that case and any other exception that may be thrown
253 logger.warn("Exception adding btc header {}", headers[i].getHash(), e);
254 }
255 }
256 }
257
258 /**
259 * Receives only one header of serialized Bitcoin block headers and adds them to the internal BlockChain structure.
260 * @param header The bitcoin headers
261 */
262 public Integer receiveHeader(BtcBlock header) throws IOException, BlockStoreException {
263 Context.propagate(btcContext);
264 this.ensureBtcBlockChain();
265
266 if (btcBlockStore.get(header.getHash()) != null) {
267 return RECEIVE_HEADER_BLOCK_PREVIOUSLY_SAVED;
268 }
269
270 long diffTimeStamp = bridgeConstants.getMinSecondsBetweenCallsToReceiveHeader();
271
272 long currentTimeStamp = rskExecutionBlock.getTimestamp(); //in seconds
273 Optional<Long> optionalLastTimeStamp = provider.getReceiveHeadersLastTimestamp();
274 if (optionalLastTimeStamp.isPresent() && (currentTimeStamp - optionalLastTimeStamp.get() < diffTimeStamp)) {
275 logger.warn("Receive header last TimeStamp less than {} milliseconds", diffTimeStamp);
276 return RECEIVE_HEADER_CALLED_TOO_SOON;
277 }
278
279 //Depth
280 StoredBlock previousBlock = btcBlockStore.get(header.getPrevBlockHash());
281 if (previousBlock == null) {
282 return RECEIVE_HEADER_CANT_FOUND_PREVIOUS_BLOCK;
283 }
284
285 // height of best chain - height of current header block greater than maximum depth accepted
286 if ((getBtcBlockchainBestChainHeight() - (previousBlock.getHeight() + 1)) > bridgeConstants.getMaxDepthBlockchainAccepted()) {
287 return RECEIVE_HEADER_BLOCK_TOO_OLD;
288 }
289
290 try {
291 btcBlockChain.add(header);
292 } catch (Exception e) {
293 // If we tray to add an orphan header bitcoinj throws an exception
294 // This catches that case and any other exception that may be thrown
295 logger.warn("Exception adding btc header {}", header.getHash(), e);
296 return RECEIVE_HEADER_UNEXPECTED_EXCEPTION;
297 }
298 provider.setReceiveHeadersLastTimestamp(currentTimeStamp);
299 return 0;
300 }
301
302 /**
303 * Get the wallet for the currently active federation
304 * @return A BTC wallet for the currently active federation
305 *
306 * @throws IOException
307 * @param shouldConsiderFastBridgeUTXOs
308 */
309 public Wallet getActiveFederationWallet(boolean shouldConsiderFastBridgeUTXOs) throws IOException {
310 Federation federation = getActiveFederation();
311 List<UTXO> utxos = getActiveFederationBtcUTXOs();
312
313 return BridgeUtils.getFederationSpendWallet(
314 btcContext,
315 federation,
316 utxos,
317 shouldConsiderFastBridgeUTXOs,
318 provider
319 );
320 }
321
322 /**
323 * Get the wallet for the currently retiring federation
324 * or null if there's currently no retiring federation
325 * @return A BTC wallet for the currently active federation
326 *
327 * @throws IOException
328 * @param shouldConsiderFastBridgeUTXOs
329 */
330 public Wallet getRetiringFederationWallet(boolean shouldConsiderFastBridgeUTXOs) throws IOException {
331 Federation federation = getRetiringFederation();
332 if (federation == null) {
333 return null;
334 }
335
336 List<UTXO> utxos = getRetiringFederationBtcUTXOs();
337
338 return BridgeUtils.getFederationSpendWallet(
339 btcContext,
340 federation,
341 utxos,
342 shouldConsiderFastBridgeUTXOs,
343 provider
344 );
345 }
346
347 /**
348 * Get the wallet for the currently live federations
349 * but limited to a specific list of UTXOs
350 * @return A BTC wallet for the currently live federation(s)
351 * limited to the given list of UTXOs
352 *
353 */
354 public Wallet getUTXOBasedWalletForLiveFederations(List<UTXO> utxos, boolean isFastBridgeCompatible) {
355 return BridgeUtils.getFederationsSpendWallet(btcContext, getLiveFederations(), utxos, isFastBridgeCompatible, provider);
356 }
357
358 /**
359 * Get a no spend wallet for the currently live federations
360 * @return A no spend BTC wallet for the currently live federation(s)
361 *
362 */
363 public Wallet getNoSpendWalletForLiveFederations(boolean isFastBridgeCompatible) {
364 return BridgeUtils.getFederationsNoSpendWallet(btcContext, getLiveFederations(), isFastBridgeCompatible, provider);
365 }
366
367 /**
368 * In case of a lock tx: Transfers some SBTCs to the sender of the btc tx and keeps track of the new UTXOs available for spending.
369 * In case of a release tx: Keeps track of the change UTXOs, now available for spending.
370 * @param rskTx The RSK transaction
371 * @param btcTxSerialized The raw BTC tx
372 * @param height The height of the BTC block that contains the tx
373 * @param pmtSerialized The raw partial Merkle tree
374 * @throws BlockStoreException
375 * @throws IOException
376 */
377 public void registerBtcTransaction(Transaction rskTx, byte[] btcTxSerialized, int height, byte[] pmtSerialized)
378 throws IOException, BlockStoreException, BridgeIllegalArgumentException {
379 Context.propagate(btcContext);
380 Sha256Hash btcTxHash = BtcTransactionFormatUtils.calculateBtcTxHash(btcTxSerialized);
381
382 try {
383 // Check the tx was not already processed
384 if (isAlreadyBtcTxHashProcessed(btcTxHash)) {
385 throw new RegisterBtcTransactionException("Transaction already processed");
386 }
387
388 // Validations for register
389 if (!validationsForRegisterBtcTransaction(btcTxHash, height, pmtSerialized, btcTxSerialized)) {
390 throw new RegisterBtcTransactionException("Could not validate transaction");
391 }
392
393 BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized);
394 btcTx.verify();
395
396 // Check again that the tx was not already processed but making sure to use the txid (no witness)
397 if (isAlreadyBtcTxHashProcessed(btcTx.getHash(false))) {
398 throw new RegisterBtcTransactionException("Transaction already processed");
399 }
400
401 // Specific code for pegin/pegout/migration/none txs
402 switch (getTransactionType(btcTx)) {
403 case PEGIN:
404 processPegIn(btcTx, rskTx, height, btcTxHash);
405 break;
406 case PEGOUT:
407 processRelease(btcTx, btcTxHash);
408 break;
409 case MIGRATION:
410 processMigration(btcTx, btcTxHash);
411 break;
412 default:
413 logger.warn("[registerBtcTransaction] This is not a lock, a release nor a migration tx {}", btcTx);
414 panicProcessor.panic("btclock", "This is not a lock, a release nor a migration tx " + btcTx);
415 }
416 } catch (RegisterBtcTransactionException e) {
417 logger.warn("[registerBtcTransaction] Could not register transaction {}. Message: {}", btcTxHash,
418 e.getMessage());
419 }
420 }
421
422 protected TxType getTransactionType(BtcTransaction btcTx) {
423 Script retiredFederationP2SHScript = provider.getLastRetiredFederationP2SHScript().orElse(null);
424
425 /************************************************************************/
426 /** Special case to migrate funds from an old federation **/
427 /************************************************************************/
428 if (activations.isActive(ConsensusRule.RSKIP199) && txIsFromOldFederation(btcTx)) {
429 return TxType.MIGRATION;
430 }
431
432 if (BridgeUtils.isValidPegInTx(
433 btcTx,
434 getLiveFederations(),
435 retiredFederationP2SHScript,
436 btcContext,
437 bridgeConstants,
438 activations
439 )) {
440 return TxType.PEGIN;
441 }
442
443 if (BridgeUtils.isMigrationTx(
444 btcTx,
445 getActiveFederation(),
446 getRetiringFederation(),
447 retiredFederationP2SHScript,
448 btcContext,
449 bridgeConstants,
450 activations
451 )) {
452 return TxType.MIGRATION;
453 }
454
455 if (BridgeUtils.isPegOutTx(btcTx, getLiveFederations(), activations)) {
456 return TxType.PEGOUT;
457 }
458
459 return TxType.UNKNOWN;
460 }
461
462 private boolean txIsFromOldFederation(BtcTransaction btcTx) {
463 Address oldFederationAddress = Address.fromBase58(bridgeConstants.getBtcParams(), bridgeConstants.getOldFederationAddress());
464 Script p2shScript = ScriptBuilder.createP2SHOutputScript(oldFederationAddress.getHash160());
465
466 for (int i = 0; i < btcTx.getInputs().size(); i++) {
467 if (BridgeUtils.scriptCorrectlySpendsTx(btcTx, i, p2shScript)) {
468 return true;
469 }
470 }
471
472 return false;
473 }
474
475
476 protected void processPegIn(
477 BtcTransaction btcTx,
478 Transaction rskTx,
479 int height,
480 Sha256Hash btcTxHash) throws IOException, RegisterBtcTransactionException {
481
482 logger.debug("[processPegIn] This is a lock tx {}", btcTx);
483
484 Coin totalAmount = computeTotalAmountSent(btcTx);
485
486 PeginInformation peginInformation = new PeginInformation(
487 btcLockSenderProvider,
488 peginInstructionsProvider,
489 activations
490 );
491 try {
492 peginInformation.parse(btcTx);
493 } catch (PeginInstructionsException e) {
494 if (activations.isActive(ConsensusRule.RSKIP170)) {
495 if (activations.isActive(ConsensusRule.RSKIP181)) {
496 eventLogger.logRejectedPegin(btcTx, RejectedPeginReason.PEGIN_V1_INVALID_PAYLOAD);
497 }
498
499 // If possible to get the sender address, refund
500 refundTxSender(btcTx, rskTx, peginInformation, totalAmount);
501 markTxAsProcessed(btcTx);
502 }
503
504 String message = String.format(
505 "Error while trying to parse peg-in information for tx %s. %s",
506 btcTx.getHash(),
507 e.getMessage()
508 );
509 logger.warn("[processPegIn] {}", message);
510 throw new RegisterBtcTransactionException(message);
511 }
512
513 int protocolVersion = peginInformation.getProtocolVersion();
514 logger.debug("[processPegIn] Protocol version: {}", protocolVersion);
515 switch (protocolVersion) {
516 case 0:
517 processPegInVersionLegacy(btcTx, rskTx, height, peginInformation, totalAmount);
518 break;
519 case 1:
520 processPegInVersion1(btcTx, rskTx, peginInformation, totalAmount);
521 break;
522 default:
523 markTxAsProcessed(btcTx);
524
525 String message = String.format("Invalid peg-in protocol version: %d", protocolVersion);
526 logger.warn("[processPegIn] {}", message);
527 throw new RegisterBtcTransactionException(message);
528 }
529
530 markTxAsProcessed(btcTx);
531 logger.info("[processPegIn] BTC Tx {} processed in RSK", btcTxHash);
532 }
533
534 private void processPegInVersionLegacy(
535 BtcTransaction btcTx,
536 Transaction rskTx,
537 int height,
538 PeginInformation peginInformation,
539 Coin totalAmount) throws IOException, RegisterBtcTransactionException {
540
541 Address senderBtcAddress = peginInformation.getSenderBtcAddress();
542 TxSenderAddressType senderBtcAddressType = peginInformation.getSenderBtcAddressType();
543
544 if (!BridgeUtils.txIsProcessableInLegacyVersion(senderBtcAddressType, activations)) {
545 logger.warn("[processPeginVersionLegacy] [btcTx:{}] Could not get BtcLockSender from Btc tx", btcTx.getHash());
546
547 if (activations.isActive(ConsensusRule.RSKIP181)) {
548 eventLogger.logRejectedPegin(btcTx, RejectedPeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER);
549 }
550
551 throw new RegisterBtcTransactionException("Could not get BtcLockSender from Btc tx");
552 }
553
554 // Confirm we should process this lock
555 if (shouldProcessPegInVersionLegacy(senderBtcAddressType, btcTx, senderBtcAddress, totalAmount, height)) {
556 executePegIn(btcTx, peginInformation, totalAmount);
557 } else {
558 if (activations.isActive(ConsensusRule.RSKIP181)) {
559 if (!isTxLockableForLegacyVersion(senderBtcAddressType, btcTx, senderBtcAddress)) {
560 eventLogger.logRejectedPegin(btcTx, RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER);
561 } else if (!verifyLockDoesNotSurpassLockingCap(btcTx, totalAmount)) {
562 eventLogger.logRejectedPegin(btcTx, RejectedPeginReason.PEGIN_CAP_SURPASSED);
563 }
564 }
565
566 generateRejectionRelease(btcTx, senderBtcAddress, rskTx, totalAmount);
567 }
568 }
569
570 private void processPegInVersion1(
571 BtcTransaction btcTx,
572 Transaction rskTx,
573 PeginInformation peginInformation,
574 Coin totalAmount) throws RegisterBtcTransactionException, IOException {
575
576 if (!activations.isActive(ConsensusRule.RSKIP170)) {
577 throw new RegisterBtcTransactionException("Can't process version 1 peg-ins before RSKIP 170 activation");
578 }
579
580 // Confirm we should process this lock
581 if (verifyLockDoesNotSurpassLockingCap(btcTx, totalAmount)) {
582 executePegIn(btcTx, peginInformation, totalAmount);
583 } else {
584 logger.debug("[processPegInVersion1] Peg-in attempt surpasses locking cap. Amount attempted to lock: {}", totalAmount);
585
586 if (activations.isActive(ConsensusRule.RSKIP181)) {
587 eventLogger.logRejectedPegin(btcTx, RejectedPeginReason.PEGIN_CAP_SURPASSED);
588 }
589
590 refundTxSender(btcTx, rskTx, peginInformation, totalAmount);
591 }
592 }
593
594 private void executePegIn(BtcTransaction btcTx, PeginInformation peginInformation, Coin amount) throws IOException {
595 RskAddress rskDestinationAddress = peginInformation.getRskDestinationAddress();
596 Address senderBtcAddress = peginInformation.getSenderBtcAddress();
597 TxSenderAddressType senderBtcAddressType = peginInformation.getSenderBtcAddressType();
598 int protocolVersion = peginInformation.getProtocolVersion();
599 co.rsk.core.Coin amountInWeis = co.rsk.core.Coin.fromBitcoin(amount);
600
601 logger.debug("[executePegIn] [btcTx:{}] Is a lock from a {} sender", btcTx.getHash(), senderBtcAddressType);
602 this.transferTo(peginInformation.getRskDestinationAddress(), amountInWeis);
603 logger.info(
604 "[executePegIn] Transferring from BTC Address {}. RSK Address: {}. Amount: {}",
605 senderBtcAddress,
606 rskDestinationAddress,
607 amountInWeis
608 );
609
610 if (activations.isActive(ConsensusRule.RSKIP146)) {
611 if (activations.isActive(ConsensusRule.RSKIP170)) {
612 eventLogger.logPeginBtc(rskDestinationAddress, btcTx, amount, protocolVersion);
613 } else {
614 eventLogger.logLockBtc(rskDestinationAddress, btcTx, senderBtcAddress, amount);
615 }
616 }
617
618 // Save UTXOs from the federation(s) only if we actually locked the funds
619 saveNewUTXOs(btcTx);
620 }
621
622 private void refundTxSender(
623 BtcTransaction btcTx,
624 Transaction rskTx,
625 PeginInformation peginInformation,
626 Coin amount) throws IOException {
627
628 Address btcRefundAddress = peginInformation.getBtcRefundAddress();
629 if (btcRefundAddress != null) {
630 generateRejectionRelease(btcTx, btcRefundAddress, rskTx, amount);
631 } else {
632 logger.debug("[refundTxSender] No btc refund address provided, couldn't get sender address either. Can't refund");
633
634 if (activations.isActive(ConsensusRule.RSKIP181)) {
635 if (peginInformation.getProtocolVersion() == 1) {
636 eventLogger.logUnrefundablePegin(btcTx, UnrefundablePeginReason.PEGIN_V1_REFUND_ADDRESS_NOT_SET);
637 } else {
638 eventLogger.logUnrefundablePegin(btcTx, UnrefundablePeginReason.LEGACY_PEGIN_UNDETERMINED_SENDER);
639 }
640 }
641 }
642 }
643
644 private void markTxAsProcessed(BtcTransaction btcTx) throws IOException {
645 // Mark tx as processed on this block (and use the txid without the witness)
646 provider.setHeightBtcTxhashAlreadyProcessed(btcTx.getHash(false), rskExecutionBlock.getNumber());
647 }
648
649 protected void processRelease(BtcTransaction btcTx, Sha256Hash btcTxHash) throws IOException {
650 logger.debug("[processRelease] This is a release tx {}", btcTx);
651 // do-nothing
652 // We could call removeUsedUTXOs(btcTx) here, but we decided to not do that.
653 // Used utxos should had been removed when we created the release tx.
654 // Invoking removeUsedUTXOs() here would make "some" sense in theses scenarios:
655 // a) In testnet, devnet or local: we restart the RSK blockchain without changing the federation address. We don't want to have utxos that were already spent.
656 // Open problem: TxA spends TxB. registerBtcTransaction() for TxB is called, it spends a utxo the bridge is not yet aware of,
657 // so nothing is removed. Then registerBtcTransaction() for TxA and the "already spent" utxo is added as it was not spent.
658 // When is not guaranteed to be called in the chronological order, so a Federator can inform
659 // b) In prod: Federator created a tx manually or the federation was compromised and some utxos were spent. Better not try to spend them.
660 // Open problem: For performance removeUsedUTXOs() just removes 1 utxo
661
662 markTxAsProcessed(btcTx);
663
664 // Generate new change UTXO
665 saveNewUTXOs(btcTx);
666 logger.info("[processRelease] BTC Tx {} processed in RSK", btcTxHash);
667 }
668
669 protected void processMigration(BtcTransaction btcTx, Sha256Hash btcTxHash) throws IOException {
670 logger.debug("[processMigration] This is a migration tx {}", btcTx);
671
672 markTxAsProcessed(btcTx);
673
674 // Input spent on retiring federation and a new UTXO that is created on active federation.
675 // It is probably merging multiple UTXOs from the retiring federation
676 saveNewUTXOs(btcTx);
677 logger.info("[processMigration] BTC Tx {} processed in RSK", btcTxHash);
678 }
679
680 private boolean shouldProcessPegInVersionLegacy(TxSenderAddressType txSenderAddressType, BtcTransaction btcTx,
681 Address senderBtcAddress, Coin totalAmount, int height) {
682 return isTxLockableForLegacyVersion(txSenderAddressType, btcTx, senderBtcAddress) &&
683 verifyLockSenderIsWhitelisted(senderBtcAddress, totalAmount, height) &&
684 verifyLockDoesNotSurpassLockingCap(btcTx, totalAmount);
685 }
686
687 protected boolean isTxLockableForLegacyVersion(TxSenderAddressType txSenderAddressType, BtcTransaction btcTx, Address senderBtcAddress) {
688
689 if (txSenderAddressType == TxSenderAddressType.P2PKH ||
690 (txSenderAddressType == TxSenderAddressType.P2SHP2WPKH && activations.isActive(ConsensusRule.RSKIP143))) {
691 return true;
692 } else {
693 logger.warn(
694 "[isTxLockableForLegacyVersion]: [btcTx:{}] Btc tx type not supported: {}, returning funds to sender: {}",
695 btcTx.getHash(),
696 txSenderAddressType,
697 senderBtcAddress
698 );
699 return false;
700 }
701 }
702
703 /**
704 * Internal method to transfer RSK to an RSK account
705 * It also produce the appropiate internal transaction subtrace if needed
706 *
707 * @param receiver address that receives the amount
708 * @param amount amount to transfer
709 */
710 private void transferTo(RskAddress receiver, co.rsk.core.Coin amount) {
711 rskRepository.transfer(
712 PrecompiledContracts.BRIDGE_ADDR,
713 receiver,
714 amount
715 );
716
717 DataWord from = DataWord.valueOf(PrecompiledContracts.BRIDGE_ADDR.getBytes());
718 DataWord to = DataWord.valueOf(receiver.getBytes());
719 long gas = 0L;
720 DataWord value = DataWord.valueOf(amount.getBytes());
721
722 TransferInvoke invoke = new TransferInvoke(from, to, gas, value);
723 ProgramResult result = ProgramResult.empty();
724 ProgramSubtrace subtrace = ProgramSubtrace.newCallSubtrace(CallType.CALL, invoke, result, null, Collections.emptyList());
725
726 logger.info("Transferred {} weis to {}", amount, receiver);
727
728 this.subtraces.add(subtrace);
729 }
730
731 /*
732 Add the btcTx outputs that send btc to the federation(s) to the UTXO list
733 */
734 private void saveNewUTXOs(BtcTransaction btcTx) throws IOException {
735 // Outputs to the active federation
736 List<TransactionOutput> outputsToTheActiveFederation = btcTx.getWalletOutputs(getActiveFederationWallet(
737 false));
738 for (TransactionOutput output : outputsToTheActiveFederation) {
739 UTXO utxo = new UTXO(btcTx.getHash(), output.getIndex(), output.getValue(), 0, btcTx.isCoinBase(), output.getScriptPubKey());
740 getActiveFederationBtcUTXOs().add(utxo);
741 }
742
743 // Outputs to the retiring federation (if any)
744 Wallet retiringFederationWallet = getRetiringFederationWallet(false);
745 if (retiringFederationWallet != null) {
746 List<TransactionOutput> outputsToTheRetiringFederation = btcTx.getWalletOutputs(retiringFederationWallet);
747 for (TransactionOutput output : outputsToTheRetiringFederation) {
748 UTXO utxo = new UTXO(btcTx.getHash(), output.getIndex(), output.getValue(), 0, btcTx.isCoinBase(), output.getScriptPubKey());
749 getRetiringFederationBtcUTXOs().add(utxo);
750 }
751 }
752 }
753
754 /**
755 * Initiates the process of sending coins back to BTC.
756 * This is the default contract method.
757 * The funds will be sent to the bitcoin address controlled by the private key that signed the rsk tx.
758 * The amount sent to the bridge in this tx will be the amount sent in the btc network minus fees.
759 * @param rskTx The rsk tx being executed.
760 * @throws IOException
761 */
762 public void releaseBtc(Transaction rskTx) throws IOException {
763 Coin value = rskTx.getValue().toBitcoin();
764 final RskAddress senderAddress = rskTx.getSender();
765 //as we can't send btc from contracts we want to send them back to the senderAddressStr
766 if (BridgeUtils.isContractTx(rskTx)) {
767 logger.trace("Contract {} tried to release funds. Release is just allowed from standard accounts.", rskTx);
768 if (activations.isActive(ConsensusRule.RSKIP185)) {
769 emitRejectEvent(value, senderAddress.toHexString(), RejectedPegoutReason.CALLER_CONTRACT);
770 return;
771 } else {
772 throw new Program.OutOfGasException("Contract calling releaseBTC");
773 }
774 }
775
776 Context.propagate(btcContext);
777 NetworkParameters btcParams = bridgeConstants.getBtcParams();
778 Address btcDestinationAddress = BridgeUtils.recoverBtcAddressFromEthTransaction(rskTx, btcParams);
779
780 requestRelease(btcDestinationAddress, value, rskTx);
781 }
782
783 private void refundAndEmitRejectEvent(Coin value, RskAddress senderAddress, RejectedPegoutReason reason) {
784 String senderAddressStr = senderAddress.toHexString();
785 logger.trace("Executing a refund of {} to {}. Reason: {}", value, senderAddressStr, reason);
786 rskRepository.transfer(
787 PrecompiledContracts.BRIDGE_ADDR,
788 senderAddress,
789 co.rsk.core.Coin.fromBitcoin(value)
790 );
791 emitRejectEvent(value, senderAddressStr, reason);
792 }
793
794 private void emitRejectEvent(Coin value, String senderAddressStr, RejectedPegoutReason reason) {
795 eventLogger.logReleaseBtcRequestRejected(senderAddressStr, value, reason);
796 }
797
798 /**
799 * Creates a request for BTC release and
800 * adds it to the request queue for it
801 * to be processed later.
802 *
803 * @param destinationAddress the destination BTC address.
804 * @param value the amount of BTC to release.
805 * @throws IOException
806 */
807 private void requestRelease(Address destinationAddress, Coin value, Transaction rskTx) throws IOException {
808 Optional<RejectedPegoutReason> optionalRejectedPegoutReason = Optional.empty();
809 if (activations.isActive(RSKIP219)) {
810 int pegoutSize = getRegularPegoutTxSize(getActiveFederation());
811 Coin feePerKB = getFeePerKb();
812 // The pegout transaction has a cost related to its size and the current feePerKB
813 // The actual cost cannot be asserted exactly so the calculation is approximated
814 // On top of this, the remainder after the fee should be enough for the user to be able to operate
815 // For this, the calculation includes an additional percentage to assert for this
816 Coin requireFundsForFee = feePerKB
817 .multiply(pegoutSize) // times the size in bytes
818 .divide(1000); // Get the s/b
819 requireFundsForFee = requireFundsForFee
820 .add(requireFundsForFee
821 .times(bridgeConstants.getMinimumPegoutValuePercentageToReceiveAfterFee())
822 .divide(100)
823 ); // add the gap
824
825 // The pegout value should be greater or equals than the max of these two values
826 Coin minValue = Coin.valueOf(Math.max(bridgeConstants.getMinimumPegoutTxValueInSatoshis().value, requireFundsForFee.value));
827
828 // Since Iris the peg-out the rule is that the minimum is inclusive
829 if (value.isLessThan(minValue)) {
830 optionalRejectedPegoutReason = Optional.of(
831 Objects.equals(minValue, requireFundsForFee) ?
832 RejectedPegoutReason.FEE_ABOVE_VALUE:
833 RejectedPegoutReason.LOW_AMOUNT
834 );
835 }
836 } else {
837 // For legacy peg-outs the rule stated that the minimum was exclusive
838 if (!value.isGreaterThan(bridgeConstants.getLegacyMinimumPegoutTxValueInSatoshis())) {
839 optionalRejectedPegoutReason = Optional.of(RejectedPegoutReason.LOW_AMOUNT);
840 }
841 }
842
843 if (optionalRejectedPegoutReason.isPresent()) {
844 logger.warn(
845 "releaseBtc ignored. To {}. Tx {}. Value {}. Reason: {}",
846 destinationAddress,
847 rskTx,
848 value,
849 optionalRejectedPegoutReason.get()
850 );
851 if (activations.isActive(ConsensusRule.RSKIP185)) {
852 refundAndEmitRejectEvent(
853 value,
854 rskTx.getSender(),
855 optionalRejectedPegoutReason.get()
856 );
857 }
858 } else {
859 if (activations.isActive(ConsensusRule.RSKIP146)) {
860 provider.getReleaseRequestQueue().add(destinationAddress, value, rskTx.getHash());
861 } else {
862 provider.getReleaseRequestQueue().add(destinationAddress, value);
863 }
864
865 if (activations.isActive(ConsensusRule.RSKIP185)) {
866 eventLogger.logReleaseBtcRequestReceived(rskTx.getSender().toHexString(), destinationAddress.getHash160(), value);
867 }
868 logger.info("releaseBtc succesful to {}. Tx {}. Value {}.", destinationAddress, rskTx, value);
869 }
870 }
871
872 /**
873 * @return Current fee per kb in BTC.
874 */
875 public Coin getFeePerKb() {
876 Coin currentFeePerKb = provider.getFeePerKb();
877
878 if (currentFeePerKb == null) {
879 currentFeePerKb = bridgeConstants.getGenesisFeePerKb();
880 }
881
882 return currentFeePerKb;
883 }
884
885 /**
886 * Executed every now and then.
887 * Performs a few tasks: processing of any pending btc funds
888 * migrations from retiring federations;
889 * processing of any outstanding btc release requests; and
890 * processing of any outstanding release btc transactions.
891 * @throws IOException
892 * @param rskTx current RSK transaction
893 */
894 public void updateCollections(Transaction rskTx) throws IOException {
895 Context.propagate(btcContext);
896
897 eventLogger.logUpdateCollections(rskTx);
898
899 processFundsMigration(rskTx);
900
901 processReleaseRequests();
902
903 processReleaseTransactions(rskTx);
904
905 updateFederationCreationBlockHeights();
906 }
907
908 private boolean federationIsInMigrationAge(Federation federation) {
909 long federationAge = rskExecutionBlock.getNumber() - federation.getCreationBlockNumber();
910 long ageBegin = bridgeConstants.getFederationActivationAge() + bridgeConstants.getFundsMigrationAgeSinceActivationBegin();
911 long ageEnd = bridgeConstants.getFederationActivationAge() + bridgeConstants.getFundsMigrationAgeSinceActivationEnd();
912
913 return federationAge > ageBegin && federationAge < ageEnd;
914 }
915
916 private boolean federationIsPastMigrationAge(Federation federation) {
917 long federationAge = rskExecutionBlock.getNumber() - federation.getCreationBlockNumber();
918 long ageEnd = bridgeConstants.getFederationActivationAge() + bridgeConstants.getFundsMigrationAgeSinceActivationEnd();
919
920 return federationAge >= ageEnd;
921 }
922
923 private boolean hasMinimumFundsToMigrate(@Nullable Wallet retiringFederationWallet) {
924 // This value is set according to the average 500 bytes transaction size
925 Coin minimumFundsToMigrate = getFeePerKb().divide(2);
926 return retiringFederationWallet != null
927 && retiringFederationWallet.getBalance().isGreaterThan(minimumFundsToMigrate);
928 }
929
930 private void processFundsMigration(Transaction rskTx) throws IOException {
931 Wallet retiringFederationWallet = getRetiringFederationWallet(true);
932 List<UTXO> availableUTXOs = getRetiringFederationBtcUTXOs();
933 ReleaseTransactionSet releaseTransactionSet = provider.getReleaseTransactionSet();
934 Federation activeFederation = getActiveFederation();
935
936 if (federationIsInMigrationAge(activeFederation)
937 && hasMinimumFundsToMigrate(retiringFederationWallet)) {
938 logger.info("Active federation (age={}) is in migration age and retiring federation has funds to migrate: {}.",
939 rskExecutionBlock.getNumber() - activeFederation.getCreationBlockNumber(),
940 retiringFederationWallet.getBalance().toFriendlyString());
941
942 Pair<BtcTransaction, List<UTXO>> createResult = createMigrationTransaction(retiringFederationWallet, activeFederation.getAddress());
943 BtcTransaction btcTx = createResult.getLeft();
944 List<UTXO> selectedUTXOs = createResult.getRight();
945
946 // Add the TX to the release set
947 if (activations.isActive(ConsensusRule.RSKIP146)) {
948 Coin amountMigrated = selectedUTXOs.stream().map(UTXO::getValue)
949 .reduce(Coin.ZERO, Coin::add);
950 releaseTransactionSet.add(btcTx, rskExecutionBlock.getNumber(), rskTx.getHash());
951 // Log the Release request
952 eventLogger.logReleaseBtcRequested(rskTx.getHash().getBytes(), btcTx, amountMigrated);
953 } else {
954 releaseTransactionSet.add(btcTx, rskExecutionBlock.getNumber());
955 }
956
957 // Mark UTXOs as spent
958 availableUTXOs.removeIf(utxo -> selectedUTXOs.stream().anyMatch(selectedUtxo ->
959 utxo.getHash().equals(selectedUtxo.getHash()) &&
960 utxo.getIndex() == selectedUtxo.getIndex()
961 ));
962 }
963
964 if (retiringFederationWallet != null && federationIsPastMigrationAge(activeFederation)) {
965 if (retiringFederationWallet.getBalance().isGreaterThan(Coin.ZERO)) {
966 logger.info("Federation is past migration age and will try to migrate remaining balance: {}.",
967 retiringFederationWallet.getBalance().toFriendlyString());
968
969 try {
970 Pair<BtcTransaction, List<UTXO>> createResult = createMigrationTransaction(retiringFederationWallet, activeFederation.getAddress());
971 BtcTransaction btcTx = createResult.getLeft();
972 List<UTXO> selectedUTXOs = createResult.getRight();
973
974 // Add the TX to the release set
975 if (activations.isActive(ConsensusRule.RSKIP146)) {
976 Coin amountMigrated = selectedUTXOs.stream().map(UTXO::getValue)
977 .reduce(Coin.ZERO, Coin::add);
978 releaseTransactionSet.add(btcTx, rskExecutionBlock.getNumber(), rskTx.getHash());
979 // Log the Release request
980 eventLogger.logReleaseBtcRequested(rskTx.getHash().getBytes(), btcTx, amountMigrated);
981 } else {
982 releaseTransactionSet.add(btcTx, rskExecutionBlock.getNumber());
983 }
984
985 // Mark UTXOs as spent
986 availableUTXOs.removeIf(utxo -> selectedUTXOs.stream().anyMatch(selectedUtxo ->
987 utxo.getHash().equals(selectedUtxo.getHash()) &&
988 utxo.getIndex() == selectedUtxo.getIndex()
989 ));
990 } catch (Exception e) {
991 logger.error("Unable to complete retiring federation migration. Balance left: {} in {}",
992 retiringFederationWallet.getBalance().toFriendlyString(),
993 getRetiringFederationAddress());
994 panicProcessor.panic("updateCollection", "Unable to complete retiring federation migration.");
995 }
996 }
997
998 logger.info("Retiring federation migration finished. Available UTXOs left: {}.", availableUTXOs.size());
999 provider.setOldFederation(null);
1000 }
1001 }
1002
1003 /**
1004 * Processes the current btc release request queue
1005 * and tries to build btc transactions using (and marking as spent)
1006 * the current active federation's utxos.
1007 * Newly created btc transactions are added to the btc release tx set,
1008 * and failed attempts are kept in the release queue for future
1009 * processing.
1010 *
1011 */
1012 private void processReleaseRequests() {
1013 final Wallet activeFederationWallet;
1014 final ReleaseRequestQueue releaseRequestQueue;
1015
1016 try {
1017 activeFederationWallet = getActiveFederationWallet(true);
1018 releaseRequestQueue = provider.getReleaseRequestQueue();
1019 } catch (IOException e) {
1020 logger.error("Unexpected error accessing storage while attempting to process release requests", e);
1021 return;
1022 }
1023
1024 // Releases are attempted using the currently active federation
1025 // wallet.
1026 final ReleaseTransactionBuilder txBuilder = new ReleaseTransactionBuilder(
1027 btcContext.getParams(),
1028 activeFederationWallet,
1029 getFederationAddress(),
1030 getFeePerKb(),
1031 activations
1032 );
1033
1034 releaseRequestQueue.process(MAX_RELEASE_ITERATIONS, (ReleaseRequestQueue.Entry releaseRequest) -> {
1035 Optional<ReleaseTransactionBuilder.BuildResult> result = txBuilder.buildAmountTo(
1036 releaseRequest.getDestination(),
1037 releaseRequest.getAmount()
1038 );
1039
1040 // Couldn't build a transaction to release these funds
1041 // Log the event and return false so that the request remains in the
1042 // queue for future processing.
1043 // Further logging is done at the tx builder level.
1044 if (!result.isPresent()) {
1045 logger.warn(
1046 "Couldn't build a release BTC tx for <{}, {}>",
1047 releaseRequest.getDestination().toBase58(),
1048 releaseRequest.getAmount());
1049 return false;
1050 }
1051
1052 // We have a BTC transaction, mark the UTXOs as spent and add the tx
1053 // to the release set.
1054
1055 List<UTXO> selectedUTXOs = result.get().getSelectedUTXOs();
1056 BtcTransaction generatedTransaction = result.get().getBtcTx();
1057 List<UTXO> availableUTXOs;
1058 ReleaseTransactionSet releaseTransactionSet;
1059
1060 // Attempt access to storage first
1061 // (any of these could fail and would invalidate both
1062 // the tx build and utxo selection, so treat as atomic)
1063 try {
1064 availableUTXOs = getActiveFederationBtcUTXOs();
1065 releaseTransactionSet = provider.getReleaseTransactionSet();
1066 } catch (IOException exception) {
1067 // Unexpected error accessing storage, log and fail
1068 logger.error(
1069 String.format(
1070 "Unexpected error accessing storage while attempting to add a release BTC tx for <%s, %s>",
1071 releaseRequest.getDestination().toString(),
1072 releaseRequest.getAmount().toString()
1073 ),
1074 exception
1075 );
1076 return false;
1077 }
1078
1079 if (activations.isActive(ConsensusRule.RSKIP146)) {
1080 Keccak256 rskTxHash = releaseRequest.getRskTxHash();
1081 // Add the TX
1082 releaseTransactionSet.add(generatedTransaction, rskExecutionBlock.getNumber(), rskTxHash);
1083 // For a short time period, there could be items in the release request queue that don't have the rskTxHash
1084 // (these are releases created right before the consensus rule activation, that weren't processed before its activation)
1085 // We shouldn't generate the event for those releases
1086 if (rskTxHash != null) {
1087 // Log the Release request
1088 eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), generatedTransaction, releaseRequest.getAmount());
1089 }
1090 } else {
1091 releaseTransactionSet.add(generatedTransaction, rskExecutionBlock.getNumber());
1092 }
1093
1094 // Mark UTXOs as spent
1095 availableUTXOs.removeAll(selectedUTXOs);
1096
1097 // TODO: (Ariel Mendelzon, 07/12/2017)
1098 // TODO: Balance adjustment assumes that change output is output with index 1.
1099 // TODO: This will change if we implement multiple releases per BTC tx, so
1100 // TODO: it would eventually need to be fixed.
1101 // Adjust balances in edge cases
1102 adjustBalancesIfChangeOutputWasDust(generatedTransaction, releaseRequest.getAmount());
1103
1104 return true;
1105 });
1106 }
1107
1108 /**
1109 * Processes the current btc release transaction set.
1110 * It basically looks for transactions with enough confirmations
1111 * and marks them as ready for signing as well as removes them
1112 * from the set.
1113 * @param rskTx the RSK transaction that is causing this processing.
1114 */
1115 private void processReleaseTransactions(Transaction rskTx) {
1116 final Map<Keccak256, BtcTransaction> txsWaitingForSignatures;
1117 final ReleaseTransactionSet releaseTransactionSet;
1118
1119 try {
1120 txsWaitingForSignatures = provider.getRskTxsWaitingForSignatures();
1121 releaseTransactionSet = provider.getReleaseTransactionSet();
1122 } catch (IOException e) {
1123 logger.error("Unexpected error accessing storage while attempting to process release btc transactions", e);
1124 return;
1125 }
1126
1127 // TODO: (Ariel Mendelzon - 07/12/2017)
1128 // TODO: at the moment, there can only be one btc transaction
1129 // TODO: per rsk transaction in the txsWaitingForSignatures
1130 // TODO: map, and the rest of the processing logic is
1131 // TODO: dependant upon this. That is the reason we
1132 // TODO: add only one btc transaction at a time
1133 // TODO: (at least at this stage).
1134
1135 // IMPORTANT: sliceWithEnoughConfirmations also modifies the transaction set in place
1136 Set<ReleaseTransactionSet.Entry> txsWithEnoughConfirmations = releaseTransactionSet.sliceWithConfirmations(
1137 rskExecutionBlock.getNumber(),
1138 bridgeConstants.getRsk2BtcMinimumAcceptableConfirmations(),
1139 Optional.of(1)
1140 );
1141 if (txsWithEnoughConfirmations.size() > 0) {
1142 ReleaseTransactionSet.Entry entry = txsWithEnoughConfirmations.iterator().next();
1143 // Since RSKIP176 we are moving back to using the updateCollections related txHash as the set key
1144 if (activations.isActive(ConsensusRule.RSKIP146) && !activations.isActive(ConsensusRule.RSKIP176)) {
1145 // The release transaction may have been created prior to the Consensus Rule activation
1146 // therefore it won't have a rskTxHash value, fallback to this transaction's hash
1147 txsWaitingForSignatures.put(entry.getRskTxHash() == null ? rskTx.getHash() : entry.getRskTxHash(), entry.getTransaction());
1148 }
1149 else {
1150 txsWaitingForSignatures.put(rskTx.getHash(), entry.getTransaction());
1151 }
1152 }
1153 }
1154
1155 private void updateFederationCreationBlockHeights() {
1156 if (!activations.isActive(RSKIP186)) {
1157 return;
1158 }
1159
1160 Optional<Long> nextFederationCreationBlockHeightOpt = provider.getNextFederationCreationBlockHeight();
1161 if (nextFederationCreationBlockHeightOpt.isPresent()) {
1162 long nextFederationCreationBlockHeight = nextFederationCreationBlockHeightOpt.get();
1163 long curBlockHeight = rskExecutionBlock.getNumber();
1164
1165 if (curBlockHeight >= nextFederationCreationBlockHeight + bridgeConstants.getFederationActivationAge()) {
1166 provider.setActiveFederationCreationBlockHeight(nextFederationCreationBlockHeight);
1167 provider.clearNextFederationCreationBlockHeight();
1168 }
1169 }
1170 }
1171
1172 /**
1173 * If federation change output value had to be increased to be non-dust, the federation now has
1174 * more BTC than it should. So, we burn some sBTC to make balances match.
1175 *
1176 * @param btcTx The btc tx that was just completed
1177 * @param sentByUser The number of sBTC originaly sent by the user
1178 */
1179 private void adjustBalancesIfChangeOutputWasDust(BtcTransaction btcTx, Coin sentByUser) {
1180 if (btcTx.getOutputs().size() <= 1) {
1181 // If there is no change, do-nothing
1182 return;
1183 }
1184 Coin sumInputs = Coin.ZERO;
1185 for (TransactionInput transactionInput : btcTx.getInputs()) {
1186 sumInputs = sumInputs.add(transactionInput.getValue());
1187 }
1188 Coin change = btcTx.getOutput(1).getValue();
1189 Coin spentByFederation = sumInputs.subtract(change);
1190 if (spentByFederation.isLessThan(sentByUser)) {
1191 Coin coinsToBurn = sentByUser.subtract(spentByFederation);
1192 this.transferTo(BURN_ADDRESS, co.rsk.core.Coin.fromBitcoin(coinsToBurn));
1193 }
1194 }
1195
1196 /**
1197 * Adds a federator signature to a btc release tx.
1198 * The hash for the signature must be calculated with Transaction.SigHash.ALL and anyoneCanPay=false. The signature must be canonical.
1199 * If enough signatures were added, ask federators to broadcast the btc release tx.
1200 *
1201 * @param federatorPublicKey Federator who is signing
1202 * @param signatures 1 signature per btc tx input
1203 * @param rskTxHash The id of the rsk tx
1204 */
1205 public void addSignature(BtcECKey federatorPublicKey, List<byte[]> signatures, byte[] rskTxHash) throws Exception {
1206 Context.propagate(btcContext);
1207
1208 Federation retiringFederation = getRetiringFederation();
1209 Federation activeFederation = getActiveFederation();
1210 Federation federation =
1211 activeFederation.hasBtcPublicKey(federatorPublicKey) ?
1212 activeFederation :
1213 (retiringFederation != null && retiringFederation.hasBtcPublicKey(federatorPublicKey) ?
1214 retiringFederation:
1215 null);
1216
1217 if (federation == null) {
1218 logger.warn("Supplied federator public key {} does not belong to any of the federators.", federatorPublicKey);
1219 return;
1220 }
1221
1222 BtcTransaction btcTx = provider.getRskTxsWaitingForSignatures().get(new Keccak256(rskTxHash));
1223 if (btcTx == null) {
1224 logger.warn("No tx waiting for signature for hash {}. Probably fully signed already.", new Keccak256(rskTxHash));
1225 return;
1226 }
1227 if (btcTx.getInputs().size() != signatures.size()) {
1228 logger.warn("Expected {} signatures but received {}.", btcTx.getInputs().size(), signatures.size());
1229 return;
1230 }
1231 eventLogger.logAddSignature(federatorPublicKey, btcTx, rskTxHash);
1232 processSigning(federatorPublicKey, signatures, rskTxHash, btcTx, federation);
1233 }
1234
1235 private void processSigning(BtcECKey federatorPublicKey, List<byte[]> signatures, byte[] rskTxHash, BtcTransaction btcTx, Federation federation) throws IOException {
1236 // Build input hashes for signatures
1237 int numInputs = btcTx.getInputs().size();
1238
1239 List<Sha256Hash> sighashes = new ArrayList<>();
1240 List<TransactionSignature> txSigs = new ArrayList<>();
1241 for (int i = 0; i < numInputs; i++) {
1242 TransactionInput txIn = btcTx.getInput(i);
1243 Script inputScript = txIn.getScriptSig();
1244 List<ScriptChunk> chunks = inputScript.getChunks();
1245 byte[] program = chunks.get(chunks.size() - 1).data;
1246 Script redeemScript = new Script(program);
1247 sighashes.add(btcTx.hashForSignature(i, redeemScript, BtcTransaction.SigHash.ALL, false));
1248 }
1249
1250 // Verify given signatures are correct before proceeding
1251 for (int i = 0; i < numInputs; i++) {
1252 BtcECKey.ECDSASignature sig;
1253 try {
1254 sig = BtcECKey.ECDSASignature.decodeFromDER(signatures.get(i));
1255 } catch (RuntimeException e) {
1256 logger.warn("Malformed signature for input {} of tx {}: {}", i, new Keccak256(rskTxHash), ByteUtil.toHexString(signatures.get(i)));
1257 return;
1258 }
1259
1260 Sha256Hash sighash = sighashes.get(i);
1261
1262 if (!federatorPublicKey.verify(sighash, sig)) {
1263 logger.warn(
1264 "Signature {} {} is not valid for hash {} and public key {}",
1265 i,
1266 ByteUtil.toHexString(sig.encodeToDER()),
1267 sighash,
1268 federatorPublicKey
1269 );
1270 return;
1271 }
1272
1273 TransactionSignature txSig = new TransactionSignature(sig, BtcTransaction.SigHash.ALL, false);
1274 txSigs.add(txSig);
1275 if (!txSig.isCanonical()) {
1276 logger.warn("Signature {} {} is not canonical.", i, ByteUtil.toHexString(signatures.get(i)));
1277 return;
1278 }
1279 }
1280
1281 // All signatures are correct. Proceed to signing
1282 for (int i = 0; i < numInputs; i++) {
1283 Sha256Hash sighash = sighashes.get(i);
1284 TransactionInput input = btcTx.getInput(i);
1285 Script inputScript = input.getScriptSig();
1286
1287 boolean alreadySignedByThisFederator = BridgeUtils.isInputSignedByThisFederator(
1288 federatorPublicKey,
1289 sighash,
1290 input);
1291
1292 // Sign the input if it wasn't already
1293 if (!alreadySignedByThisFederator) {
1294 try {
1295 int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorPublicKey);
1296 inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex, 1, 1);
1297 input.setScriptSig(inputScript);
1298 logger.debug("Tx input {} for tx {} signed.", i, new Keccak256(rskTxHash));
1299 } catch (IllegalStateException e) {
1300 Federation retiringFederation = getRetiringFederation();
1301 if (getActiveFederation().hasBtcPublicKey(federatorPublicKey)) {
1302 logger.debug("A member of the active federation is trying to sign a tx of the retiring one");
1303 return;
1304 } else if (retiringFederation != null && retiringFederation.hasBtcPublicKey(federatorPublicKey)) {
1305 logger.debug("A member of the retiring federation is trying to sign a tx of the active one");
1306 return;
1307 }
1308 throw e;
1309 }
1310 } else {
1311 logger.warn("Input {} of tx {} already signed by this federator.", i, new Keccak256(rskTxHash));
1312 break;
1313 }
1314 }
1315
1316 if (BridgeUtils.hasEnoughSignatures(btcContext, btcTx)) {
1317 logger.info("Tx fully signed {}. Hex: {}", btcTx, Hex.toHexString(btcTx.bitcoinSerialize()));
1318 provider.getRskTxsWaitingForSignatures().remove(new Keccak256(rskTxHash));
1319
1320 eventLogger.logReleaseBtc(btcTx, rskTxHash);
1321 } else if (logger.isDebugEnabled()) {
1322 int missingSignatures = BridgeUtils.countMissingSignatures(btcContext, btcTx);
1323 int neededSignatures = federation.getNumberOfSignaturesRequired();
1324 int signaturesCount = neededSignatures - missingSignatures;
1325
1326 logger.debug("Tx {} not yet fully signed. Requires {}/{} signatures but has {}",
1327 new Keccak256(rskTxHash), neededSignatures, getFederationSize(), signaturesCount);
1328 }
1329 }
1330
1331 /**
1332 * Returns the btc tx that federators need to sign or broadcast
1333 * @return a StateForFederator serialized in RLP
1334 */
1335 public byte[] getStateForBtcReleaseClient() throws IOException {
1336 StateForFederator stateForFederator = new StateForFederator(provider.getRskTxsWaitingForSignatures());
1337 return stateForFederator.getEncoded();
1338 }
1339
1340 /**
1341 * Returns the insternal state of the bridge
1342 * @return a BridgeState serialized in RLP
1343 */
1344 public byte[] getStateForDebugging() throws IOException, BlockStoreException {
1345 BridgeState stateForDebugging = new BridgeState(getBtcBlockchainBestChainHeight(), provider, activations);
1346
1347 return stateForDebugging.getEncoded();
1348 }
1349
1350 /**
1351 * Returns the bitcoin blockchain best chain height know by the bridge contract
1352 */
1353 public int getBtcBlockchainBestChainHeight() throws IOException, BlockStoreException {
1354 return getBtcBlockchainChainHead().getHeight();
1355 }
1356
1357 /**
1358 * Returns the bitcoin blockchain initial stored block height
1359 */
1360 public int getBtcBlockchainInitialBlockHeight() throws IOException {
1361 return getLowestBlock().getHeight();
1362 }
1363
1364 /**
1365 * @deprecated
1366 * Returns an array of block hashes known by the bridge contract.
1367 * Federators can use this to find what is the latest block in the mainchain the bridge has.
1368 * @return a List of bitcoin block hashes
1369 */
1370 @Deprecated
1371 public List<Sha256Hash> getBtcBlockchainBlockLocator() throws IOException, BlockStoreException {
1372 StoredBlock initialBtcStoredBlock = this.getLowestBlock();
1373 final int maxHashesToInform = 100;
1374 List<Sha256Hash> blockLocator = new ArrayList<>();
1375 StoredBlock cursor = getBtcBlockchainChainHead();
1376 int bestBlockHeight = cursor.getHeight();
1377 blockLocator.add(cursor.getHeader().getHash());
1378 if (bestBlockHeight > initialBtcStoredBlock.getHeight()) {
1379 boolean stop = false;
1380 int i = 0;
1381 try {
1382 while (blockLocator.size() <= maxHashesToInform && !stop) {
1383 int blockHeight = (int) (bestBlockHeight - Math.pow(2, i));
1384 if (blockHeight <= initialBtcStoredBlock.getHeight()) {
1385 blockLocator.add(initialBtcStoredBlock.getHeader().getHash());
1386 stop = true;
1387 } else {
1388 cursor = this.getPrevBlockAtHeight(cursor, blockHeight);
1389 blockLocator.add(cursor.getHeader().getHash());
1390 }
1391 i++;
1392 }
1393 } catch (Exception e) {
1394 logger.error("Failed to walk the block chain whilst constructing a locator");
1395 panicProcessor.panic("btcblockchain", "Failed to walk the block chain whilst constructing a locator");
1396 throw new RuntimeException(e);
1397 }
1398 if (!stop) {
1399 blockLocator.add(initialBtcStoredBlock.getHeader().getHash());
1400 }
1401 }
1402 return blockLocator;
1403 }
1404
1405 public byte[] getBtcBlockchainBestBlockHeader() throws BlockStoreException, IOException {
1406 return serializeBlockHeader(getBtcBlockchainChainHead());
1407 }
1408
1409 public byte[] getBtcBlockchainBlockHeaderByHash(Sha256Hash hash) throws IOException, BlockStoreException {
1410 this.ensureBtcBlockStore();
1411
1412 return serializeBlockHeader(btcBlockStore.get(hash));
1413 }
1414
1415 public byte[] getBtcBlockchainBlockHeaderByHeight(int height) throws BlockStoreException, IOException {
1416 Context.propagate(btcContext);
1417 this.ensureBtcBlockStore();
1418
1419 StoredBlock block = btcBlockStore.getStoredBlockAtMainChainHeight(height);
1420
1421 return serializeBlockHeader(block);
1422 }
1423
1424 public byte[] getBtcBlockchainParentBlockHeaderByHash(Sha256Hash hash) throws IOException, BlockStoreException {
1425 this.ensureBtcBlockStore();
1426
1427 StoredBlock block = btcBlockStore.get(hash);
1428
1429 if (block == null) {
1430 return ByteUtil.EMPTY_BYTE_ARRAY;
1431 }
1432
1433 return serializeBlockHeader(btcBlockStore.get(block.getHeader().getPrevBlockHash()));
1434 }
1435
1436 public Sha256Hash getBtcBlockchainBlockHashAtDepth(int depth) throws BlockStoreException, IOException {
1437 Context.propagate(btcContext);
1438 this.ensureBtcBlockStore();
1439
1440 StoredBlock head = btcBlockStore.getChainHead();
1441 int maxDepth = head.getHeight() - getLowestBlock().getHeight();
1442
1443 if (depth < 0 || depth > maxDepth) {
1444 throw new IndexOutOfBoundsException(String.format("Depth must be between 0 and %d", maxDepth));
1445 }
1446
1447 StoredBlock blockAtDepth = btcBlockStore.getStoredBlockAtMainChainDepth(depth);
1448 return blockAtDepth.getHeader().getHash();
1449 }
1450
1451 public Long getBtcTransactionConfirmationsGetCost(Object[] args) {
1452 final long BASIC_COST = 27_000;
1453 final long STEP_COST = 315;
1454 final long DOUBLE_HASH_COST = 144; // 72 * 2. 72 is the cost of the hash operation
1455
1456 Sha256Hash btcBlockHash;
1457 int branchHashesSize;
1458 try {
1459 btcBlockHash = Sha256Hash.wrap((byte[]) args[1]);
1460 Object[] merkleBranchHashesArray = (Object[]) args[3];
1461 branchHashesSize = merkleBranchHashesArray.length;
1462 } catch (NullPointerException | IllegalArgumentException e) {
1463 return BASIC_COST;
1464 }
1465
1466 // Dynamic cost based on the depth of the block that contains
1467 // the transaction. Find such depth first, then calculate
1468 // the cost.
1469 Context.propagate(btcContext);
1470 try {
1471 this.ensureBtcBlockStore();
1472 final StoredBlock block = btcBlockStore.getFromCache(btcBlockHash);
1473
1474 // Block not found, default to basic cost
1475 if (block == null) {
1476 return BASIC_COST;
1477 }
1478
1479 final int bestChainHeight = getBtcBlockchainBestChainHeight();
1480
1481 // Make sure calculated depth is >= 0
1482 final int blockDepth = Math.max(0, bestChainHeight - block.getHeight());
1483
1484 // Block too deep, default to basic cost
1485 if (blockDepth > BTC_TRANSACTION_CONFIRMATION_MAX_DEPTH) {
1486 return BASIC_COST;
1487 }
1488
1489 return BASIC_COST + blockDepth*STEP_COST + branchHashesSize*DOUBLE_HASH_COST;
1490 } catch (IOException | BlockStoreException e) {
1491 logger.warn("getBtcTransactionConfirmationsGetCost btcBlockHash:{} there was a problem " +
1492 "gathering the block depth while calculating the gas cost. " +
1493 "Defaulting to basic cost.", btcBlockHash, e);
1494 return BASIC_COST;
1495 }
1496 }
1497
1498 /**
1499 * @param btcTxHash The BTC transaction Hash
1500 * @param btcBlockHash The BTC block hash
1501 * @param merkleBranch The merkle branch
1502 * @throws BlockStoreException
1503 * @throws IOException
1504 */
1505 public Integer getBtcTransactionConfirmations(Sha256Hash btcTxHash, Sha256Hash btcBlockHash, MerkleBranch merkleBranch) throws BlockStoreException, IOException {
1506 Context.propagate(btcContext);
1507 this.ensureBtcBlockChain();
1508
1509 // Get the block using the given block hash
1510 StoredBlock block = btcBlockStore.getFromCache(btcBlockHash);
1511 if (block == null) {
1512 return BTC_TRANSACTION_CONFIRMATION_INEXISTENT_BLOCK_HASH_ERROR_CODE;
1513 }
1514
1515 final int bestChainHeight = getBtcBlockchainBestChainHeight();
1516
1517 // Prevent diving too deep in the blockchain to avoid high processing costs
1518 final int blockDepth = Math.max(0, bestChainHeight - block.getHeight());
1519 if (blockDepth > BTC_TRANSACTION_CONFIRMATION_MAX_DEPTH) {
1520 return BTC_TRANSACTION_CONFIRMATION_BLOCK_TOO_OLD_ERROR_CODE;
1521 }
1522
1523 try {
1524 StoredBlock storedBlock = btcBlockStore.getStoredBlockAtMainChainHeight(block.getHeight());
1525 // Make sure it belongs to the best chain
1526 if (storedBlock == null || !storedBlock.equals(block)){
1527 return BTC_TRANSACTION_CONFIRMATION_BLOCK_NOT_IN_BEST_CHAIN_ERROR_CODE;
1528 }
1529 } catch (BlockStoreException e) {
1530 logger.warn(String.format(
1531 "Illegal state trying to get block with hash %s",
1532 btcBlockHash
1533 ), e);
1534 return BTC_TRANSACTION_CONFIRMATION_INCONSISTENT_BLOCK_ERROR_CODE;
1535 }
1536
1537 Sha256Hash merkleRoot = merkleBranch.reduceFrom(btcTxHash);
1538
1539 if (!isBlockMerkleRootValid(merkleRoot, block.getHeader())) {
1540 return BTC_TRANSACTION_CONFIRMATION_INVALID_MERKLE_BRANCH_ERROR_CODE;
1541 }
1542
1543 return bestChainHeight - block.getHeight() + 1;
1544 }
1545
1546 private StoredBlock getPrevBlockAtHeight(StoredBlock cursor, int height) throws BlockStoreException {
1547 if (cursor.getHeight() == height) {
1548 return cursor;
1549 }
1550
1551 boolean stop = false;
1552 StoredBlock current = cursor;
1553 while (!stop) {
1554 current = current.getPrev(this.btcBlockStore);
1555 stop = current.getHeight() == height;
1556 }
1557 return current;
1558 }
1559
1560 /**
1561 * Returns whether a given btc transaction hash has already
1562 * been processed by the bridge.
1563 * @param btcTxHash the btc tx hash to check.
1564 * @return a Boolean indicating whether the given btc tx hash was
1565 * already processed by the bridge.
1566 * @throws IOException
1567 */
1568 public Boolean isBtcTxHashAlreadyProcessed(Sha256Hash btcTxHash) throws IOException {
1569 return provider.getHeightIfBtcTxhashIsAlreadyProcessed(btcTxHash).isPresent();
1570 }
1571
1572 /**
1573 * Returns the RSK blockchain height a given btc transaction hash
1574 * was processed at by the bridge.
1575 * @param btcTxHash the btc tx hash for which to retrieve the height.
1576 * @return a Long with the processed height. If the hash was not processed
1577 * -1 is returned.
1578 * @throws IOException
1579 */
1580 public Long getBtcTxHashProcessedHeight(Sha256Hash btcTxHash) throws IOException {
1581 // Return -1 if the transaction hasn't been processed
1582 return provider.getHeightIfBtcTxhashIsAlreadyProcessed(btcTxHash).orElse(-1L);
1583 }
1584
1585 /**
1586 * Returns if tx was already processed by the bridge
1587 * @param btcTxHash the btc tx hash for which to retrieve the height.
1588 * @return true or false according
1589 * @throws IOException
1590 * */
1591 public boolean isAlreadyBtcTxHashProcessed(Sha256Hash btcTxHash) throws IOException {
1592 if (getBtcTxHashProcessedHeight(btcTxHash) > -1L) {
1593 logger.warn("Supplied Btc Tx {} was already processed", btcTxHash);
1594 return true;
1595 }
1596
1597 return false;
1598 }
1599
1600 /**
1601 * Returns the currently active federation.
1602 * See getActiveFederationReference() for details.
1603 * @return the currently active federation.
1604 */
1605 public Federation getActiveFederation() {
1606 return federationSupport.getActiveFederation();
1607 }
1608
1609 /**
1610 * Returns the currently retiring federation.
1611 * See getRetiringFederationReference() for details.
1612 * @return the retiring federation.
1613 */
1614 @Nullable
1615 public Federation getRetiringFederation() {
1616 return federationSupport.getRetiringFederation();
1617 }
1618
1619 private List<UTXO> getActiveFederationBtcUTXOs() throws IOException {
1620 return federationSupport.getActiveFederationBtcUTXOs();
1621 }
1622
1623 private List<UTXO> getRetiringFederationBtcUTXOs() throws IOException {
1624 return federationSupport.getRetiringFederationBtcUTXOs();
1625 }
1626
1627 /**
1628 * Returns the federation bitcoin address.
1629 * @return the federation bitcoin address.
1630 */
1631 public Address getFederationAddress() {
1632 return getActiveFederation().getAddress();
1633 }
1634
1635 /**
1636 * Returns the federation's size
1637 * @return the federation size
1638 */
1639 public Integer getFederationSize() {
1640 return getActiveFederation().getBtcPublicKeys().size();
1641 }
1642
1643 /**
1644 * Returns the federation's minimum required signatures
1645 * @return the federation minimum required signatures
1646 */
1647 public Integer getFederationThreshold() {
1648 return getActiveFederation().getNumberOfSignaturesRequired();
1649 }
1650
1651 /**
1652 * Returns the public key of the federation's federator at the given index
1653 * @param index the federator's index (zero-based)
1654 * @return the federator's public key
1655 */
1656 public byte[] getFederatorPublicKey(int index) {
1657 return federationSupport.getFederatorBtcPublicKey(index);
1658 }
1659
1660 /**
1661 * Returns the public key of given type of the federation's federator at the given index
1662 * @param index the federator's index (zero-based)
1663 * @param keyType the key type
1664 * @return the federator's public key
1665 */
1666 public byte[] getFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType) {
1667 return federationSupport.getFederatorPublicKeyOfType(index, keyType);
1668 }
1669
1670 /**
1671 * Returns the federation's creation time
1672 * @return the federation creation time
1673 */
1674 public Instant getFederationCreationTime() {
1675 return getActiveFederation().getCreationTime();
1676 }
1677
1678 /**
1679 * Returns the federation's creation block number
1680 * @return the federation creation block number
1681 */
1682 public long getFederationCreationBlockNumber() {
1683 return getActiveFederation().getCreationBlockNumber();
1684 }
1685
1686 /**
1687 * Returns the retiring federation bitcoin address.
1688 * @return the retiring federation bitcoin address, null if no retiring federation exists
1689 */
1690 public Address getRetiringFederationAddress() {
1691 Federation retiringFederation = getRetiringFederation();
1692 if (retiringFederation == null) {
1693 return null;
1694 }
1695
1696 return retiringFederation.getAddress();
1697 }
1698
1699 /**
1700 * Returns the retiring federation's size
1701 * @return the retiring federation size, -1 if no retiring federation exists
1702 */
1703 public Integer getRetiringFederationSize() {
1704 Federation retiringFederation = getRetiringFederation();
1705 if (retiringFederation == null) {
1706 return -1;
1707 }
1708
1709 return retiringFederation.getBtcPublicKeys().size();
1710 }
1711
1712 /**
1713 * Returns the retiring federation's minimum required signatures
1714 * @return the retiring federation minimum required signatures, -1 if no retiring federation exists
1715 */
1716 public Integer getRetiringFederationThreshold() {
1717 Federation retiringFederation = getRetiringFederation();
1718 if (retiringFederation == null) {
1719 return -1;
1720 }
1721
1722 return retiringFederation.getNumberOfSignaturesRequired();
1723 }
1724
1725 /**
1726 * Returns the public key of the retiring federation's federator at the given index
1727 * @param index the retiring federator's index (zero-based)
1728 * @return the retiring federator's public key, null if no retiring federation exists
1729 */
1730 public byte[] getRetiringFederatorPublicKey(int index) {
1731 Federation retiringFederation = getRetiringFederation();
1732 if (retiringFederation == null) {
1733 return null;
1734 }
1735
1736 List<BtcECKey> publicKeys = retiringFederation.getBtcPublicKeys();
1737
1738 if (index < 0 || index >= publicKeys.size()) {
1739 throw new IndexOutOfBoundsException(String.format("Retiring federator index must be between 0 and %d", publicKeys.size() - 1));
1740 }
1741
1742 return publicKeys.get(index).getPubKey();
1743 }
1744
1745 /**
1746 * Returns the public key of the given type of the retiring federation's federator at the given index
1747 * @param index the retiring federator's index (zero-based)
1748 * @param keyType the key type
1749 * @return the retiring federator's public key of the given type, null if no retiring federation exists
1750 */
1751 public byte[] getRetiringFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType) {
1752 Federation retiringFederation = getRetiringFederation();
1753 if (retiringFederation == null) {
1754 return null;
1755 }
1756
1757 return federationSupport.getMemberPublicKeyOfType(retiringFederation.getMembers(), index, keyType, "Retiring federator");
1758 }
1759
1760 /**
1761 * Returns the retiring federation's creation time
1762 * @return the retiring federation creation time, null if no retiring federation exists
1763 */
1764 public Instant getRetiringFederationCreationTime() {
1765 Federation retiringFederation = getRetiringFederation();
1766 if (retiringFederation == null) {
1767 return null;
1768 }
1769
1770 return retiringFederation.getCreationTime();
1771 }
1772
1773 /**
1774 * Returns the retiring federation's creation block number
1775 * @return the retiring federation creation block number,
1776 * -1 if no retiring federation exists
1777 */
1778 public long getRetiringFederationCreationBlockNumber() {
1779 Federation retiringFederation = getRetiringFederation();
1780 if (retiringFederation == null) {
1781 return -1L;
1782 }
1783 return retiringFederation.getCreationBlockNumber();
1784 }
1785
1786 /**
1787 * Returns the currently live federations
1788 * This would be the active federation plus
1789 * potentially the retiring federation
1790 * @return a list of live federations
1791 */
1792 private List<Federation> getLiveFederations() {
1793 List<Federation> liveFederations = new ArrayList<>();
1794 liveFederations.add(getActiveFederation());
1795 Federation retiringFederation = getRetiringFederation();
1796 if (retiringFederation != null) {
1797 liveFederations.add(retiringFederation);
1798 }
1799 return liveFederations;
1800 }
1801
1802 /**
1803 * Creates a new pending federation
1804 * If there's currently no pending federation and no funds remain
1805 * to be moved from a previous federation, a new one is created.
1806 * Otherwise, -1 is returned if there's already a pending federation,
1807 * -2 is returned if there is a federation awaiting to be active,
1808 * or -3 if funds are left from a previous one.
1809 * @param dryRun whether to just do a dry run
1810 * @return 1 upon success, -1 when a pending federation is present,
1811 * -2 when a federation is to be activated,
1812 * and if -3 funds are still to be moved between federations.
1813 */
1814 private Integer createFederation(boolean dryRun) {
1815 PendingFederation currentPendingFederation = provider.getPendingFederation();
1816
1817 if (currentPendingFederation != null) {
1818 return -1;
1819 }
1820
1821 if (federationSupport.amAwaitingFederationActivation()) {
1822 return -2;
1823 }
1824
1825 if (getRetiringFederation() != null) {
1826 return -3;
1827 }
1828
1829 if (dryRun) {
1830 return 1;
1831 }
1832
1833 currentPendingFederation = new PendingFederation(Collections.emptyList());
1834
1835 provider.setPendingFederation(currentPendingFederation);
1836
1837 // Clear votes on election
1838 provider.getFederationElection(bridgeConstants.getFederationChangeAuthorizer()).clear();
1839
1840 return 1;
1841 }
1842
1843 /**
1844 * Adds the given keys to the current pending federation.
1845 *
1846 * @param dryRun whether to just do a dry run
1847 * @param btcKey the BTC public key to add
1848 * @param rskKey the RSK public key to add
1849 * @param mstKey the MST public key to add
1850 * @return 1 upon success, -1 if there was no pending federation, -2 if the key was already in the pending federation
1851 */
1852 private Integer addFederatorPublicKeyMultikey(boolean dryRun, BtcECKey btcKey, ECKey rskKey, ECKey mstKey) {
1853 PendingFederation currentPendingFederation = provider.getPendingFederation();
1854
1855 if (currentPendingFederation == null) {
1856 return -1;
1857 }
1858
1859 if (currentPendingFederation.getBtcPublicKeys().contains(btcKey) ||
1860 currentPendingFederation.getMembers().stream().map(FederationMember::getRskPublicKey).anyMatch(k -> k.equals(rskKey)) ||
1861 currentPendingFederation.getMembers().stream().map(FederationMember::getMstPublicKey).anyMatch(k -> k.equals(mstKey))) {
1862 return -2;
1863 }
1864
1865 if (dryRun) {
1866 return 1;
1867 }
1868
1869 FederationMember member = new FederationMember(btcKey, rskKey, mstKey);
1870
1871 currentPendingFederation = currentPendingFederation.addMember(member);
1872
1873 provider.setPendingFederation(currentPendingFederation);
1874
1875 return 1;
1876 }
1877
1878 /**
1879 * Commits the currently pending federation.
1880 * That is, the retiring federation is set to be the currently active federation,
1881 * the active federation is replaced with a new federation generated from the pending federation,
1882 * and the pending federation is wiped out.
1883 * Also, UTXOs are moved from active to retiring so that the transfer of funds can
1884 * begin.
1885 * @param dryRun whether to just do a dry run
1886 * @param hash the pending federation's hash. This is checked the execution block's pending federation hash for equality.
1887 * @return 1 upon success, -1 if there was no pending federation, -2 if the pending federation was incomplete,
1888 * -3 if the given hash doesn't match the current pending federation's hash.
1889 */
1890 protected Integer commitFederation(boolean dryRun, Keccak256 hash) throws IOException {
1891 PendingFederation currentPendingFederation = provider.getPendingFederation();
1892
1893 if (currentPendingFederation == null) {
1894 return -1;
1895 }
1896
1897 if (!currentPendingFederation.isComplete()) {
1898 return -2;
1899 }
1900
1901 if (!hash.equals(currentPendingFederation.getHash())) {
1902 return -3;
1903 }
1904
1905 if (dryRun) {
1906 return 1;
1907 }
1908
1909 // Move UTXOs from the new federation into the old federation
1910 // and clear the new federation's UTXOs
1911 List<UTXO> utxosToMove = new ArrayList<>(provider.getNewFederationBtcUTXOs());
1912 provider.getNewFederationBtcUTXOs().clear();
1913 List<UTXO> oldFederationUTXOs = provider.getOldFederationBtcUTXOs();
1914 oldFederationUTXOs.clear();
1915 oldFederationUTXOs.addAll(utxosToMove);
1916
1917 // Network parameters for the new federation are taken from the bridge constants.
1918 // Creation time is the block's timestamp.
1919 Instant creationTime = Instant.ofEpochMilli(rskExecutionBlock.getTimestamp());
1920 Federation oldFederation = getActiveFederation();
1921 provider.setOldFederation(oldFederation);
1922 provider.setNewFederation(
1923 currentPendingFederation.buildFederation(
1924 creationTime,
1925 rskExecutionBlock.getNumber(),
1926 bridgeConstants,
1927 activations
1928 )
1929 );
1930 provider.setPendingFederation(null);
1931
1932 // Clear votes on election
1933 provider.getFederationElection(bridgeConstants.getFederationChangeAuthorizer()).clear();
1934
1935 if (activations.isActive(RSKIP186)) {
1936 // Preserve federation change info
1937 long nextFederationCreationBlockHeight = rskExecutionBlock.getNumber();
1938 provider.setNextFederationCreationBlockHeight(nextFederationCreationBlockHeight);
1939 Script oldFederationP2SHScript = oldFederation.getP2SHScript();
1940 provider.setLastRetiredFederationP2SHScript(oldFederationP2SHScript);
1941 }
1942
1943 logger.debug("[commitFederation] New Federation committed: {}", provider.getNewFederation().getAddress());
1944 eventLogger.logCommitFederation(rskExecutionBlock, provider.getOldFederation(), provider.getNewFederation());
1945
1946 return 1;
1947 }
1948
1949 /**
1950 * Rolls back the currently pending federation
1951 * That is, the pending federation is wiped out.
1952 * @param dryRun whether to just do a dry run
1953 * @return 1 upon success, 1 if there was no pending federation
1954 */
1955 private Integer rollbackFederation(boolean dryRun) {
1956 PendingFederation currentPendingFederation = provider.getPendingFederation();
1957
1958 if (currentPendingFederation == null) {
1959 return -1;
1960 }
1961
1962 if (dryRun) {
1963 return 1;
1964 }
1965
1966 provider.setPendingFederation(null);
1967
1968 // Clear votes on election
1969 provider.getFederationElection(bridgeConstants.getFederationChangeAuthorizer()).clear();
1970
1971 return 1;
1972 }
1973
1974 public Integer voteFederationChange(Transaction tx, ABICallSpec callSpec) throws BridgeIllegalArgumentException {
1975 // Must be on one of the allowed functions
1976 if (!FEDERATION_CHANGE_FUNCTIONS.contains(callSpec.getFunction())) {
1977 return FEDERATION_CHANGE_GENERIC_ERROR_CODE;
1978 }
1979
1980 AddressBasedAuthorizer authorizer = bridgeConstants.getFederationChangeAuthorizer();
1981
1982 // Must be authorized to vote (checking for signature)
1983 if (!authorizer.isAuthorized(tx)) {
1984 return FEDERATION_CHANGE_GENERIC_ERROR_CODE;
1985 }
1986
1987 // Try to do a dry-run and only register the vote if the
1988 // call would be successful
1989 ABICallVoteResult result;
1990 try {
1991 result = executeVoteFederationChangeFunction(true, callSpec);
1992 } catch (IOException | BridgeIllegalArgumentException e) {
1993 result = new ABICallVoteResult(false, FEDERATION_CHANGE_GENERIC_ERROR_CODE);
1994 }
1995
1996 // Return if the dry run failed or we are on a reversible execution
1997 if (!result.wasSuccessful()) {
1998 return (Integer) result.getResult();
1999 }
2000
2001 ABICallElection election = provider.getFederationElection(authorizer);
2002 // Register the vote. It is expected to succeed, since all previous checks succeeded
2003 if (!election.vote(callSpec, tx.getSender())) {
2004 logger.warn("Unexpected federation change vote failure");
2005 return FEDERATION_CHANGE_GENERIC_ERROR_CODE;
2006 }
2007
2008 // If enough votes have been reached, then actually execute the function
2009 ABICallSpec winnerSpec = election.getWinner();
2010 if (winnerSpec != null) {
2011 try {
2012 result = executeVoteFederationChangeFunction(false, winnerSpec);
2013 } catch (IOException e) {
2014 logger.warn("Unexpected federation change vote exception: {}", e.getMessage());
2015 return FEDERATION_CHANGE_GENERIC_ERROR_CODE;
2016 } finally {
2017 // Clear the winner so that we don't repeat ourselves
2018 election.clearWinners();
2019 }
2020 }
2021
2022 return (Integer) result.getResult();
2023 }
2024
2025 private ABICallVoteResult executeVoteFederationChangeFunction(boolean dryRun, ABICallSpec callSpec) throws IOException, BridgeIllegalArgumentException {
2026 // Try to do a dry-run and only register the vote if the
2027 // call would be successful
2028 ABICallVoteResult result;
2029 Integer executionResult;
2030 switch (callSpec.getFunction()) {
2031 case "create":
2032 executionResult = createFederation(dryRun);
2033 result = new ABICallVoteResult(executionResult == 1, executionResult);
2034 break;
2035 case "add":
2036 byte[] publicKeyBytes = callSpec.getArguments()[0];
2037 BtcECKey publicKey;
2038 ECKey publicKeyEc;
2039 try {
2040 publicKey = BtcECKey.fromPublicOnly(publicKeyBytes);
2041 publicKeyEc = ECKey.fromPublicOnly(publicKeyBytes);
2042 } catch (Exception e) {
2043 throw new BridgeIllegalArgumentException("Public key could not be parsed " + ByteUtil.toHexString(publicKeyBytes), e);
2044 }
2045 executionResult = addFederatorPublicKeyMultikey(dryRun, publicKey, publicKeyEc, publicKeyEc);
2046 result = new ABICallVoteResult(executionResult == 1, executionResult);
2047 break;
2048 case "add-multi":
2049 BtcECKey btcPublicKey;
2050 ECKey rskPublicKey, mstPublicKey;
2051 try {
2052 btcPublicKey = BtcECKey.fromPublicOnly(callSpec.getArguments()[0]);
2053 } catch (Exception e) {
2054 throw new BridgeIllegalArgumentException("BTC public key could not be parsed " + ByteUtil.toHexString(callSpec.getArguments()[0]), e);
2055 }
2056
2057 try {
2058 rskPublicKey = ECKey.fromPublicOnly(callSpec.getArguments()[1]);
2059 } catch (Exception e) {
2060 throw new BridgeIllegalArgumentException("RSK public key could not be parsed " + ByteUtil.toHexString(callSpec.getArguments()[1]), e);
2061 }
2062
2063 try {
2064 mstPublicKey = ECKey.fromPublicOnly(callSpec.getArguments()[2]);
2065 } catch (Exception e) {
2066 throw new BridgeIllegalArgumentException("MST public key could not be parsed " + ByteUtil.toHexString(callSpec.getArguments()[2]), e);
2067 }
2068 executionResult = addFederatorPublicKeyMultikey(dryRun, btcPublicKey, rskPublicKey, mstPublicKey);
2069 result = new ABICallVoteResult(executionResult == 1, executionResult);
2070 break;
2071 case "commit":
2072 Keccak256 hash = new Keccak256((byte[]) callSpec.getArguments()[0]);
2073 executionResult = commitFederation(dryRun, hash);
2074 result = new ABICallVoteResult(executionResult == 1, executionResult);
2075 break;
2076 case "rollback":
2077 executionResult = rollbackFederation(dryRun);
2078 result = new ABICallVoteResult(executionResult == 1, executionResult);
2079 break;
2080 default:
2081 // Fail by default
2082 result = new ABICallVoteResult(false, FEDERATION_CHANGE_GENERIC_ERROR_CODE);
2083 }
2084
2085 return result;
2086 }
2087
2088 /**
2089 * Returns the currently pending federation hash, or null if none exists
2090 * @return the currently pending federation hash, or null if none exists
2091 */
2092 public byte[] getPendingFederationHash() {
2093 PendingFederation currentPendingFederation = provider.getPendingFederation();
2094
2095 if (currentPendingFederation == null) {
2096 return null;
2097 }
2098
2099 return currentPendingFederation.getHash().getBytes();
2100 }
2101
2102 /**
2103 * Returns the currently pending federation size, or -1 if none exists
2104 * @return the currently pending federation size, or -1 if none exists
2105 */
2106 public Integer getPendingFederationSize() {
2107 PendingFederation currentPendingFederation = provider.getPendingFederation();
2108
2109 if (currentPendingFederation == null) {
2110 return -1;
2111 }
2112
2113 return currentPendingFederation.getBtcPublicKeys().size();
2114 }
2115
2116 /**
2117 * Returns the currently pending federation federator's public key at the given index, or null if none exists
2118 * @param index the federator's index (zero-based)
2119 * @return the pending federation's federator public key
2120 */
2121 public byte[] getPendingFederatorPublicKey(int index) {
2122 PendingFederation currentPendingFederation = provider.getPendingFederation();
2123
2124 if (currentPendingFederation == null) {
2125 return null;
2126 }
2127
2128 List<BtcECKey> publicKeys = currentPendingFederation.getBtcPublicKeys();
2129
2130 if (index < 0 || index >= publicKeys.size()) {
2131 throw new IndexOutOfBoundsException(String.format("Federator index must be between 0 and %d", publicKeys.size() - 1));
2132 }
2133
2134 return publicKeys.get(index).getPubKey();
2135 }
2136
2137 /**
2138 * Returns the public key of the given type of the pending federation's federator at the given index
2139 * @param index the federator's index (zero-based)
2140 * @param keyType the key type
2141 * @return the pending federation's federator public key of given type
2142 */
2143 public byte[] getPendingFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType) {
2144 PendingFederation currentPendingFederation = provider.getPendingFederation();
2145
2146 if (currentPendingFederation == null) {
2147 return null;
2148 }
2149
2150 return federationSupport.getMemberPublicKeyOfType(currentPendingFederation.getMembers(), index, keyType, "Federator");
2151 }
2152
2153 /**
2154 * Returns the lock whitelist size, that is,
2155 * the number of whitelisted addresses
2156 * @return the lock whitelist size
2157 */
2158 public Integer getLockWhitelistSize() {
2159 return provider.getLockWhitelist().getSize();
2160 }
2161
2162 /**
2163 * Returns the lock whitelist address stored
2164 * at the given index, or null if the
2165 * index is out of bounds
2166 * @param index the index at which to get the address
2167 * @return the base58-encoded address stored at the given index, or null if index is out of bounds
2168 */
2169 public LockWhitelistEntry getLockWhitelistEntryByIndex(int index) {
2170 List<LockWhitelistEntry> entries = provider.getLockWhitelist().getAll();
2171
2172 if (index < 0 || index >= entries.size()) {
2173 return null;
2174 }
2175
2176 return entries.get(index);
2177 }
2178
2179 /**
2180 *
2181 * @param addressBase58
2182 * @return
2183 */
2184 public LockWhitelistEntry getLockWhitelistEntryByAddress(String addressBase58) {
2185 try {
2186 Address address = getParsedAddress(addressBase58);
2187 return provider.getLockWhitelist().get(address);
2188 } catch (AddressFormatException e) {
2189 logger.warn(INVALID_ADDRESS_FORMAT_MESSAGE, e);
2190 return null;
2191 }
2192 }
2193
2194 /**
2195 * Adds the given address to the lock whitelist.
2196 * Returns 1 upon success, or -1 if the address was
2197 * already in the whitelist.
2198 * @param addressBase58 the base58-encoded address to add to the whitelist
2199 * @param maxTransferValue the max amount of satoshis enabled to transfer for this address
2200 * @return 1 upon success, -1 if the address was already
2201 * in the whitelist, -2 if address is invalid
2202 * LOCK_WHITELIST_GENERIC_ERROR_CODE otherwise.
2203 */
2204 public Integer addOneOffLockWhitelistAddress(Transaction tx, String addressBase58, BigInteger maxTransferValue) {
2205 try {
2206 Address address = getParsedAddress(addressBase58);
2207 Coin maxTransferValueCoin = Coin.valueOf(maxTransferValue.longValueExact());
2208 return this.addLockWhitelistAddress(tx, new OneOffWhiteListEntry(address, maxTransferValueCoin));
2209 } catch (AddressFormatException e) {
2210 logger.warn(INVALID_ADDRESS_FORMAT_MESSAGE, e);
2211 return LOCK_WHITELIST_INVALID_ADDRESS_FORMAT_ERROR_CODE;
2212 }
2213 }
2214
2215 public Integer addUnlimitedLockWhitelistAddress(Transaction tx, String addressBase58) {
2216 try {
2217 Address address = getParsedAddress(addressBase58);
2218 return this.addLockWhitelistAddress(tx, new UnlimitedWhiteListEntry(address));
2219 } catch (AddressFormatException e) {
2220 logger.warn(INVALID_ADDRESS_FORMAT_MESSAGE, e);
2221 return LOCK_WHITELIST_INVALID_ADDRESS_FORMAT_ERROR_CODE;
2222 }
2223 }
2224
2225 private Integer addLockWhitelistAddress(Transaction tx, LockWhitelistEntry entry) {
2226 if (!isLockWhitelistChangeAuthorized(tx)) {
2227 return LOCK_WHITELIST_GENERIC_ERROR_CODE;
2228 }
2229
2230 LockWhitelist whitelist = provider.getLockWhitelist();
2231
2232 try {
2233 if (whitelist.isWhitelisted(entry.address())) {
2234 return LOCK_WHITELIST_ALREADY_EXISTS_ERROR_CODE;
2235 }
2236 whitelist.put(entry.address(), entry);
2237 return LOCK_WHITELIST_SUCCESS_CODE;
2238 } catch (Exception e) {
2239 logger.error("Unexpected error in addLockWhitelistAddress: {}", e.getMessage());
2240 panicProcessor.panic("lock-whitelist", e.getMessage());
2241 return LOCK_WHITELIST_UNKNOWN_ERROR_CODE;
2242 }
2243 }
2244
2245 private boolean isLockWhitelistChangeAuthorized(Transaction tx) {
2246 AddressBasedAuthorizer authorizer = bridgeConstants.getLockWhitelistChangeAuthorizer();
2247
2248 return authorizer.isAuthorized(tx);
2249 }
2250
2251 /**
2252 * Removes the given address from the lock whitelist.
2253 * Returns 1 upon success, or -1 if the address was
2254 * not in the whitelist.
2255 * @param addressBase58 the base58-encoded address to remove from the whitelist
2256 * @return 1 upon success, -1 if the address was not
2257 * in the whitelist, -2 if the address is invalid,
2258 * LOCK_WHITELIST_GENERIC_ERROR_CODE otherwise.
2259 */
2260 public Integer removeLockWhitelistAddress(Transaction tx, String addressBase58) {
2261 if (!isLockWhitelistChangeAuthorized(tx)) {
2262 return LOCK_WHITELIST_GENERIC_ERROR_CODE;
2263 }
2264
2265 LockWhitelist whitelist = provider.getLockWhitelist();
2266
2267 try {
2268 Address address = getParsedAddress(addressBase58);
2269
2270 if (!whitelist.remove(address)) {
2271 return -1;
2272 }
2273
2274 return 1;
2275 } catch (AddressFormatException e) {
2276 return -2;
2277 } catch (Exception e) {
2278 logger.error("Unexpected error in removeLockWhitelistAddress: {}", e.getMessage());
2279 panicProcessor.panic("lock-whitelist", e.getMessage());
2280 return 0;
2281 }
2282 }
2283
2284 /**
2285 * Returns the minimum amount of satoshis a user should send to the federation.
2286 * @return the minimum amount of satoshis a user should send to the federation.
2287 */
2288 public Coin getMinimumPeginTxValue() {
2289 return activations.isActive(RSKIP219) ? bridgeConstants.getMinimumPeginTxValueInSatoshis() : bridgeConstants.getLegacyMinimumPeginTxValueInSatoshis();
2290 }
2291
2292 /**
2293 * Votes for a fee per kb value.
2294 *
2295 * @return 1 upon successful vote, -1 when the vote was unsuccessful,
2296 * FEE_PER_KB_GENERIC_ERROR_CODE when there was an un expected error.
2297 */
2298 public Integer voteFeePerKbChange(Transaction tx, Coin feePerKb) {
2299 AddressBasedAuthorizer authorizer = bridgeConstants.getFeePerKbChangeAuthorizer();
2300 if (!authorizer.isAuthorized(tx)) {
2301 return FEE_PER_KB_GENERIC_ERROR_CODE;
2302 }
2303
2304 if(!feePerKb.isPositive()){
2305 return NEGATIVE_FEE_PER_KB_ERROR_CODE;
2306 }
2307
2308 if(feePerKb.isGreaterThan(bridgeConstants.getMaxFeePerKb())) {
2309 return EXCESSIVE_FEE_PER_KB_ERROR_CODE;
2310 }
2311
2312 ABICallElection feePerKbElection = provider.getFeePerKbElection(authorizer);
2313 ABICallSpec feeVote = new ABICallSpec("setFeePerKb", new byte[][]{BridgeSerializationUtils.serializeCoin(feePerKb)});
2314 boolean successfulVote = feePerKbElection.vote(feeVote, tx.getSender());
2315 if (!successfulVote) {
2316 return -1;
2317 }
2318
2319 ABICallSpec winner = feePerKbElection.getWinner();
2320 if (winner == null) {
2321 logger.info("Successful fee per kb vote for {}", feePerKb);
2322 return 1;
2323 }
2324
2325 Coin winnerFee;
2326 try {
2327 winnerFee = BridgeSerializationUtils.deserializeCoin(winner.getArguments()[0]);
2328 } catch (Exception e) {
2329 logger.warn("Exception deserializing winner feePerKb", e);
2330 return FEE_PER_KB_GENERIC_ERROR_CODE;
2331 }
2332
2333 if (winnerFee == null) {
2334 logger.warn("Invalid winner feePerKb: feePerKb can't be null");
2335 return FEE_PER_KB_GENERIC_ERROR_CODE;
2336 }
2337
2338 if (!winnerFee.equals(feePerKb)) {
2339 logger.debug("Winner fee is different than the last vote: maybe you forgot to clear winners");
2340 }
2341
2342 logger.info("Fee per kb changed to {}", winnerFee);
2343 provider.setFeePerKb(winnerFee);
2344 feePerKbElection.clear();
2345 return 1;
2346 }
2347
2348 /**
2349 * Sets a delay in the BTC best chain to disable lock whitelist
2350 * @param tx current RSK transaction
2351 * @param disableBlockDelayBI block since current BTC best chain height to disable lock whitelist
2352 * @return 1 if it was successful, -1 if a delay was already set, -2 if disableBlockDelay contains an invalid value
2353 */
2354 public Integer setLockWhitelistDisableBlockDelay(Transaction tx, BigInteger disableBlockDelayBI) throws IOException, BlockStoreException {
2355 if (!isLockWhitelistChangeAuthorized(tx)) {
2356 return LOCK_WHITELIST_GENERIC_ERROR_CODE;
2357 }
2358 LockWhitelist lockWhitelist = provider.getLockWhitelist();
2359 if (lockWhitelist.isDisableBlockSet()) {
2360 return -1;
2361 }
2362 int disableBlockDelay = disableBlockDelayBI.intValueExact();
2363 int bestChainHeight = getBtcBlockchainBestChainHeight();
2364 if (disableBlockDelay + bestChainHeight <= bestChainHeight) {
2365 return -2;
2366 }
2367 lockWhitelist.setDisableBlockHeight(bestChainHeight + disableBlockDelay);
2368 return 1;
2369 }
2370
2371 public Coin getLockingCap() {
2372 // Before returning the locking cap, check if it was already set
2373 if (activations.isActive(ConsensusRule.RSKIP134) && this.provider.getLockingCap() == null) {
2374 // Set the initial locking cap value
2375 logger.debug("Setting initial locking cap value");
2376 this.provider.setLockingCap(bridgeConstants.getInitialLockingCap());
2377 }
2378
2379 return this.provider.getLockingCap();
2380 }
2381
2382 public boolean increaseLockingCap(Transaction tx, Coin newCap) {
2383 // Only pre configured addresses can modify locking cap
2384 AddressBasedAuthorizer authorizer = bridgeConstants.getIncreaseLockingCapAuthorizer();
2385 if (!authorizer.isAuthorized(tx)) {
2386 logger.warn("not authorized address tried to increase locking cap. Address: {}", tx.getSender());
2387 return false;
2388 }
2389 // new locking cap must be bigger than current locking cap
2390 Coin currentLockingCap = this.getLockingCap();
2391 if (newCap.compareTo(currentLockingCap) < 0) {
2392 logger.warn("attempted value doesn't increase locking cap. Attempted: {}", newCap.value);
2393 return false;
2394 }
2395 Coin maxLockingCap = currentLockingCap.multiply(bridgeConstants.getLockingCapIncrementsMultiplier());
2396 if (newCap.compareTo(maxLockingCap) > 0) {
2397 logger.warn("attempted value increases locking cap above its limit. Attempted: {}", newCap.value);
2398 return false;
2399 }
2400
2401 logger.info("increased locking cap: {}", newCap.value);
2402 this.provider.setLockingCap(newCap);
2403
2404 return true;
2405 }
2406
2407 public void registerBtcCoinbaseTransaction(byte[] btcTxSerialized, Sha256Hash blockHash, byte[] pmtSerialized, Sha256Hash witnessMerkleRoot, byte[] witnessReservedValue) throws VMException {
2408 Context.propagate(btcContext);
2409 try{
2410 this.ensureBtcBlockStore();
2411 }catch (BlockStoreException | IOException e) {
2412 logger.warn("Exception in registerBtcCoinbaseTransaction", e);
2413 throw new VMException("Exception in registerBtcCoinbaseTransaction", e);
2414 }
2415
2416 Sha256Hash btcTxHash = BtcTransactionFormatUtils.calculateBtcTxHash(btcTxSerialized);
2417
2418 if (witnessReservedValue.length != 32) {
2419 logger.warn("[btcTx:{}] WitnessResevedValue length can't be different than 32 bytes", btcTxHash);
2420 throw new BridgeIllegalArgumentException("WitnessResevedValue length can't be different than 32 bytes");
2421 }
2422
2423 if (!PartialMerkleTreeFormatUtils.hasExpectedSize(pmtSerialized)) {
2424 logger.warn("[btcTx:{}] PartialMerkleTree doesn't have expected size", btcTxHash);
2425 throw new BridgeIllegalArgumentException("PartialMerkleTree doesn't have expected size");
2426 }
2427
2428 Sha256Hash merkleRoot;
2429
2430 try {
2431 PartialMerkleTree pmt = new PartialMerkleTree(bridgeConstants.getBtcParams(), pmtSerialized, 0);
2432 List<Sha256Hash> hashesInPmt = new ArrayList<>();
2433 merkleRoot = pmt.getTxnHashAndMerkleRoot(hashesInPmt);
2434 if (!hashesInPmt.contains(btcTxHash)) {
2435 logger.warn("Supplied Btc Tx {} is not in the supplied partial merkle tree", btcTxHash);
2436 return;
2437 }
2438 } catch (VerificationException e) {
2439 logger.warn("[btcTx:{}] PartialMerkleTree could not be parsed", btcTxHash);
2440 throw new BridgeIllegalArgumentException(String.format("PartialMerkleTree could not be parsed %s", ByteUtil.toHexString(pmtSerialized)), e);
2441 }
2442
2443 // Check merkle root equals btc block merkle root at the specified height in the btc best chain
2444 // Btc blockstore is available since we've already queried the best chain height
2445 StoredBlock storedBlock = btcBlockStore.getFromCache(blockHash);
2446 if (storedBlock == null) {
2447 logger.warn("[btcTx:{}] Block not registered", btcTxHash);
2448 throw new BridgeIllegalArgumentException(String.format("Block not registered %s", blockHash.toString()));
2449 }
2450 BtcBlock blockHeader = storedBlock.getHeader();
2451 if (!blockHeader.getMerkleRoot().equals(merkleRoot)) {
2452 String panicMessage = String.format(
2453 "Btc Tx %s Supplied merkle root %s does not match block's merkle root %s",
2454 btcTxHash.toString(),
2455 merkleRoot,
2456 blockHeader.getMerkleRoot()
2457 );
2458 logger.warn(panicMessage);
2459 panicProcessor.panic("btclock", panicMessage);
2460 return;
2461 }
2462
2463 BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized);
2464 btcTx.verify();
2465
2466 Sha256Hash witnessCommitment = Sha256Hash.twiceOf(witnessMerkleRoot.getReversedBytes(), witnessReservedValue);
2467
2468 if(!witnessCommitment.equals(btcTx.findWitnessCommitment())){
2469 logger.warn("[btcTx:{}] WitnessCommitment does not match", btcTxHash);
2470 throw new BridgeIllegalArgumentException("WitnessCommitment does not match");
2471 }
2472
2473 CoinbaseInformation coinbaseInformation = new CoinbaseInformation(witnessMerkleRoot);
2474 provider.setCoinbaseInformation(blockHeader.getHash(), coinbaseInformation);
2475
2476 logger.warn("[btcTx:{}] Registered coinbase information", btcTxHash);
2477 }
2478
2479 public boolean hasBtcBlockCoinbaseTransactionInformation(Sha256Hash blockHash) {
2480 CoinbaseInformation coinbaseInformation = provider.getCoinbaseInformation(blockHash);
2481 return coinbaseInformation != null;
2482 }
2483
2484 public long getActiveFederationCreationBlockHeight() {
2485 if (!activations.isActive(RSKIP186)) {
2486 return 0L;
2487 }
2488
2489 Optional<Long> nextFederationCreationBlockHeightOpt = provider.getNextFederationCreationBlockHeight();
2490 if (nextFederationCreationBlockHeightOpt.isPresent()) {
2491 long nextFederationCreationBlockHeight = nextFederationCreationBlockHeightOpt.get();
2492 long curBlockHeight = rskExecutionBlock.getNumber();
2493 if (curBlockHeight >= nextFederationCreationBlockHeight + bridgeConstants.getFederationActivationAge()) {
2494 return nextFederationCreationBlockHeight;
2495 }
2496 }
2497
2498 Optional<Long> activeFederationCreationBlockHeightOpt = provider.getActiveFederationCreationBlockHeight();
2499 return activeFederationCreationBlockHeightOpt.orElse(0L);
2500 }
2501
2502 public long registerFastBridgeBtcTransaction(
2503 Transaction rskTx,
2504 byte[] btcTxSerialized,
2505 int height,
2506 byte[] pmtSerialized,
2507 Keccak256 derivationArgumentsHash,
2508 Address userRefundAddress,
2509 RskAddress lbcAddress,
2510 Address lpBtcAddress,
2511 boolean shouldTransferToContract
2512 )
2513 throws BlockStoreException, IOException, BridgeIllegalArgumentException {
2514 if (!BridgeUtils.isContractTx(rskTx)) {
2515 logger.debug("[registerFastBridgeBtcTransaction] (rskTx:{}) Sender not a contract", rskTx.getHash());
2516 return FAST_BRIDGE_UNPROCESSABLE_TX_NOT_CONTRACT_ERROR_CODE;
2517 }
2518
2519 if (!rskTx.getSender().equals(lbcAddress)) {
2520 logger.debug(
2521 "[registerFastBridgeBtcTransaction] Expected sender to be the same as lbcAddress. (sender: {}) (lbcAddress:{})",
2522 rskTx.getSender(),
2523 lbcAddress
2524 );
2525 return FAST_BRIDGE_UNPROCESSABLE_TX_INVALID_SENDER_ERROR_CODE;
2526 }
2527
2528 Context.propagate(btcContext);
2529 Sha256Hash btcTxHash = BtcTransactionFormatUtils.calculateBtcTxHash(btcTxSerialized);
2530
2531 Keccak256 fastBridgeDerivationHash = getFastBridgeDerivationHash(
2532 derivationArgumentsHash,
2533 userRefundAddress,
2534 lpBtcAddress,
2535 lbcAddress
2536 );
2537
2538 if (provider.isFastBridgeFederationDerivationHashUsed(btcTxHash, fastBridgeDerivationHash)) {
2539 logger.debug("[registerFastBridgeBtcTransaction] Transaction and derivation hash already used");
2540 return FAST_BRIDGE_UNPROCESSABLE_TX_ALREADY_PROCESSED_ERROR_CODE;
2541 }
2542
2543 if (!validationsForRegisterBtcTransaction(btcTxHash, height, pmtSerialized, btcTxSerialized)) {
2544 logger.debug(
2545 "[registerFastBridgeBtcTransaction] (btcTx:{}) error during validationsForRegisterBtcTransaction",
2546 btcTxHash
2547 );
2548 return FAST_BRIDGE_UNPROCESSABLE_TX_VALIDATIONS_ERROR;
2549 }
2550
2551 BtcTransaction btcTx = new BtcTransaction(bridgeConstants.getBtcParams(), btcTxSerialized);
2552 btcTx.verify();
2553
2554 Sha256Hash btcTxHashWithoutWitness = btcTx.getHash(false);
2555 if (!btcTxHashWithoutWitness.equals(btcTxHash) &&
2556 (provider.isFastBridgeFederationDerivationHashUsed(btcTxHashWithoutWitness, derivationArgumentsHash))) {
2557 logger.debug("[registerFastBridgeBtcTransaction] Transaction and derivation hash already used");
2558 return FAST_BRIDGE_UNPROCESSABLE_TX_ALREADY_PROCESSED_ERROR_CODE;
2559 }
2560
2561 FastBridgeFederationInformation fastBridgeFederationInformation =
2562 createFastBridgeFederationInformation(fastBridgeDerivationHash);
2563
2564 Address fastBridgeFedAddress =
2565 fastBridgeFederationInformation.getFastBridgeFederationAddress(bridgeConstants.getBtcParams());
2566
2567 Coin totalAmount = getAmountSentToAddress(btcTx, fastBridgeFedAddress);
2568
2569 if (totalAmount == Coin.ZERO) {
2570 logger.debug("[registerFastBridgeBtcTransaction] Amount sent can't be 0");
2571 return FAST_BRIDGE_UNPROCESSABLE_TX_VALUE_ZERO_ERROR;
2572 }
2573
2574 if (!verifyLockDoesNotSurpassLockingCap(btcTx, totalAmount)) {
2575 InternalTransaction internalTx = (InternalTransaction)rskTx;
2576 logger.info("[registerFastBridgeBtcTransaction] Locking cap surpassed, going to return funds!");
2577 WalletProvider walletProvider = createFastBridgeWalletProvider(fastBridgeFederationInformation);
2578
2579 provider.markFastBridgeFederationDerivationHashAsUsed(btcTxHash, fastBridgeDerivationHash);
2580
2581 if (shouldTransferToContract) {
2582 logger.debug("[registerFastBridgeBtcTransaction] Returning to liquidity provider");
2583 generateRejectionRelease(btcTx, lpBtcAddress, fastBridgeFedAddress, new Keccak256(internalTx.getOriginHash()), totalAmount, walletProvider);
2584 return FAST_BRIDGE_REFUNDED_LP_ERROR_CODE;
2585 } else {
2586 logger.debug("[registerFastBridgeBtcTransaction] Returning to user");
2587 generateRejectionRelease(btcTx, userRefundAddress, fastBridgeFedAddress, new Keccak256(internalTx.getOriginHash()), totalAmount, walletProvider);
2588 return FAST_BRIDGE_REFUNDED_USER_ERROR_CODE;
2589 }
2590 }
2591
2592 transferTo(lbcAddress, co.rsk.core.Coin.fromBitcoin(totalAmount));
2593
2594 saveFastBridgeDataInStorage(
2595 btcTxHashWithoutWitness,
2596 fastBridgeDerivationHash,
2597 fastBridgeFederationInformation,
2598 getUTXOsForAddress(btcTx, fastBridgeFedAddress)
2599 );
2600
2601 logger.info("[registerFastBridgeBtcTransaction] (btcTx:{}) transaction registered successfully", btcTxHashWithoutWitness);
2602
2603 return totalAmount.getValue();
2604 }
2605
2606 protected FastBridgeFederationInformation createFastBridgeFederationInformation(Keccak256 fastBridgeDerivationHash) {
2607 Script fastBridgeScript = FastBridgeRedeemScriptParser.createMultiSigFastBridgeRedeemScript(
2608 getActiveFederation().getRedeemScript(),
2609 Sha256Hash.wrap(fastBridgeDerivationHash.getBytes())
2610 );
2611
2612 Script fastBridgeScriptHash = ScriptBuilder.createP2SHOutputScript(fastBridgeScript);
2613
2614 return new FastBridgeFederationInformation(
2615 fastBridgeDerivationHash,
2616 getActiveFederation().getP2SHScript().getPubKeyHash(),
2617 fastBridgeScriptHash.getPubKeyHash()
2618 );
2619 }
2620
2621 private WalletProvider createFastBridgeWalletProvider(
2622 FastBridgeFederationInformation fastBridgeFederationInformation) {
2623 return (BtcTransaction a, Address b) -> {
2624 List<UTXO> utxosList = getUTXOsForAddress(a, b);
2625 return getFastBridgeWallet(btcContext, utxosList, fastBridgeFederationInformation);
2626 };
2627 }
2628
2629 protected List<UTXO> getUTXOsForAddress(BtcTransaction btcTx, Address btcAddress) {
2630 List<UTXO> utxosList = new ArrayList<>();
2631 for (TransactionOutput o : btcTx.getOutputs()) {
2632 if (o.getScriptPubKey().getToAddress(bridgeConstants.getBtcParams()).equals(btcAddress)) {
2633 utxosList.add(
2634 new UTXO(
2635 btcTx.getHash(),
2636 o.getIndex(),
2637 o.getValue(),
2638 0,
2639 btcTx.isCoinBase(),
2640 o.getScriptPubKey()
2641 )
2642 );
2643 }
2644 }
2645
2646 return utxosList;
2647 }
2648
2649 protected Wallet getFastBridgeWallet(Context btcContext, List<UTXO> utxos, FastBridgeFederationInformation fb) {
2650 Wallet wallet = new FastBridgeCompatibleBtcWalletWithSingleScript(btcContext, getLiveFederations(), fb);
2651 RskUTXOProvider utxoProvider = new RskUTXOProvider(btcContext.getParams(), utxos);
2652 wallet.setUTXOProvider(utxoProvider);
2653 wallet.setCoinSelector(new RskAllowUnconfirmedCoinSelector());
2654 return wallet;
2655 }
2656
2657 protected Keccak256 getFastBridgeDerivationHash(
2658 Keccak256 derivationArgumentsHash,
2659 Address userRefundAddress,
2660 Address lpBtcAddress,
2661 RskAddress lbcAddress
2662 ) {
2663 byte[] fastBridgeDerivationHashData = derivationArgumentsHash.getBytes();
2664 byte[] userRefundAddressBytes = getBytesFromBtcAddress(userRefundAddress);
2665 byte[] lpBtcAddressBytes = getBytesFromBtcAddress(lpBtcAddress);
2666 byte[] lbcAddressBytes = lbcAddress.getBytes();
2667 byte[] result = new byte[fastBridgeDerivationHashData.length +
2668 userRefundAddressBytes.length + lpBtcAddressBytes.length + lbcAddressBytes.length];
2669
2670 int dstPosition = 0;
2671
2672 System.arraycopy(
2673 fastBridgeDerivationHashData,
2674 0,
2675 result,
2676 dstPosition,
2677 fastBridgeDerivationHashData.length
2678 );
2679
2680 dstPosition += fastBridgeDerivationHashData.length;
2681
2682 System.arraycopy(
2683 userRefundAddressBytes,
2684 0,
2685 result,
2686 dstPosition,
2687 userRefundAddressBytes.length
2688 );
2689
2690 dstPosition += userRefundAddressBytes.length;
2691
2692 System.arraycopy(
2693 lbcAddressBytes,
2694 0,
2695 result,
2696 dstPosition,
2697 lbcAddressBytes.length
2698 );
2699
2700 dstPosition += lbcAddressBytes.length;
2701
2702 System.arraycopy(
2703 lpBtcAddressBytes,
2704 0,
2705 result,
2706 dstPosition,
2707 lpBtcAddressBytes.length
2708 );
2709 return new Keccak256(HashUtil.keccak256(result));
2710 }
2711
2712 protected byte[] getBytesFromBtcAddress(Address btcAddress) {
2713 byte[] hash160 = btcAddress.getHash160();
2714 byte[] version = BigInteger.valueOf(btcAddress.getVersion()).toByteArray();
2715 byte[] btcAddressBytes = new byte[hash160.length + version.length];
2716 System.arraycopy(version, 0, btcAddressBytes, 0, version.length);
2717 System.arraycopy(hash160, 0, btcAddressBytes, version.length, hash160.length);
2718
2719 return btcAddressBytes;
2720 }
2721
2722 protected Coin getAmountSentToAddress(BtcTransaction btcTx, Address btcAddress) {
2723 Coin v = Coin.ZERO;
2724 for (TransactionOutput o : btcTx.getOutputs()) {
2725 if (o.getScriptPubKey().getToAddress(bridgeConstants.getBtcParams()).equals(btcAddress)) {
2726 v = v.add(o.getValue());
2727 }
2728 }
2729 return v;
2730 }
2731
2732 // This method will be used by registerBtcTransfer to save all the data required on storage (utxos, btcTxHash-derivationHash),
2733 // and will look like.
2734 protected void saveFastBridgeDataInStorage(
2735 Sha256Hash btcTxHash,
2736 Keccak256 derivationHash,
2737 FastBridgeFederationInformation fastBridgeFederationInformation,
2738 List<UTXO> utxosList) throws IOException {
2739 provider.markFastBridgeFederationDerivationHashAsUsed(btcTxHash, derivationHash);
2740 provider.setFastBridgeFederationInformation(fastBridgeFederationInformation);
2741 for (UTXO utxo : utxosList) {
2742 getActiveFederationBtcUTXOs().add(utxo);
2743 }
2744 }
2745
2746 private StoredBlock getBtcBlockchainChainHead() throws IOException, BlockStoreException {
2747 // Gather the current btc chain's head
2748 // IMPORTANT: we assume that getting the chain head from the btc blockstore
2749 // is enough since we're not manipulating the blockchain here, just querying it.
2750 this.ensureBtcBlockStore();
2751 return btcBlockStore.getChainHead();
2752 }
2753
2754 /**
2755 * Returns the first bitcoin block we have. It is either a checkpoint or the genesis
2756 */
2757 private StoredBlock getLowestBlock() throws IOException {
2758 InputStream checkpoints = this.getCheckPoints();
2759 if (checkpoints == null) {
2760 BtcBlock genesis = bridgeConstants.getBtcParams().getGenesisBlock();
2761 return new StoredBlock(genesis, genesis.getWork(), 0);
2762 }
2763 CheckpointManager manager = new CheckpointManager(bridgeConstants.getBtcParams(), checkpoints);
2764 long time = getActiveFederation().getCreationTime().toEpochMilli();
2765 // Go back 1 week to match CheckpointManager.checkpoint() behaviour
2766 time -= 86400 * 7;
2767 return manager.getCheckpointBefore(time);
2768 }
2769
2770 private Pair<BtcTransaction, List<UTXO>> createMigrationTransaction(Wallet originWallet, Address destinationAddress) {
2771 Coin expectedMigrationValue = originWallet.getBalance();
2772 logger.debug("[createMigrationTransaction] Balance to migrate: {}", expectedMigrationValue);
2773 for(;;) {
2774 BtcTransaction migrationBtcTx = new BtcTransaction(originWallet.getParams());
2775 migrationBtcTx.addOutput(expectedMigrationValue, destinationAddress);
2776
2777 SendRequest sr = SendRequest.forTx(migrationBtcTx);
2778 sr.changeAddress = destinationAddress;
2779 sr.feePerKb = getFeePerKb();
2780 sr.missingSigsMode = Wallet.MissingSigsMode.USE_OP_ZERO;
2781 sr.recipientsPayFees = true;
2782 try {
2783 originWallet.completeTx(sr);
2784 for (TransactionInput transactionInput : migrationBtcTx.getInputs()) {
2785 transactionInput.disconnect();
2786 }
2787
2788 List<UTXO> selectedUTXOs = originWallet
2789 .getUTXOProvider().getOpenTransactionOutputs(originWallet.getWatchedAddresses()).stream()
2790 .filter(utxo ->
2791 migrationBtcTx.getInputs().stream().anyMatch(input ->
2792 input.getOutpoint().getHash().equals(utxo.getHash()) &&
2793 input.getOutpoint().getIndex() == utxo.getIndex()
2794 )
2795 )
2796 .collect(Collectors.toList());
2797
2798 return Pair.of(migrationBtcTx, selectedUTXOs);
2799 } catch (InsufficientMoneyException | Wallet.ExceededMaxTransactionSize | Wallet.CouldNotAdjustDownwards e) {
2800 expectedMigrationValue = expectedMigrationValue.divide(2);
2801 } catch(Wallet.DustySendRequested e) {
2802 throw new IllegalStateException("Retiring federation wallet cannot be emptied", e);
2803 } catch (UTXOProviderException e) {
2804 throw new RuntimeException("Unexpected UTXO provider error", e);
2805 }
2806 }
2807 }
2808
2809 // Make sure the local bitcoin blockchain is instantiated
2810 private void ensureBtcBlockChain() throws IOException, BlockStoreException {
2811 this.ensureBtcBlockStore();
2812
2813 if (this.btcBlockChain == null) {
2814 this.btcBlockChain = new BtcBlockChain(btcContext, btcBlockStore);
2815 }
2816 }
2817
2818 // Make sure the local bitcoin blockstore is instantiated
2819 private void ensureBtcBlockStore() throws IOException, BlockStoreException {
2820 if(btcBlockStore == null) {
2821 btcBlockStore = btcBlockStoreFactory.newInstance(
2822 rskRepository,
2823 bridgeConstants,
2824 provider,
2825 activations
2826 );
2827 NetworkParameters btcParams = this.bridgeConstants.getBtcParams();
2828
2829 if (this.btcBlockStore.getChainHead().getHeader().getHash().equals(btcParams.getGenesisBlock().getHash())) {
2830 // We are building the blockstore for the first time, so we have not set the checkpoints yet.
2831 long time = federationSupport.getActiveFederation().getCreationTime().toEpochMilli();
2832 InputStream checkpoints = this.getCheckPoints();
2833 if (time > 0 && checkpoints != null) {
2834 CheckpointManager.checkpoint(btcParams, checkpoints, this.btcBlockStore, time);
2835 }
2836 }
2837 }
2838 }
2839
2840 private Address getParsedAddress(String base58Address) throws AddressFormatException {
2841 return Address.fromBase58(btcContext.getParams(), base58Address);
2842 }
2843
2844 private void generateRejectionRelease(
2845 BtcTransaction btcTx,
2846 Address btcRefundAddress,
2847 Address spendingAddress,
2848 Keccak256 rskTxHash,
2849 Coin totalAmount,
2850 WalletProvider walletProvider) throws IOException {
2851
2852 ReleaseTransactionBuilder txBuilder = new ReleaseTransactionBuilder(
2853 btcContext.getParams(),
2854 walletProvider.provide(btcTx, spendingAddress),
2855 btcRefundAddress,
2856 getFeePerKb(),
2857 activations
2858 );
2859
2860 Optional<ReleaseTransactionBuilder.BuildResult> buildReturnResult = txBuilder.buildEmptyWalletTo(btcRefundAddress);
2861 if (buildReturnResult.isPresent()) {
2862 if (activations.isActive(ConsensusRule.RSKIP146)) {
2863 provider.getReleaseTransactionSet().add(buildReturnResult.get().getBtcTx(), rskExecutionBlock.getNumber(), rskTxHash);
2864 eventLogger.logReleaseBtcRequested(rskTxHash.getBytes(), buildReturnResult.get().getBtcTx(), totalAmount);
2865 } else {
2866 provider.getReleaseTransactionSet().add(buildReturnResult.get().getBtcTx(), rskExecutionBlock.getNumber());
2867 }
2868 logger.info("Rejecting peg-in: return tx build successful to {}. Tx {}. Value {}.", btcRefundAddress, rskTxHash, totalAmount);
2869 } else {
2870 logger.warn("Rejecting peg-in: return tx build for btc tx {} error. Return was to {}. Tx {}. Value {}", btcTx.getHash(), btcRefundAddress, rskTxHash, totalAmount);
2871 panicProcessor.panic("peg-in-refund", String.format("peg-in money return tx build for btc tx %s error. Return was to %s. Tx %s. Value %s", btcTx.getHash(), btcRefundAddress, rskTxHash, totalAmount));
2872 }
2873 }
2874
2875 private void generateRejectionRelease(
2876 BtcTransaction btcTx,
2877 Address senderBtcAddress,
2878 Transaction rskTx,
2879 Coin totalAmount
2880 ) throws IOException {
2881 WalletProvider createWallet = (BtcTransaction a, Address b) -> {
2882 // Build the list of UTXOs in the BTC transaction sent to either the active
2883 // or retiring federation
2884 List<UTXO> utxosToUs = btcTx.getWalletOutputs(
2885 getNoSpendWalletForLiveFederations(false)
2886 )
2887 .stream()
2888 .map(output ->
2889 new UTXO(
2890 btcTx.getHash(),
2891 output.getIndex(),
2892 output.getValue(),
2893 0,
2894 btcTx.isCoinBase(),
2895 output.getScriptPubKey()
2896 )
2897 ).collect(Collectors.toList());
2898 // Use the list of UTXOs to build a transaction builder
2899 // for the return btc transaction generation
2900 return getUTXOBasedWalletForLiveFederations(utxosToUs, false);
2901 };
2902
2903 generateRejectionRelease(btcTx, senderBtcAddress, null, rskTx.getHash(), totalAmount, createWallet);
2904 }
2905
2906 private boolean verifyLockSenderIsWhitelisted(Address senderBtcAddress, Coin totalAmount, int height) {
2907 // If the address is not whitelisted, then return the funds
2908 // using the exact same utxos sent to us.
2909 // That is, build a release transaction and get it in the release transaction set.
2910 // Otherwise, transfer SBTC to the sender of the BTC
2911 // The RSK account to update is the one that matches the pubkey "spent" on the first bitcoin tx input
2912 LockWhitelist lockWhitelist = provider.getLockWhitelist();
2913 if (!lockWhitelist.isWhitelistedFor(senderBtcAddress, totalAmount, height)) {
2914 logger.info("Rejecting lock. Address {} is not whitelisted.", senderBtcAddress);
2915 return false;
2916 }
2917
2918 // Consume this whitelisted address
2919 lockWhitelist.consume(senderBtcAddress);
2920
2921 return true;
2922 }
2923
2924 private boolean verifyLockDoesNotSurpassLockingCap(BtcTransaction btcTx, Coin totalAmount) {
2925 if (!activations.isActive(ConsensusRule.RSKIP134)) {
2926 return true;
2927 }
2928
2929 Coin fedCurrentFunds = getBtcLockedInFederation();
2930 Coin lockingCap = this.getLockingCap();
2931 logger.trace("Evaluating locking cap for: TxId {}. Value to lock {}. Current funds {}. Current locking cap {}", btcTx.getHash(true), totalAmount, fedCurrentFunds, lockingCap);
2932 Coin fedUTXOsAfterThisLock = fedCurrentFunds.add(totalAmount);
2933 // If the federation funds (including this new UTXO) are smaller than or equals to the current locking cap, we are fine.
2934 if (fedUTXOsAfterThisLock.compareTo(lockingCap) <= 0) {
2935 return true;
2936 }
2937
2938 logger.info("locking cap exceeded! btc Tx {}", btcTx);
2939 return false;
2940 }
2941
2942 private Coin getBtcLockedInFederation() {
2943 Coin maxRbtc = this.bridgeConstants.getMaxRbtc();
2944 Coin currentBridgeBalance = rskRepository.getBalance(PrecompiledContracts.BRIDGE_ADDR).toBitcoin();
2945
2946 return maxRbtc.subtract(currentBridgeBalance);
2947 }
2948
2949 @VisibleForTesting
2950 protected boolean isBlockMerkleRootValid(Sha256Hash merkleRoot, BtcBlock blockHeader) {
2951 boolean isValid = false;
2952
2953 if (blockHeader.getMerkleRoot().equals(merkleRoot)) {
2954 logger.trace("block merkle root is valid");
2955 isValid = true;
2956 }
2957 else {
2958 if (activations.isActive(ConsensusRule.RSKIP143)) {
2959 CoinbaseInformation coinbaseInformation = provider.getCoinbaseInformation(blockHeader.getHash());
2960 if (coinbaseInformation == null) {
2961 logger.trace("coinbase information for block {} is not yet registered", blockHeader.getHash());
2962 }
2963 isValid = coinbaseInformation != null && coinbaseInformation.getWitnessMerkleRoot().equals(merkleRoot);
2964 logger.trace("witness merkle root is {} valid", (isValid ? "":"NOT"));
2965 } else {
2966 logger.trace("RSKIP143 is not active, avoid checking witness merkle root");
2967 }
2968 }
2969 return isValid;
2970 }
2971
2972 @VisibleForTesting
2973 protected boolean validationsForRegisterBtcTransaction(Sha256Hash btcTxHash, int height, byte[] pmtSerialized, byte[] btcTxSerialized)
2974 throws BlockStoreException, VerificationException.EmptyInputsOrOutputs, BridgeIllegalArgumentException {
2975
2976 // Validates height and confirmations for tx
2977 try {
2978 int acceptableConfirmationsAmount = bridgeConstants.getBtc2RskMinimumAcceptableConfirmations();
2979 if (!BridgeUtils.validateHeightAndConfirmations(height, getBtcBlockchainBestChainHeight(), acceptableConfirmationsAmount, btcTxHash)) {
2980 return false;
2981 }
2982 } catch (Exception e) {
2983 String panicMessage = String.format("Btc Tx %s Supplied Height is %d but should be greater than 0", btcTxHash, height);
2984 logger.warn(panicMessage);
2985 panicProcessor.panic("btclock", panicMessage);
2986 return false;
2987 }
2988
2989 // Validates pmt size
2990 if (!PartialMerkleTreeFormatUtils.hasExpectedSize(pmtSerialized)) {
2991 throw new BridgeIllegalArgumentException("PartialMerkleTree doesn't have expected size");
2992 }
2993
2994 // Calculates merkleRoot
2995 Sha256Hash merkleRoot;
2996 try {
2997 NetworkParameters networkParameters = bridgeConstants.getBtcParams();
2998 merkleRoot = BridgeUtils.calculateMerkleRoot(networkParameters, pmtSerialized, btcTxHash);
2999 if (merkleRoot == null) {
3000 return false;
3001 }
3002 } catch (VerificationException e) {
3003 throw new BridgeIllegalArgumentException(e.getMessage(), e);
3004 }
3005
3006 // Validates inputs count
3007 logger.info("Going to validate inputs for btc tx {}", btcTxHash);
3008 BridgeUtils.validateInputsCount(btcTxSerialized, activations.isActive(ConsensusRule.RSKIP143));
3009
3010 // Check the the merkle root equals merkle root of btc block at specified height in the btc best chain
3011 // BTC blockstore is available since we've already queried the best chain height
3012 logger.trace("Getting btc block at height: {}", height);
3013 BtcBlock blockHeader = btcBlockStore.getStoredBlockAtMainChainHeight(height).getHeader();
3014 logger.trace("Validating block merkle root at height: {}", height);
3015 if (!isBlockMerkleRootValid(merkleRoot, blockHeader)){
3016 String panicMessage = String.format(
3017 "Btc Tx %s Supplied merkle root %s does not match block's merkle root %s",
3018 btcTxHash.toString(),
3019 merkleRoot,
3020 blockHeader.getMerkleRoot()
3021 );
3022 logger.warn(panicMessage);
3023 panicProcessor.panic("btclock", panicMessage);
3024 return false;
3025 }
3026
3027 return true;
3028 }
3029
3030 private Coin computeTotalAmountSent(BtcTransaction btcTx) throws IOException {
3031 // Compute the total amount sent. Value could have been sent both to the
3032 // currently active federation as well as to the currently retiring federation.
3033 // Add both amounts up in that case.
3034 Coin amountToActive = btcTx.getValueSentToMe(getActiveFederationWallet(false));
3035 Coin amountToRetiring = Coin.ZERO;
3036 Wallet retiringFederationWallet = getRetiringFederationWallet(false);
3037 if (retiringFederationWallet != null) {
3038 amountToRetiring = btcTx.getValueSentToMe(retiringFederationWallet);
3039 }
3040 return amountToActive.add(amountToRetiring);
3041 }
3042
3043 private static byte[] serializeBlockHeader(StoredBlock block) {
3044 if (block == null) {
3045 return ByteUtil.EMPTY_BYTE_ARRAY;
3046 }
3047
3048 byte[] bytes = block.getHeader().unsafeBitcoinSerialize();
3049
3050 byte[] header = new byte[80];
3051
3052 System.arraycopy(bytes, 0, header, 0, 80);
3053
3054 return header;
3055 }
3056 }