Skip to content

Commit 08ac720

Browse files
committed
Bitcoin: persist authenticated Bitcoin Core balance observations
1 parent 9e8541f commit 08ac720

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package bitcoin.module;
2+
3+
import bitcoin.base.BitcoinBase;
4+
5+
import java.math.BigDecimal;
6+
import java.math.RoundingMode;
7+
import java.sql.Connection;
8+
import java.sql.PreparedStatement;
9+
import java.sql.Statement;
10+
import java.time.Instant;
11+
12+
/**
13+
* Records authenticated Bitcoin Core balance observations.
14+
*
15+
* The value is an observation from getbalance, not an inferred wallet-file
16+
* balance. Monetary storage is fixed-point satoshis.
17+
*/
18+
public final class BitcoinBalanceObserver
19+
{
20+
private static final String TABLE = "bitcoin_balance_observations";
21+
22+
public void observe()
23+
{
24+
try (Connection conn = database.N21DataSource.get())
25+
{
26+
if (conn == null) return;
27+
createTable(conn);
28+
29+
BitcoinBase rpc = new BitcoinBase(null);
30+
String balanceText = rpc.get_balance();
31+
long satoshis = parseSatoshis(balanceText);
32+
33+
String sql = "INSERT INTO " + TABLE +
34+
" (wallet_name, balance_satoshis, source_method, observed_at) VALUES (?,?,?,?)";
35+
try (PreparedStatement ps = conn.prepareStatement(sql))
36+
{
37+
ps.setString(1, "United States");
38+
ps.setLong(2, satoshis);
39+
ps.setString(3, "getbalance");
40+
ps.setTimestamp(4, java.sql.Timestamp.from(Instant.now()));
41+
ps.executeUpdate();
42+
}
43+
}
44+
catch (Exception e)
45+
{
46+
exceptions.ExceptionHandler.dispatch(e);
47+
}
48+
}
49+
50+
private void createTable(final Connection conn) throws Exception
51+
{
52+
try (Statement st = conn.createStatement())
53+
{
54+
st.executeUpdate("CREATE TABLE IF NOT EXISTS " + TABLE + " (" +
55+
"id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY," +
56+
"wallet_name VARCHAR(128) NOT NULL," +
57+
"balance_satoshis BIGINT UNSIGNED NOT NULL," +
58+
"source_method VARCHAR(64) NOT NULL," +
59+
"observed_at DATETIME(6) NOT NULL," +
60+
"KEY ix_balance_wallet_time (wallet_name, observed_at)" +
61+
") ENGINE=InnoDB");
62+
}
63+
}
64+
65+
static long parseSatoshis(final String text)
66+
{
67+
if (text == null || text.isBlank()) throw new IllegalArgumentException("Bitcoin Core returned an empty balance");
68+
BigDecimal btc = new BigDecimal(text.trim());
69+
if (btc.signum() < 0 || btc.scale() > 8) throw new IllegalArgumentException("Invalid Bitcoin Core balance");
70+
BigDecimal satoshis = btc.movePointRight(8).setScale(0, RoundingMode.UNNECESSARY);
71+
if (satoshis.compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) > 0)
72+
throw new IllegalArgumentException("Bitcoin Core balance is too large");
73+
return satoshis.longValueExact();
74+
}
75+
}

0 commit comments

Comments
 (0)