Repository navigation
Merge 9.0.3.0 changes into master - #620
Merged
Merged
Conversation
Prepare 9.0.3.0 release
…lready part of bridge best chain
…is not part of best chain any more
…d-blocks Fix/inform coinbases of informed blocks
Obtain rskTxHash from releaseCreationInformation instead of setting the tx in the tx receipt Refactor ReleaseCreationInformation and getter to obtain outpoint values from logs Add missing outpoint values when creating release creation information in tests setup Keep releaseCreationInformation altogether in PowHSMSignerMessageBuilder Fix tests in PowHSMSigningClientBtcTest Remove signer version check from getTxInfoToSign Remove unnecessary isNull check. Reorder logs to be more accurate
Improve release event log checks
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR merges the 9.0.3.0 line into master, updating the powpeg-node codebase and tests to align with updated pegout-signing and BTC coinbase informing flows, alongside a dependency/version alignment to rskj-core 9.0.3-VETIVER.
Changes:
- Refactors pegout release creation lookup (
ReleaseCreationInformationGetter/ReleaseCreationInformation) to validate Bridge logs more strictly and to carry decoded UTXO outpoint values explicitly. - Reworks BTC coinbase informing in
BtcToRskClientto send coinbase transactions based on Bridge state (block header informed / coinbase already informed / main-chain membership) rather than a “ready-to-inform” flag. - Updates tests and test utilities to match the new APIs/data shapes and adds additional coverage for restart/reorg scenarios.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/java/co/rsk/federate/utils/ClientProofsAssertions.java | Adds assertions for presence/absence of block hashes in the persisted coinbase map. |
| src/test/java/co/rsk/federate/signing/utils/TestUtils.java | Adds receipt/log/txinfo builders and updates block creation helper to include activation mocking. |
| src/test/java/co/rsk/federate/signing/hsm/message/SignerMessageBuilderFactoryTest.java | Updates ReleaseCreationInformation construction to include outpoint values list. |
| src/test/java/co/rsk/federate/signing/hsm/message/ReleaseCreationInformationGetterTest.java | Reworks tests around the new log-validation and receipt/blockchain setup helpers. |
| src/test/java/co/rsk/federate/signing/hsm/message/PowHSMSignerMessageBuilderTest.java | Updates tests for ReleaseCreationInformation’s new outpoint-values parameter. |
| src/test/java/co/rsk/federate/signing/hsm/client/PowHSMSigningClientBtcTest.java | Updates signing client tests to pass explicit outpoint values into release information. |
| src/test/java/co/rsk/federate/FederatorSupportTest.java | Adds tests for isBlockHashInformedToBridge behavior on empty/non-empty Bridge results. |
| src/test/java/co/rsk/federate/EventsTestUtils.java | Adds helpers to build encoded topics/data and generic log builders for Bridge events. |
| src/test/java/co/rsk/federate/CoinbaseInformationTest.java | Removes tests for the removed readyToInform flag. |
| src/test/java/co/rsk/federate/BtcToRskClientTest.java | Refactors storage setup and adds new coinbase-informing tests including restart/reorg scenarios. |
| src/test/java/co/rsk/federate/btcreleaseclient/BtcReleaseClientTest.java | Refactors and expands release-processing tests using new event/log utilities and receipt helpers. |
| src/test/java/co/rsk/federate/bitcoin/KitStub.java | Initializes vChain when injecting a mocked BlockStore. |
| src/main/resources/version.properties | Updates runtime version metadata to 9.0.3.0 / VETIVER. |
| src/main/java/co/rsk/federate/signing/hsm/message/ReleaseCreationInformationGetter.java | Replaces version-dependent logic with strict Bridge-log validation and outpoint-values extraction. |
| src/main/java/co/rsk/federate/signing/hsm/message/ReleaseCreationInformation.java | Makes outpoint-values an explicit constructor parameter and stores it immutably. |
| src/main/java/co/rsk/federate/signing/hsm/message/PowHSMSignerMessageBuilder.java | Uses ReleaseCreationInformation as the single source of receipt/block/outpoint data. |
| src/main/java/co/rsk/federate/FederatorSupport.java | Adds isBlockHashInformedToBridge helper calling Bridge GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH. |
| src/main/java/co/rsk/federate/CoinbaseInformation.java | Removes readyToInform flag and related accessors. |
| src/main/java/co/rsk/federate/BtcToRskClient.java | Removes “mark ready” flow and reworks coinbase informing to be state-based and reorg-aware. |
| src/main/java/co/rsk/federate/btcreleaseclient/BtcReleaseClient.java | Updates to new getter signature and adjusts error handling/logging around release retrieval. |
| gradle/verification-metadata.xml | Updates verification metadata for co.rsk:rskj-core:9.0.3-VETIVER. |
| build.gradle | Pins rskj-core dependency to 9.0.3-VETIVER. |
| } | ||
| TransactionReceipt transactionReceipt = transactionInfo.getReceipt(); | ||
| Block block = blockStore.getBlockByHash(transactionInfo.getBlockHash()); | ||
| List<LogInfo> logs = pegoutCreationRskTxReceipt.getLogInfoList(); |
Comment on lines
+142
to
+149
| public boolean isBlockHashInformedToBridge(Sha256Hash blockHash) { | ||
| byte[] blockHeaderBytes = this.bridgeTransactionSender.callTx( | ||
| federatorAddress, | ||
| Bridge.GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH, | ||
| new Object[]{ blockHash.getBytes() } | ||
| ); | ||
| return blockHeaderBytes.length > 0; | ||
| } |
Comment on lines
+350
to
354
| } catch (FederatorAlreadySignedException | FederationCantSignException | HSMReleaseCreationInformationException e) { | ||
| String message = String.format( | ||
| "[tryGetReleaseInformation] There was an error trying to process pegout with btcTxHash %s", | ||
| pegoutToSign.getHash() | ||
| pegoutBtcTxHash | ||
| ); |
Comment on lines
+1
to
+2
| versionNumber='9.0.3.0' | ||
| modifier="VETIVER" |
Comment on lines
+142
to
+149
| public boolean isBlockHashInformedToBridge(Sha256Hash blockHash) { | ||
| byte[] blockHeaderBytes = this.bridgeTransactionSender.callTx( | ||
| federatorAddress, | ||
| Bridge.GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH, | ||
| new Object[]{ blockHash.getBytes() } | ||
| ); | ||
| return blockHeaderBytes.length > 0; | ||
| } |
Comment on lines
+59
to
65
| TransactionReceipt pegoutCreationRskTxReceipt = pegoutCreationRskTxInfo.getReceipt(); | ||
| Block pegoutCreationRskBlock = blockStore.getBlockByHash(pegoutCreationRskTxInfo.getBlockHash()); | ||
|
|
||
| protected ReleaseCreationInformation getBaseReleaseCreationInformation( | ||
| Keccak256 pegoutCreationRskTxHash, | ||
| BtcTransaction pegoutBtcTx | ||
| ) throws HSMReleaseCreationInformationException { | ||
| TransactionInfo transactionInfo = receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore).orElse(null); | ||
| if (transactionInfo == null) { | ||
| String message = String.format( | ||
| "Rsk transaction %s where the pegout was created could not be found in best chain", | ||
| pegoutCreationRskTxHash | ||
| ); | ||
| logger.error("[getTxInfoToSign] {}", message); | ||
| throw new HSMReleaseCreationInformationException(message); | ||
| } | ||
| TransactionReceipt transactionReceipt = transactionInfo.getReceipt(); | ||
| Block block = blockStore.getBlockByHash(transactionInfo.getBlockHash()); | ||
| List<LogInfo> logs = pegoutCreationRskTxReceipt.getLogInfoList(); | ||
| ReleaseCreationLogs releaseCreationLogs = getReleaseCreationLogs(pegoutCreationRskTxHash, pegoutBtcTx, logs); | ||
| List<Coin> utxoOutpointsValues = getUtxoOutpointsValues(releaseCreationLogs.pegoutTransactionCreatedEvent()); | ||
|
|
Comment on lines
+111
to
+116
| private List<Coin> getUtxoOutpointsValues(LogInfo logInfo) { | ||
| CallTransaction.Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| byte[] pegoutTransactionCreatedEventData = logInfo.getData(); | ||
| byte[] utxoOutpointsValuesEncoded = (byte[]) pegoutTransactionCreatedEvent.decodeEventData(pegoutTransactionCreatedEventData)[0]; | ||
| return UtxoUtils.decodeOutpointValues(utxoOutpointsValuesEncoded); | ||
| } |
Comment on lines
+139
to
142
| private boolean isLogFromBridge(LogInfo logInfo) { | ||
| RskAddress logsEmittedFrom = new RskAddress(logInfo.getAddress()); | ||
| return logsEmittedFrom.equals(PrecompiledContracts.BRIDGE_ADDR); | ||
| } |
Comment on lines
32
to
37
| this.pegoutCreationBlock = pegoutCreationBlock; | ||
| this.transactionReceipt = transactionReceipt; | ||
| this.pegoutCreationRskTxHash = pegoutCreationRskTxHash; | ||
| this.pegoutBtcTx = pegoutBtcTx; | ||
|
|
||
| this.utxoOutpointValues = decodeUtxoOutpointValues(); | ||
| } | ||
|
|
||
| private List<Coin> decodeUtxoOutpointValues() { | ||
| return transactionReceipt.getLogInfoList().stream() | ||
| .filter(this::isPegoutTransactionCreatedLog) | ||
| .findFirst() | ||
| .map(LogInfo::getData) | ||
| .map(this::decodePegoutTransactionEventData) | ||
| .map(UtxoUtils::decodeOutpointValues) | ||
| .orElse(Collections.emptyList()); | ||
| } | ||
|
|
||
| private boolean isPegoutTransactionCreatedLog(LogInfo log) { | ||
| Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| final byte[] pegoutTransactionCreatedSignatureTopic = pegoutTransactionCreatedEvent.encodeSignatureLong(); | ||
|
|
||
| boolean logHasTopics = !log.getTopics().isEmpty(); | ||
| return logHasTopics && | ||
| // event signature topic is always the first one | ||
| Arrays.equals(log.getTopics().get(0).getData(), pegoutTransactionCreatedSignatureTopic); | ||
| } | ||
|
|
||
| private byte[] decodePegoutTransactionEventData(byte[] pegoutCreatedTransactionEventData) { | ||
| Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| return (byte[]) pegoutTransactionCreatedEvent.decodeEventData( | ||
| pegoutCreatedTransactionEventData)[0]; | ||
| this.utxoOutpointValues = Collections.unmodifiableList(utxoOutpointsValues); | ||
| } |
Comment on lines
+381
to
387
| } catch (FederatorAlreadySignedException | FederationCantSignException | HSMReleaseCreationInformationException e) { | ||
| String message = String.format( | ||
| "[tryGetReleaseInformation] There was an error trying to process pegout with btcTxHash %s", | ||
| pegoutToSign.getHash() | ||
| pegoutBtcTxHash | ||
| ); | ||
| logger.error(message, e); | ||
| } catch (FederatorAlreadySignedException e) { | ||
| logger.info("[tryGetReleaseInformation] {}", e.getMessage()); | ||
| } |
Comment on lines
+59
to
+64
| TransactionReceipt pegoutCreationRskTxReceipt = pegoutCreationRskTxInfo.getReceipt(); | ||
| Block pegoutCreationRskBlock = blockStore.getBlockByHash(pegoutCreationRskTxInfo.getBlockHash()); | ||
|
|
||
| protected ReleaseCreationInformation getBaseReleaseCreationInformation( | ||
| Keccak256 pegoutCreationRskTxHash, | ||
| BtcTransaction pegoutBtcTx | ||
| ) throws HSMReleaseCreationInformationException { | ||
| TransactionInfo transactionInfo = receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore).orElse(null); | ||
| if (transactionInfo == null) { | ||
| String message = String.format( | ||
| "Rsk transaction %s where the pegout was created could not be found in best chain", | ||
| pegoutCreationRskTxHash | ||
| ); | ||
| logger.error("[getTxInfoToSign] {}", message); | ||
| throw new HSMReleaseCreationInformationException(message); | ||
| } | ||
| TransactionReceipt transactionReceipt = transactionInfo.getReceipt(); | ||
| Block block = blockStore.getBlockByHash(transactionInfo.getBlockHash()); | ||
| List<LogInfo> logs = pegoutCreationRskTxReceipt.getLogInfoList(); | ||
| ReleaseCreationLogs releaseCreationLogs = getReleaseCreationLogs(pegoutCreationRskTxHash, pegoutBtcTx, logs); | ||
| List<Coin> utxoOutpointsValues = getUtxoOutpointsValues(releaseCreationLogs.pegoutTransactionCreatedEvent()); |
| public List<Coin> getUtxoOutpointValues() { | ||
| return Collections.unmodifiableList(utxoOutpointValues); | ||
| } | ||
| public List<Coin> getUtxoOutpointValues() { return utxoOutpointValues; } |
Comment on lines
+142
to
+149
| public boolean isBlockHashInformedToBridge(Sha256Hash blockHash) { | ||
| byte[] blockHeaderBytes = this.bridgeTransactionSender.callTx( | ||
| federatorAddress, | ||
| Bridge.GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH, | ||
| new Object[]{ blockHash.getBytes() } | ||
| ); | ||
| return blockHeaderBytes.length > 0; | ||
| } |
Comment on lines
+59
to
+64
| TransactionReceipt pegoutCreationRskTxReceipt = pegoutCreationRskTxInfo.getReceipt(); | ||
| Block pegoutCreationRskBlock = blockStore.getBlockByHash(pegoutCreationRskTxInfo.getBlockHash()); | ||
|
|
||
| protected ReleaseCreationInformation getBaseReleaseCreationInformation( | ||
| Keccak256 pegoutCreationRskTxHash, | ||
| BtcTransaction pegoutBtcTx | ||
| ) throws HSMReleaseCreationInformationException { | ||
| TransactionInfo transactionInfo = receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore).orElse(null); | ||
| if (transactionInfo == null) { | ||
| String message = String.format( | ||
| "Rsk transaction %s where the pegout was created could not be found in best chain", | ||
| pegoutCreationRskTxHash | ||
| ); | ||
| logger.error("[getTxInfoToSign] {}", message); | ||
| throw new HSMReleaseCreationInformationException(message); | ||
| } | ||
| TransactionReceipt transactionReceipt = transactionInfo.getReceipt(); | ||
| Block block = blockStore.getBlockByHash(transactionInfo.getBlockHash()); | ||
| List<LogInfo> logs = pegoutCreationRskTxReceipt.getLogInfoList(); | ||
| ReleaseCreationLogs releaseCreationLogs = getReleaseCreationLogs(pegoutCreationRskTxHash, pegoutBtcTx, logs); | ||
| List<Coin> utxoOutpointsValues = getUtxoOutpointsValues(releaseCreationLogs.pegoutTransactionCreatedEvent()); |
Comment on lines
33
to
37
| this.transactionReceipt = transactionReceipt; | ||
| this.pegoutCreationRskTxHash = pegoutCreationRskTxHash; | ||
| this.pegoutBtcTx = pegoutBtcTx; | ||
|
|
||
| this.utxoOutpointValues = decodeUtxoOutpointValues(); | ||
| } | ||
|
|
||
| private List<Coin> decodeUtxoOutpointValues() { | ||
| return transactionReceipt.getLogInfoList().stream() | ||
| .filter(this::isPegoutTransactionCreatedLog) | ||
| .findFirst() | ||
| .map(LogInfo::getData) | ||
| .map(this::decodePegoutTransactionEventData) | ||
| .map(UtxoUtils::decodeOutpointValues) | ||
| .orElse(Collections.emptyList()); | ||
| } | ||
|
|
||
| private boolean isPegoutTransactionCreatedLog(LogInfo log) { | ||
| Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| final byte[] pegoutTransactionCreatedSignatureTopic = pegoutTransactionCreatedEvent.encodeSignatureLong(); | ||
|
|
||
| boolean logHasTopics = !log.getTopics().isEmpty(); | ||
| return logHasTopics && | ||
| // event signature topic is always the first one | ||
| Arrays.equals(log.getTopics().get(0).getData(), pegoutTransactionCreatedSignatureTopic); | ||
| } | ||
|
|
||
| private byte[] decodePegoutTransactionEventData(byte[] pegoutCreatedTransactionEventData) { | ||
| Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| return (byte[]) pegoutTransactionCreatedEvent.decodeEventData( | ||
| pegoutCreatedTransactionEventData)[0]; | ||
| this.utxoOutpointValues = Collections.unmodifiableList(utxoOutpointsValues); | ||
| } |
Comment on lines
+142
to
+148
| public boolean isBlockHashInformedToBridge(Sha256Hash blockHash) { | ||
| byte[] blockHeaderBytes = this.bridgeTransactionSender.callTx( | ||
| federatorAddress, | ||
| Bridge.GET_BTC_BLOCKCHAIN_BLOCK_HEADER_BY_HASH, | ||
| new Object[]{ blockHash.getBytes() } | ||
| ); | ||
| return blockHeaderBytes.length > 0; |
Comment on lines
+59
to
+63
| TransactionReceipt pegoutCreationRskTxReceipt = pegoutCreationRskTxInfo.getReceipt(); | ||
| Block pegoutCreationRskBlock = blockStore.getBlockByHash(pegoutCreationRskTxInfo.getBlockHash()); | ||
|
|
||
| protected ReleaseCreationInformation getBaseReleaseCreationInformation( | ||
| Keccak256 pegoutCreationRskTxHash, | ||
| BtcTransaction pegoutBtcTx | ||
| ) throws HSMReleaseCreationInformationException { | ||
| TransactionInfo transactionInfo = receiptStore.getInMainChain(pegoutCreationRskTxHash.getBytes(), blockStore).orElse(null); | ||
| if (transactionInfo == null) { | ||
| String message = String.format( | ||
| "Rsk transaction %s where the pegout was created could not be found in best chain", | ||
| pegoutCreationRskTxHash | ||
| ); | ||
| logger.error("[getTxInfoToSign] {}", message); | ||
| throw new HSMReleaseCreationInformationException(message); | ||
| } | ||
| TransactionReceipt transactionReceipt = transactionInfo.getReceipt(); | ||
| Block block = blockStore.getBlockByHash(transactionInfo.getBlockHash()); | ||
| List<LogInfo> logs = pegoutCreationRskTxReceipt.getLogInfoList(); | ||
| ReleaseCreationLogs releaseCreationLogs = getReleaseCreationLogs(pegoutCreationRskTxHash, pegoutBtcTx, logs); |
Comment on lines
+111
to
+116
| private List<Coin> getUtxoOutpointsValues(LogInfo logInfo) { | ||
| CallTransaction.Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| byte[] pegoutTransactionCreatedEventData = logInfo.getData(); | ||
| byte[] utxoOutpointsValuesEncoded = (byte[]) pegoutTransactionCreatedEvent.decodeEventData(pegoutTransactionCreatedEventData)[0]; | ||
| return UtxoUtils.decodeOutpointValues(utxoOutpointsValuesEncoded); | ||
| } |
Comment on lines
33
to
37
| this.transactionReceipt = transactionReceipt; | ||
| this.pegoutCreationRskTxHash = pegoutCreationRskTxHash; | ||
| this.pegoutBtcTx = pegoutBtcTx; | ||
|
|
||
| this.utxoOutpointValues = decodeUtxoOutpointValues(); | ||
| } | ||
|
|
||
| private List<Coin> decodeUtxoOutpointValues() { | ||
| return transactionReceipt.getLogInfoList().stream() | ||
| .filter(this::isPegoutTransactionCreatedLog) | ||
| .findFirst() | ||
| .map(LogInfo::getData) | ||
| .map(this::decodePegoutTransactionEventData) | ||
| .map(UtxoUtils::decodeOutpointValues) | ||
| .orElse(Collections.emptyList()); | ||
| } | ||
|
|
||
| private boolean isPegoutTransactionCreatedLog(LogInfo log) { | ||
| Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| final byte[] pegoutTransactionCreatedSignatureTopic = pegoutTransactionCreatedEvent.encodeSignatureLong(); | ||
|
|
||
| boolean logHasTopics = !log.getTopics().isEmpty(); | ||
| return logHasTopics && | ||
| // event signature topic is always the first one | ||
| Arrays.equals(log.getTopics().get(0).getData(), pegoutTransactionCreatedSignatureTopic); | ||
| } | ||
|
|
||
| private byte[] decodePegoutTransactionEventData(byte[] pegoutCreatedTransactionEventData) { | ||
| Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); | ||
| return (byte[]) pegoutTransactionCreatedEvent.decodeEventData( | ||
| pegoutCreatedTransactionEventData)[0]; | ||
| this.utxoOutpointValues = Collections.unmodifiableList(utxoOutpointsValues); | ||
| } |
|
julia-zack
approved these changes
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



rit:fix/inform-coinbases-of-informed-blocks