/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt; import nxt.AccountLedger.LedgerEvent; import nxt.db.DbClause; import nxt.db.DbIterator; import nxt.db.DbKey; import nxt.db.VersionedEntityDbTable; import nxt.util.Listener; import nxt.util.Listeners; import nxt.util.Logger; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * Manages currency proof of work minting */ public final class CurrencyMint { public enum Event { CURRENCY_MINT } public static class Mint { public final long accountId; public final long currencyId; public final long units; private Mint(long accountId, long currencyId, long units) { this.accountId = accountId; this.currencyId = currencyId; this.units = units; } } private static final DbKey.LinkKeyFactory<CurrencyMint> currencyMintDbKeyFactory = new DbKey.LinkKeyFactory<CurrencyMint>("currency_id", "account_id") { @Override public DbKey newKey(CurrencyMint currencyMint) { return currencyMint.dbKey; } }; private static final VersionedEntityDbTable<CurrencyMint> currencyMintTable = new VersionedEntityDbTable<CurrencyMint>("currency_mint", currencyMintDbKeyFactory) { @Override protected CurrencyMint load(Connection con, ResultSet rs) throws SQLException { return new CurrencyMint(rs); } @Override protected void save(Connection con, CurrencyMint currencyMint) throws SQLException { currencyMint.save(con); } }; private static final Listeners<Mint,Event> listeners = new Listeners<>(); public static boolean addListener(Listener<Mint> listener, Event eventType) { return listeners.addListener(listener, eventType); } public static boolean removeListener(Listener<Mint> listener, Event eventType) { return listeners.removeListener(listener, eventType); } static void init() {} private final DbKey dbKey; private final long currencyId; private final long accountId; private long counter; private CurrencyMint(long currencyId, long accountId, long counter) { this.currencyId = currencyId; this.accountId = accountId; this.dbKey = currencyMintDbKeyFactory.newKey(this.currencyId, this.accountId); this.counter = counter; } private CurrencyMint(ResultSet rs) throws SQLException { this.currencyId = rs.getLong("currency_id"); this.accountId = rs.getLong("account_id"); this.dbKey = currencyMintDbKeyFactory.newKey(this.currencyId, this.accountId); this.counter = rs.getLong("counter"); } private void save(Connection con) throws SQLException { try (PreparedStatement pstmt = con.prepareStatement("MERGE INTO currency_mint (currency_id, account_id, counter, height, latest) " + "KEY (currency_id, account_id, height) VALUES (?, ?, ?, ?, TRUE)")) { int i = 0; pstmt.setLong(++i, this.currencyId); pstmt.setLong(++i, this.accountId); pstmt.setLong(++i, this.counter); pstmt.setInt(++i, Nxt.getBlockchain().getHeight()); pstmt.executeUpdate(); } } public long getCurrencyId() { return currencyId; } public long getAccountId() { return accountId; } public long getCounter() { return counter; } static void mintCurrency(LedgerEvent event, long eventId, final Account account, final Attachment.MonetarySystemCurrencyMinting attachment) { CurrencyMint currencyMint = currencyMintTable.get(currencyMintDbKeyFactory.newKey(attachment.getCurrencyId(), account.getId())); if (currencyMint != null && attachment.getCounter() <= currencyMint.getCounter()) { return; } Currency currency = Currency.getCurrency(attachment.getCurrencyId()); if (CurrencyMinting.meetsTarget(account.getId(), currency, attachment)) { if (currencyMint == null) { currencyMint = new CurrencyMint(attachment.getCurrencyId(), account.getId(), attachment.getCounter()); } else { currencyMint.counter = attachment.getCounter(); } currencyMintTable.insert(currencyMint); long units = Math.min(attachment.getUnits(), currency.getMaxSupply() - currency.getCurrentSupply()); account.addToCurrencyAndUnconfirmedCurrencyUnits(event, eventId, currency.getId(), units); currency.increaseSupply(units); listeners.notify(new Mint(account.getId(), currency.getId(), units), Event.CURRENCY_MINT); } else { Logger.logDebugMessage("Currency mint hash no longer meets target %s", attachment.getJSONObject().toJSONString()); } } public static long getCounter(long currencyId, long accountId) { CurrencyMint currencyMint = currencyMintTable.get(currencyMintDbKeyFactory.newKey(currencyId, accountId)); if (currencyMint != null) { return currencyMint.getCounter(); } else { return 0; } } static void deleteCurrency(Currency currency) { List<CurrencyMint> currencyMints = new ArrayList<>(); try (DbIterator<CurrencyMint> mints = currencyMintTable.getManyBy(new DbClause.LongClause("currency_id", currency.getId()), 0, -1)) { while (mints.hasNext()) { currencyMints.add(mints.next()); } } currencyMints.forEach(currencyMintTable::delete); } }