Skip to content

Commit 711abc5

Browse files
committed
use db instead of searchrawtransactions
db/dcrpg: Rewrite GetAddressTransactionsRawWithSkip to use the pg db instead of the searchrawtransactions RPC. Remove InsightSearchRPCAddressTransactions, which was unused. api/types: Stop using chainjson.VinPrevOut. Add Vin type that is similar to chainjson.Vin. The VinPrevOut type is not even emulated because it was almost useless: the amout was already in the Vin struct, and the address of the prev out is not of high importance when requesting address transactions. Redefine AddressTxRaw with new Vin type. work with dcrd RPC version 7.0.0 or 8.0.0
1 parent 49d45d1 commit 711abc5

14 files changed

Lines changed: 434 additions & 317 deletions

File tree

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ Always run the Current release or on the Current stable branch. Do not use `mast
132132
- [Go](https://golang.org) 1.18 or 1.19
133133
- [Node.js](https://nodejs.org/en/download/) 16.x or 18.x. Node.js is only used
134134
as a build tool, and is **not used at runtime**.
135-
- Running `dcrd` running with `--txindex --addrindex`, and synchronized to the
136-
current best block on the network. On startup, dcrdata will verify that the
137-
dcrd version is compatible.
135+
- Running `dcrd` running with `--txindex`, and synchronized to the current best
136+
block on the network. On startup, dcrdata will verify that the dcrd version is
137+
compatible.
138138
- PostgreSQL 11+
139139

140140
## Docker Support
@@ -367,7 +367,6 @@ dcrd.conf:
367367

368368
```ini
369369
txindex=1
370-
addrindex=1
371370
```
372371

373372
If these parameters are not set, dcrdata will be unable to retrieve transaction

api/types/apitypes.go

Lines changed: 103 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type TimeAPI struct {
2424
S dbtypes.TimeDef
2525
}
2626

27+
var _ fmt.Stringer = TimeAPI{}
28+
2729
// String formats the time in a human-friendly layout.
2830
func (t TimeAPI) String() string {
2931
return t.S.String()
@@ -34,6 +36,9 @@ func (t TimeAPI) UNIX() int64 {
3436
return t.S.UNIX()
3537
}
3638

39+
var _ json.Marshaler = (*TimeAPI)(nil)
40+
var _ json.Unmarshaler = (*TimeAPI)(nil)
41+
3742
// MarshalJSON is set as the default marshalling function for TimeAPI struct.
3843
func (t *TimeAPI) MarshalJSON() ([]byte, error) {
3944
return json.Marshal(t.S.UNIX())
@@ -83,6 +88,7 @@ type Tx struct {
8388
Block *BlockID `json:"block,omitempty"`
8489
}
8590

91+
// Vin is an alias for dcrd's rpc/jsonrpc/types/v3.Vin type.
8692
type Vin = chainjson.Vin
8793

8894
// TxShort models info about transaction TxID
@@ -191,7 +197,7 @@ type Vout struct {
191197
N uint32 `json:"n"`
192198
Version uint16 `json:"version"`
193199
ScriptPubKeyDecoded ScriptPubKey `json:"scriptPubKey"`
194-
Spend *TxInputID `json:"spend,omitempty"`
200+
Spend *TxInputID `json:"spend,omitempty"` // unused?
195201
}
196202

197203
// TxInputID specifies a transaction input as hash:vin_index.
@@ -380,19 +386,104 @@ type Address struct {
380386
Transactions []*AddressTxShort `json:"address_transactions"`
381387
}
382388

389+
// ScriptSig models the signature script used to redeem a transaction output.
390+
// type ScriptSig struct {
391+
// Asm string `json:"asm,omitempty"`
392+
// Hex string `json:"hex,omitempty"`
393+
// }
394+
395+
// VinShort describes a transaction input with limited detail, for the address
396+
// txn API endpoints. In particular, there is no ScriptSig or Sequence, and the
397+
// string fields for Coinbase, Stakebase, and TreasurySpend are just booleans.
398+
type VinShort struct {
399+
Coinbase bool `json:"coinbase"`
400+
Stakebase bool `json:"stakebase"`
401+
Treasurybase bool `json:"treasurybase"`
402+
TreasurySpend bool `json:"treasuryspend"`
403+
Txid string `json:"txid"`
404+
Vout uint32 `json:"vout"`
405+
Tree int8 `json:"tree"`
406+
AmountIn float64 `json:"amountin"`
407+
BlockHeight *uint32 `json:"blockheight,omitempty"`
408+
BlockIndex *uint32 `json:"blockindex,omitempty"`
409+
// No ScriptSig or Sequence
410+
}
411+
412+
// MarshalJSON is used to marshal a Vin to JSON with special handling for when
413+
// the these are generate coins (should be zero txid).
414+
func (v *VinShort) MarshalJSON() ([]byte, error) {
415+
switch {
416+
case v.Coinbase:
417+
generated := struct {
418+
Coinbase bool `json:"coinbase"`
419+
AmountIn float64 `json:"amountin"`
420+
}{
421+
Coinbase: true,
422+
AmountIn: v.AmountIn,
423+
}
424+
return json.Marshal(generated)
425+
case v.Stakebase:
426+
generated := struct {
427+
Stakebase bool `json:"stakebase"`
428+
AmountIn float64 `json:"amountin"`
429+
}{
430+
Stakebase: true,
431+
AmountIn: v.AmountIn,
432+
}
433+
return json.Marshal(generated)
434+
case v.Treasurybase:
435+
generated := struct {
436+
Treasurybase bool `json:"treasurybase"`
437+
AmountIn float64 `json:"amountin"`
438+
}{
439+
Treasurybase: true,
440+
AmountIn: v.AmountIn,
441+
}
442+
return json.Marshal(generated)
443+
case v.TreasurySpend:
444+
generated := struct {
445+
TreasurySpend bool `json:"treasuryspend"`
446+
AmountIn float64 `json:"amountin"`
447+
}{
448+
TreasurySpend: true,
449+
AmountIn: v.AmountIn,
450+
}
451+
return json.Marshal(generated)
452+
453+
}
454+
455+
return json.Marshal(struct {
456+
Txid string `json:"txid"`
457+
Vout uint32 `json:"vout"`
458+
Tree int8 `json:"tree"`
459+
AmountIn float64 `json:"amountin"`
460+
BlockHeight *uint32 `json:"blockheight,omitempty"`
461+
BlockIndex *uint32 `json:"blockindex,omitempty"`
462+
}{
463+
Txid: v.Txid,
464+
Vout: v.Vout,
465+
Tree: v.Tree,
466+
AmountIn: v.AmountIn,
467+
BlockHeight: v.BlockHeight,
468+
BlockIndex: v.BlockIndex,
469+
})
470+
}
471+
383472
// AddressTxRaw is modeled from SearchRawTransactionsResult but with size in
384-
// place of hex
473+
// place of hex, and a limited vin structure.
385474
type AddressTxRaw struct {
386-
Size int32 `json:"size"`
387-
TxID string `json:"txid"`
388-
Version int32 `json:"version"`
389-
Locktime uint32 `json:"locktime"`
390-
Vin []chainjson.VinPrevOut `json:"vin"`
391-
Vout []Vout `json:"vout"`
392-
Confirmations int64 `json:"confirmations"`
393-
BlockHash string `json:"blockhash"`
394-
Time TimeAPI `json:"time,omitempty"`
395-
Blocktime TimeAPI `json:"blocktime,omitempty"`
475+
Size int32 `json:"size"`
476+
TxID string `json:"txid"`
477+
Version int32 `json:"version"`
478+
Locktime uint32 `json:"locktime"`
479+
Type int32 `json:"type"`
480+
Vin []VinShort `json:"vin"`
481+
Vout []Vout `json:"vout"`
482+
Confirmations int64 `json:"confirmations"`
483+
BlockHash string `json:"blockhash,omitempty"`
484+
Time TimeAPI `json:"time,omitempty"` // for mempool txns?
485+
Blocktime *TimeAPI `json:"blocktime,omitempty"` // vs mined?
486+
// BlockHeight int64 `json:"blockheight"`
396487
}
397488

398489
// AddressTxShort is a subset of AddressTxRaw with just the basic tx details
@@ -438,37 +529,6 @@ type TxRawWithTxType struct {
438529
TxType string
439530
}
440531

441-
// ScriptSig models the signature script used to redeem the origin transaction
442-
// as a JSON object (non-coinbase txns only)
443-
type ScriptSig struct {
444-
Asm string `json:"asm,omitempty"`
445-
Hex string `json:"hex,omitempty"`
446-
}
447-
448-
// PrevOut represents previous output for an input Vin.
449-
type PrevOut struct {
450-
Addresses []string `json:"addresses,omitempty"`
451-
Value float64 `json:"value"`
452-
}
453-
454-
// VinPrevOut is like Vin except it includes PrevOut. It is used by
455-
// searchrawtransaction
456-
type VinPrevOut struct {
457-
Coinbase string `json:"coinbase"`
458-
Stakebase string `json:"stakebase"`
459-
Txid string `json:"txid"`
460-
Vout uint32 `json:"vout"`
461-
Tree int8 `json:"tree"`
462-
AmountIn *float64 `json:"amountin,omitempty"`
463-
BlockHeight *uint32 `json:"blockheight,omitempty"`
464-
BlockIndex *uint32 `json:"blockindex,omitempty"`
465-
ScriptSig *ScriptSig `json:"scriptSig"`
466-
PrevOut *PrevOut `json:"prevOut"`
467-
Sequence uint32 `json:"sequence"`
468-
}
469-
470-
// end copy-paste from chainjson
471-
472532
// Status indicates the state of the server. All fields are mutex protected and
473533
// and should be set with the getters and setters.
474534
type Status struct {

cmd/dcrdata/internal/api/apiroutes.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ func (c *appContext) setTrimmedTxSpends(tx *apitypes.TrimmedTx) error {
624624
return c.setOutputSpends(tx.TxID, tx.Vout)
625625
}
626626

627+
// getTransaction handles the /tx/{txid} API endpoint.
627628
func (c *appContext) getTransaction(w http.ResponseWriter, r *http.Request) {
628629
// Look up any spending transactions for each output of this transaction
629630
// when the client requests spends with the URL query ?spends=true.
@@ -811,6 +812,7 @@ func (c *appContext) getTxSwapsInfo(w http.ResponseWriter, r *http.Request) {
811812
writeJSON(w, swapsInfo, m.GetIndentCtx(r))
812813
}
813814

815+
// getTransactions handles the /txns POST API endpoint.
814816
func (c *appContext) getTransactions(w http.ResponseWriter, r *http.Request) {
815817
// Look up any spending transactions for each output of this transaction
816818
// when the client requests spends with the URL query ?spends=true.
@@ -1857,6 +1859,8 @@ func (c *appContext) getAddressTransactions(w http.ResponseWriter, r *http.Reque
18571859
writeJSON(w, txs, m.GetIndentCtx(r))
18581860
}
18591861

1862+
// getAddressTransactionsRaw handles the various /address/{addr}/.../raw API
1863+
// endpoints.
18601864
func (c *appContext) getAddressTransactionsRaw(w http.ResponseWriter, r *http.Request) {
18611865
addresses, err := m.GetAddressCtx(r, c.Params)
18621866
if err != nil || len(addresses) > 1 {
@@ -1869,14 +1873,13 @@ func (c *appContext) getAddressTransactionsRaw(w http.ResponseWriter, r *http.Re
18691873
skip := int64(m.GetMCtx(r))
18701874
if count <= 0 {
18711875
count = 10
1872-
} else if count > 8000 {
1873-
count = 8000
1876+
} else if count > 1000 {
1877+
count = 1000
18741878
}
18751879
if skip <= 0 {
18761880
skip = 0
18771881
}
18781882

1879-
// TODO: add postgresql powered method
18801883
txs := c.DataSource.GetAddressTransactionsRawWithSkip(address, int(count), int(skip))
18811884
if txs == nil {
18821885
http.Error(w, http.StatusText(422), 422)

cmd/dcrdata/internal/middleware/apimiddleware.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,12 @@ func AddressPathCtxN(n int) func(next http.Handler) http.Handler {
766766
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
767767
addressStr := chi.URLParam(r, "address")
768768
if len(addressStr) < minAddressLength {
769+
apiLog.Warnf("AddressPathCtxN rejecting address parameter of length %d", len(addressStr))
770+
http.Error(w, "invalid address", http.StatusUnprocessableEntity)
771+
return
772+
}
773+
if n == 1 && len(addressStr) > maxAddressLength {
774+
apiLog.Warnf("AddressPathCtxN rejecting address parameter of length %d", len(addressStr))
769775
http.Error(w, "invalid address", http.StatusUnprocessableEntity)
770776
return
771777
}

db/dbtypes/extraction.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func processTransactions(msgBlock *wire.MsgBlock, tree int8, chainParams *chainc
9292
BlockHash: blockHash.String(),
9393
BlockHeight: int64(blockHeight),
9494
BlockTime: blockTime,
95-
Time: blockTime, // TODO, receive time?
95+
Time: blockTime, // TODO, receive time? no! REMOVE
9696
TxType: int16(txType),
9797
Version: tx.Version,
9898
Tree: tree,

db/dbtypes/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1923,7 +1923,7 @@ type Tx struct {
19231923
BlockHash string `json:"block_hash"`
19241924
BlockHeight int64 `json:"block_height"`
19251925
BlockTime TimeDef `json:"block_time"`
1926-
Time TimeDef `json:"time"`
1926+
Time TimeDef `json:"time"` // REMOVE!
19271927
TxType int16 `json:"tx_type"`
19281928
Version uint16 `json:"version"`
19291929
Tree int8 `json:"tree"`

db/dcrpg/insightapi.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,29 +131,6 @@ func (pgb *ChainDB) AddressIDsByOutpoint(txHash string, voutIndex uint32) ([]uin
131131
return ids, addrs, val, pgb.replaceCancelError(err)
132132
}
133133

134-
// InsightSearchRPCAddressTransactions performs a searchrawtransactions for the
135-
// specified address, max number of transactions, and offset into the transaction
136-
// list. The search results are in reverse temporal order.
137-
// TODO: Does this really need all the prev vout extra data?
138-
func (pgb *ChainDB) InsightSearchRPCAddressTransactions(addr string, count,
139-
skip int) []*chainjson.SearchRawTransactionsResult {
140-
address, err := stdaddr.DecodeAddress(addr, pgb.chainParams)
141-
if err != nil {
142-
log.Infof("Invalid address %s: %v", addr, err)
143-
return nil
144-
}
145-
prevVoutExtraData := true
146-
ctx, cancel := context.WithTimeout(pgb.ctx, 10*time.Second)
147-
defer cancel()
148-
txs, err := pgb.Client.SearchRawTransactionsVerbose(ctx,
149-
address, skip, count, prevVoutExtraData, true, nil)
150-
if err != nil {
151-
log.Warnf("GetAddressTransactions failed for address %s: %v", addr, err)
152-
return nil
153-
}
154-
return txs
155-
}
156-
157134
// GetTransactionHex returns the full serialized transaction for the specified
158135
// transaction hash as a hex encode string.
159136
func (pgb *ChainDB) GetTransactionHex(txid *chainhash.Hash) string {

db/dcrpg/internal/addrstmts.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ const (
6767
` ON addresses(matching_tx_hash);`
6868
DeindexAddressTableOnMatchingTxHash = `DROP INDEX IF EXISTS ` + IndexOfAddressTableOnMatchingTx + ` CASCADE;`
6969

70-
// TODO: figure out why this index exists since it's covered by the unique
71-
// on tx_vin_vout_row_id too.
70+
// IndexAddressTableOnAddress exists so address can be the first column in
71+
// an index; it is second in tx_vin_vout_row_id.
7272
IndexAddressTableOnAddress = `CREATE INDEX IF NOT EXISTS ` + IndexOfAddressTableOnAddress +
7373
` ON addresses(address);`
7474
DeindexAddressTableOnAddress = `DROP INDEX IF EXISTS ` + IndexOfAddressTableOnAddress + ` CASCADE;`
@@ -82,6 +82,59 @@ const (
8282
// SelectFundingTxsByTx = `SELECT id, prev_tx_hash FROM vins WHERE tx_hash=$1;`
8383
// SelectFundingTxByTxIn = `SELECT id, prev_tx_hash FROM vins WHERE tx_hash=$1 AND tx_index=$2;`
8484

85+
addressTxnsSubQuery = `SELECT tx_hash
86+
FROM addresses
87+
WHERE address = $1
88+
AND valid_mainchain
89+
GROUP BY tx_hash
90+
ORDER BY MAX(block_time) DESC
91+
LIMIT $2 OFFSET $3`
92+
93+
// need random table name? does lib/pq share sessions?
94+
CreateTempAddrTxnsTable = `CREATE TEMPORARY TABLE address_transactions
95+
ON COMMIT DROP -- do in a txn!
96+
AS (` + addressTxnsSubQuery + `);`
97+
98+
SelectVinsForAddress0 = `SELECT vins.tx_hash, vins.tx_index, vins.prev_tx_hash, vins.prev_tx_index,
99+
vins.prev_tx_tree, vins.value_in -- no block height or block index
100+
FROM (` + addressTxnsSubQuery + `) atxs
101+
-- JOIN transactions txs ON txs.tx_hash=atxs.tx_hash
102+
-- JOIN vins ON vins.id = any(txs.vin_db_ids)
103+
JOIN vins ON vins.tx_hash = atxs.tx_hash;`
104+
105+
SelectVinsForAddress = `SELECT vins.tx_hash, vins.tx_index, vins.prev_tx_hash, vins.prev_tx_index,
106+
vins.prev_tx_tree, vins.value_in, prevtxs.block_height, prevtxs.block_index
107+
FROM (` + addressTxnsSubQuery + `) atxs
108+
JOIN vins ON vins.tx_hash = atxs.tx_hash -- JOIN vins on vins.id = any(txs.vin_db_ids)
109+
LEFT JOIN transactions prevtxs ON vins.prev_tx_hash=prevtxs.tx_hash;` // LEFT JOIN because prev_tx_hash may be coinbase
110+
111+
SelectVoutsForAddress = `SELECT vouts.value, vouts.tx_hash, vouts.tx_index, vouts.version, vouts.pkscript
112+
FROM (` + addressTxnsSubQuery + `) atxs
113+
JOIN vouts ON vouts.tx_hash = atxs.tx_hash;` // -- vouts.id = any(transactions.vout_db_ids)
114+
115+
// select distinct tx_hash, block_time
116+
// from addresses
117+
// where address = 'DsSWTHFrsXV77SwAcMe451kJTwWjwPYjWTM' and valid_mainchain
118+
// order by block_time desc
119+
// limit 10 offset 0;
120+
121+
SelectAddressTxns = `SELECT txs.tx_hash, txs.block_hash, txs.block_height, txs.block_time,
122+
txs.version, txs.lock_time, txs.size, txs.tx_type, cardinality(txs.vin_db_ids), cardinality(txs.vout_db_ids)
123+
FROM (` + addressTxnsSubQuery + `) atxs
124+
JOIN transactions txs ON txs.tx_hash = atxs.tx_hash
125+
WHERE is_valid AND is_mainchain -- needed?
126+
ORDER BY txs.block_time DESC;`
127+
128+
// SelectAddressTxnsAlt is very slow with a join on the full tables
129+
SelectAddressTxnsAlt = `SELECT txs.tx_hash, txs.vin_db_ids, txs.vout_db_ids,
130+
txs.block_hash, txs.block_height, txs.block_time,
131+
txs.version, txs.lock_time, txs.size
132+
FROM addresses
133+
JOIN transactions txs ON addresses.tx_hash=txs.tx_hash AND valid_mainchain
134+
WHERE address = $1 AND is_valid AND is_mainchain
135+
ORDER BY block_height -- NOTE: height vs time
136+
LIMIT $2 OFFSET $3;`
137+
85138
addrsColumnNames = `id, address, matching_tx_hash, tx_hash, tx_type, valid_mainchain,
86139
tx_vin_vout_index, block_time, tx_vin_vout_row_id, value, is_funding`
87140

db/dcrpg/internal/txstmts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717
block_hash TEXT,
1818
block_height INT8,
1919
block_time TIMESTAMPTZ,
20-
time TIMESTAMPTZ,
20+
time TIMESTAMPTZ, -- TODO: REMOVE!
2121
tx_type INT4,
2222
version INT4,
2323
tree INT2,

0 commit comments

Comments
 (0)