-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Admin CLI - Manual Rollback command (#436)
* feat: add rollback command in Admin CLI * chore: update rollback yaml file * chore: update rollback yml file
- Loading branch information
1 parent
d6e87df
commit 319bbdd
Showing
6 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
17 changes: 17 additions & 0 deletions
17
...tils/src/main/java/com/bloxbean/cardano/yaci/store/dbutils/index/model/RollbackBlock.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.bloxbean.cardano.yaci.store.dbutils.index.model; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
|
||
@Data | ||
@Builder | ||
@AllArgsConstructor | ||
public class RollbackBlock { | ||
private String hash; | ||
private Long number; | ||
private Long slot; | ||
private Integer epoch; | ||
private Integer epochSlot; | ||
private Integer era; | ||
} |
11 changes: 11 additions & 0 deletions
11
...rc/main/java/com/bloxbean/cardano/yaci/store/dbutils/index/model/TableRollbackAction.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.bloxbean.cardano.yaci.store.dbutils.index.model; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@AllArgsConstructor | ||
public class TableRollbackAction { | ||
private String tableName; | ||
private String sql; | ||
} |
166 changes: 166 additions & 0 deletions
166
.../src/main/java/com/bloxbean/cardano/yaci/store/dbutils/index/service/RollbackService.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package com.bloxbean.cardano.yaci.store.dbutils.index.service; | ||
|
||
import com.bloxbean.cardano.yaci.store.dbutils.index.model.*; | ||
import com.bloxbean.cardano.yaci.store.dbutils.index.util.DatabaseUtils; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.data.util.Pair; | ||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; | ||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class RollbackService { | ||
private final NamedParameterJdbcTemplate jdbcTemplate; | ||
private final DatabaseUtils databaseUtils; | ||
|
||
@Transactional | ||
public Pair<List<TableRollbackAction>, Boolean> executeRollback(List<String> tableNames, int epoch, long eventPublisherId) { | ||
RollbackBlock rollbackBlock = getRollbackBlockByEpoch(epoch); | ||
|
||
if (rollbackBlock == null) { | ||
log.error("Failed to get rollback block for epoch: {}", epoch); | ||
return Pair.of(new ArrayList<>(), false); | ||
} | ||
|
||
rollbackCursor(rollbackBlock, eventPublisherId); | ||
rollbackAccountConfig(rollbackBlock); | ||
|
||
var params = new MapSqlParameterSource(); | ||
params.addValue("epoch", rollbackBlock.getEpoch()); | ||
params.addValue("slot", rollbackBlock.getSlot()); | ||
|
||
List<TableRollbackAction> failedRollbackActions = new ArrayList<>(); | ||
|
||
// Execute DELETE statements for each table/condition | ||
for (String tableName : tableNames) { | ||
if (databaseUtils.tableExists(tableName)) { | ||
String sql = buildDeleteSql(tableName, rollbackBlock.getEpoch(), rollbackBlock.getSlot()); | ||
log.info("Executing rollback on table '{}': {}", tableName, sql); | ||
try { | ||
jdbcTemplate.update(sql, params); | ||
} catch (Exception e) { | ||
log.error("Failed to execute rollback on table '{}': {}", tableName, e.getMessage()); | ||
failedRollbackActions.add(new TableRollbackAction(tableName, sql)); | ||
} | ||
} | ||
} | ||
|
||
boolean rollbackSuccess = failedRollbackActions.isEmpty(); | ||
|
||
return Pair.of(failedRollbackActions, rollbackSuccess); | ||
} | ||
|
||
private RollbackBlock getRollbackBlockByEpoch(int epoch) { | ||
// TODO: Handling for cases where there is no 'block' table | ||
String sql = "SELECT hash, slot, number, epoch_slot, era FROM block WHERE epoch = :epoch ORDER BY slot DESC LIMIT 1"; | ||
|
||
var params = new MapSqlParameterSource() | ||
.addValue("epoch", epoch - 1); | ||
|
||
return jdbcTemplate.queryForObject(sql, params, (rs, rowNum) -> | ||
RollbackBlock.builder() | ||
.hash(rs.getString("hash")) | ||
.slot(rs.getLong("slot")) | ||
.epoch(epoch) | ||
.epochSlot(rs.getInt("epoch_slot")) | ||
.number(rs.getLong("number")) | ||
.era(Integer.valueOf(rs.getString("era"))) | ||
.build() | ||
|
||
); | ||
} | ||
|
||
private Integer getMaxEpoch() { | ||
String sql = "SELECT MAX(epoch) FROM block"; | ||
return jdbcTemplate.getJdbcTemplate().queryForObject(sql, Integer.class); | ||
} | ||
|
||
public Pair<List<String>, List<String>> verifyRollbackActions(List<String> tableNames) { | ||
List<String> tableExists = new ArrayList<>(); | ||
List<String> tableNotExists = new ArrayList<>(); | ||
|
||
for (String tableName: tableNames) { | ||
if (databaseUtils.tableExists(tableName)) { | ||
tableExists.add(tableName); | ||
} else { | ||
tableNotExists.add(tableName); | ||
} | ||
} | ||
|
||
return Pair.of(tableExists, tableNotExists); | ||
} | ||
|
||
public boolean isValidRollbackEpoch(int epoch) { | ||
Integer maxEpoch = getMaxEpoch(); | ||
|
||
if (maxEpoch == null) { | ||
log.error("Failed to get max epoch from block table"); | ||
return false; | ||
} | ||
|
||
return epoch >= 1 && epoch <= maxEpoch; | ||
} | ||
|
||
private String buildDeleteSql(String table, int epoch, long slot) { | ||
String deleteFilter = buildDeleteFilter(table, epoch, slot); | ||
return "DELETE FROM " + table + " WHERE " + deleteFilter; | ||
} | ||
|
||
private String buildDeleteFilter(String tableName, int epoch, long slot) { | ||
if (tableName.equals("epoch_stake") || tableName.equals("drep_dist") || tableName.equals("gov_action_proposal_status")) { | ||
return "epoch >= " + epoch; | ||
} else if (tableName.equals("tx_input")) { | ||
return "spent_at_slot > " + slot; | ||
} | ||
else { | ||
return "slot > " + slot; | ||
} | ||
} | ||
|
||
private void rollbackCursor(RollbackBlock rollbackBlock, long eventPublisherId) { | ||
String truncateCursor = "TRUNCATE TABLE cursor_"; | ||
log.info("Truncating cursor_ table: {}", truncateCursor); | ||
jdbcTemplate.getJdbcTemplate().update(truncateCursor); | ||
|
||
String insertCursor = | ||
"INSERT INTO cursor_ (id, block_hash, slot, block_number, era) " + | ||
"VALUES (:id, :block_hash, :slot, :block_number, :era)"; | ||
log.info("Inserting into cursor_: {}", insertCursor); | ||
|
||
var params = new MapSqlParameterSource(); | ||
params.addValue("id", eventPublisherId); | ||
params.addValue("slot", rollbackBlock.getSlot()); | ||
params.addValue("block_number", rollbackBlock.getNumber()); | ||
params.addValue("block_hash", rollbackBlock.getHash()); | ||
params.addValue("era", rollbackBlock.getEra()); | ||
|
||
jdbcTemplate.update(insertCursor, params); | ||
} | ||
|
||
private void rollbackAccountConfig(RollbackBlock rollbackBlock) { | ||
if (databaseUtils.tableExists("account_config")) { | ||
|
||
String truncateAccCfg = "TRUNCATE TABLE account_config"; | ||
log.info("Truncating account_config: {}", truncateAccCfg); | ||
|
||
jdbcTemplate.getJdbcTemplate().update(truncateAccCfg); | ||
|
||
String insertAccCfg = | ||
"INSERT INTO account_config (config_id, status, slot, block, block_hash) " + | ||
"VALUES ('last_account_balance_processed_block', null, :slot, :block_number, :block_hash)"; | ||
log.info("Inserting into account_config: {}", insertAccCfg); | ||
|
||
jdbcTemplate.update(insertAccCfg, new MapSqlParameterSource() | ||
.addValue("slot", rollbackBlock.getSlot()) | ||
.addValue("block_number", rollbackBlock.getNumber()) | ||
.addValue("block_hash", rollbackBlock.getHash())); | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...tils/src/main/java/com/bloxbean/cardano/yaci/store/dbutils/index/util/RollbackLoader.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.bloxbean.cardano.yaci.store.dbutils.index.util; | ||
|
||
import org.yaml.snakeyaml.Yaml; | ||
|
||
import java.io.InputStream; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class RollbackLoader { | ||
|
||
@SuppressWarnings("unchecked") | ||
public List<String> loadRollbackTableNames(String yamlFilePath) { | ||
Yaml yaml = new Yaml(); | ||
|
||
InputStream is = getClass().getClassLoader().getResourceAsStream(yamlFilePath); | ||
if (is == null) { | ||
throw new IllegalArgumentException("File not found: " + yamlFilePath); | ||
} | ||
|
||
Map<String, Object> root = yaml.load(is); | ||
List<String> tables = new ArrayList<>(); | ||
|
||
Object tablesObj = root.get("tables"); | ||
if (tablesObj instanceof List) { | ||
for (Object t : (List<Object>) tablesObj) { | ||
if (t instanceof String) { | ||
String table = ((String) t).trim(); | ||
if (!table.isEmpty()) { | ||
tables.add(table); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return tables; | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
tables: | ||
# asset store | ||
- asset | ||
# block store | ||
- block | ||
# epoch store | ||
- protocol_params_proposal | ||
- epoch_param | ||
- cost_model | ||
# governance store | ||
- gov_action_proposal | ||
- delegation_vote | ||
- drep_registration | ||
- committee | ||
- committee_deregistration | ||
- committee_member | ||
- committee_registration | ||
- constitution | ||
- voting_procedure | ||
# metadata store | ||
- transaction_metadata | ||
# mir store | ||
- mir | ||
# script store | ||
- script | ||
- transaction_scripts | ||
# staking store | ||
- stake_registration | ||
- delegation | ||
- pool_registration | ||
- pool_retirement | ||
- pool | ||
# transaction store | ||
- transaction | ||
- transaction_witness | ||
- withdrawal | ||
- invalid_transaction | ||
# utxo store | ||
- address_utxo | ||
- tx_input | ||
# other tables from account, adapot, governance-aggr module | ||
- adapot | ||
- address_balance | ||
- address_tx_amount | ||
- cost_model | ||
- instant_reward | ||
- reward | ||
- reward_rest | ||
- stake_address_balance | ||
- adapot_jobs | ||
- drep | ||
- epoch_stake | ||
- drep_dist | ||
- gov_action_proposal_status |