Skip to content
This repository was archived by the owner on Mar 2, 2023. It is now read-only.

Commit 7d10a15

Browse files
Filter transactions based on height as early as possible
1 parent ba5ec47 commit 7d10a15

15 files changed

Lines changed: 190 additions & 189 deletions

accounter/accounter.go

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package accounter
22

33
import (
44
"encoding/hex"
5-
"fmt"
65
"log"
76
"sync"
87
"time"
@@ -28,8 +27,10 @@ type Accounter struct {
2827
xpubs []string
2928
blockHeight uint32 // height at which we want to compute the balance
3029

31-
addresses map[string]address // map of address script => (Address, txHashes)
32-
transactions map[string]transaction // map of txhash => transaction
30+
addresses map[string]address // map of address script => (Address, txHashes)
31+
txAddressesMu sync.Mutex
32+
txAddresses map[string][]*deriver.Address // map of txhash => []Address
33+
transactions map[string]transaction // map of txhash => transaction
3334

3435
backend backend.Backend
3536
deriver *deriver.AddressDeriver
@@ -71,20 +72,19 @@ type vout struct {
7172
}
7273

7374
// New instantiates a new Accounter.
74-
// TODO: find a better way to pass options to the NewCounter. Maybe thru a config or functional option params?
7575
func New(b backend.Backend, addressDeriver *deriver.AddressDeriver, lookahead uint32, blockHeight uint32) *Accounter {
76-
a := &Accounter{
76+
return &Accounter{
7777
blockHeight: blockHeight,
7878
backend: b,
7979
deriver: addressDeriver,
8080
lookahead: lookahead,
8181
lastAddresses: [2]uint32{lookahead, lookahead},
82+
addresses: make(map[string]address),
83+
txAddresses: make(map[string][]*deriver.Address),
84+
transactions: make(map[string]transaction),
85+
addrResponses: b.AddrResponses(),
86+
txResponses: b.TxResponses(),
8287
}
83-
a.addresses = make(map[string]address)
84-
a.transactions = make(map[string]transaction)
85-
a.addrResponses = b.AddrResponses()
86-
a.txResponses = b.TxResponses()
87-
return a
8888
}
8989

9090
func (a *Accounter) ComputeBalance() uint64 {
@@ -114,31 +114,25 @@ func (a *Accounter) fetchTransactions() {
114114
func (a *Accounter) processTransactions() {
115115
for hash, tx := range a.transactions {
116116
// remove transactions which are too recent
117-
if tx.height > int64(a.blockHeight) {
118-
reporter.GetInstance().Logf("transaction %s has height %d > BLOCK HEIGHT (%d)", hash, tx.height, a.blockHeight)
117+
if (tx.height > int64(a.blockHeight)) || (tx.height == 0) {
118+
log.Printf("backend failed to filter tx %s (%d, %d)", hash, tx.height, a.blockHeight)
119119
delete(a.transactions, hash)
120120
}
121-
// remove transactions which haven't been mined
122-
if tx.height <= 0 {
123-
reporter.GetInstance().Logf("transaction %s has not been mined, yet (height=%d)", hash, tx.height)
124-
delete(a.transactions, hash)
121+
if tx.height < 0 {
122+
log.Panicf("tx %s has negative height %d", hash, tx.height)
125123
}
126124
}
127-
reporter.GetInstance().SetTxAfterFilter(int32(len(a.transactions)))
128-
reporter.GetInstance().Log("done filtering")
129125

130126
// TODO: we could check that scheduled == fetched in the metrics we track in reporter.
131-
132127
// parse the transaction hex
133128
for hash, tx := range a.transactions {
134129
b, err := hex.DecodeString(tx.hex)
135130
if err != nil {
136-
fmt.Printf("failed to unhex transaction %s: %s", hash, tx.hex)
131+
log.Panicf("failed to unhex transaction %s: %s", hash, tx.hex)
137132
}
138133
parsedTx, err := btcutil.NewTxFromBytes(b)
139134
if err != nil {
140-
fmt.Printf("failed to parse transaction %s: %s", hash, tx.hex)
141-
continue
135+
log.Panicf("failed to parse transaction %s: %s", hash, tx.hex)
142136
}
143137
for _, txin := range parsedTx.MsgTx().TxIn {
144138
tx.vin = append(tx.vin, vin{
@@ -234,7 +228,10 @@ func (a *Accounter) sendWork() {
234228
indexes[change]++
235229
}
236230
}
237-
// apparently no more work for us, so we can sleep a bit
231+
// apparently no more work for now.
232+
233+
// TODO: we should either merge sendWork/recvWork or use some kind of mutex to sleep exactly
234+
// until there's more work that needs to be done. For now, a simple sleep works.
238235
time.Sleep(time.Millisecond * 100)
239236
}
240237
}
@@ -251,6 +248,7 @@ func (a *Accounter) recvWork() {
251248
continue
252249
}
253250
reporter.GetInstance().IncAddressesFetched()
251+
reporter.GetInstance().Logf("received address: %s", resp.Address)
254252

255253
a.countMu.Lock()
256254
a.processedAddrCount++
@@ -263,20 +261,23 @@ func (a *Accounter) recvWork() {
263261

264262
a.countMu.Lock()
265263
for _, txHash := range resp.TxHashes {
264+
// TODO: mark this txHash as having been scheduled. So we don't fetch it multiple times.
266265
if _, exists := a.transactions[txHash]; !exists {
267266
a.backend.TxRequest(txHash)
268267
a.seenTxCount++
269268
}
270269
}
271270
a.countMu.Unlock()
272271

272+
// we can only update the lastAddresses after we filter the transaction heights
273+
a.txAddressesMu.Lock()
274+
for _, txHash := range resp.TxHashes {
275+
a.txAddresses[txHash] = append(a.txAddresses[txHash], resp.Address)
276+
}
277+
a.txAddressesMu.Unlock()
278+
273279
reporter.GetInstance().Logf("address %s has %d transactions", resp.Address, len(resp.TxHashes))
274280

275-
if resp.HasTransactions() {
276-
a.countMu.Lock()
277-
a.lastAddresses[resp.Address.Change()] = Max(a.lastAddresses[resp.Address.Change()], resp.Address.Index()+a.lookahead)
278-
a.countMu.Unlock()
279-
}
280281
case resp, ok := <-txResponses:
281282
// channel is closed now, so ignore this case by blocking forever
282283
if !ok {
@@ -285,6 +286,7 @@ func (a *Accounter) recvWork() {
285286
}
286287

287288
reporter.GetInstance().IncTxFetched()
289+
reporter.GetInstance().Logf("received tx: %s", resp.Hash)
288290

289291
a.countMu.Lock()
290292
a.processedTxCount++
@@ -297,6 +299,15 @@ func (a *Accounter) recvWork() {
297299
vout: []vout{},
298300
}
299301
a.transactions[resp.Hash] = tx
302+
303+
a.txAddressesMu.Lock()
304+
a.countMu.Lock()
305+
for _, addr := range a.txAddresses[resp.Hash] {
306+
a.lastAddresses[addr.Change()] = Max(a.lastAddresses[addr.Change()], addr.Index()+a.lookahead)
307+
}
308+
a.countMu.Unlock()
309+
a.txAddressesMu.Unlock()
310+
300311
case <-time.Tick(1 * time.Second):
301312
if a.complete() {
302313
return

accounter/accounter_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func TestComputeBalanceTestnet(t *testing.T) {
9494
deriver := deriver.NewAddressDeriver(Testnet, pubs, 1, "")
9595
b, err := backend.NewFixtureBackend("testdata/tpub_data.json")
9696
assert.NoError(t, err)
97+
b.Start(1435169)
9798
a := New(b, deriver, 100, 1435169)
9899

99100
assert.Equal(t, uint64(267893477), a.ComputeBalance())

accounter/testdata/tpub_data.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
"metadata": {
3+
"height": 1435169
4+
},
25
"addresses": [
36
{
47
"address": "mfsNoNz57ANkYrCzHaLZDLoMGujBW8u3zv",

backend/backend.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,21 @@ import (
2828
// forgo the Finish() method and have the Accounter read from the TxResponses channel until it has
2929
// all the data it needs. This would require the Accounter to maintain its own set of transactions.
3030
type Backend interface {
31+
// Returns chain height. Possibly connects + disconnects from first node.
3132
ChainHeight() uint32
3233

34+
// Gets backend ready to serve requests
35+
Start(blockHeight uint32) error
36+
37+
// Request-response channels
3338
AddrRequest(addr *deriver.Address)
3439
AddrResponses() <-chan *AddrResponse
3540
TxRequest(txHash string)
3641
TxResponses() <-chan *TxResponse
3742
BlockRequest(height uint32)
3843
BlockResponses() <-chan *BlockResponse
3944

45+
// Call this to disconnect from nodes and cleanup
4046
Finish()
4147
}
4248

backend/btcd_backend.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package backend
33
import (
44
"fmt"
55
"log"
6+
"math"
67
"sync"
78

89
"github.com/btcsuite/btcd/btcjson"
@@ -80,15 +81,10 @@ func NewBtcdBackend(host, port, user, pass string, network Network) (*BtcdBacken
8081
return nil, errors.Errorf("Unexpected genesis block %s != %s", genesis.String(), GenesisBlock(network))
8182
}
8283

83-
height, err := client.GetBlockCount()
84-
if err != nil {
85-
return nil, errors.Wrap(err, "could not connect to the Btcd server")
86-
}
87-
88-
b := &BtcdBackend{
84+
return &BtcdBackend{
8985
client: client,
9086
network: network,
91-
chainHeight: uint32(height),
87+
chainHeight: 0,
9288
addrRequests: make(chan *deriver.Address, addrRequestsChanSize),
9389
addrResponses: make(chan *AddrResponse, addrRequestsChanSize),
9490
txRequests: make(chan string, 2*maxTxsPerAddr),
@@ -99,13 +95,26 @@ func NewBtcdBackend(host, port, user, pass string, network Network) (*BtcdBacken
9995
blockHeightLookup: make(map[string]int64),
10096
cachedTransactions: make(map[string]*TxResponse),
10197
doneCh: make(chan bool),
98+
}, nil
99+
}
100+
101+
func (b *BtcdBackend) ChainHeight() uint32 {
102+
height, err := b.client.GetBlockCount()
103+
PanicOnError(err)
104+
if height <= 0 || height > math.MaxUint32 {
105+
log.Panicf("invalid height: %d", height)
102106
}
107+
return uint32(height)
108+
}
109+
110+
func (b *BtcdBackend) Start(blockHeight uint32) error {
111+
b.chainHeight = blockHeight
103112

104113
// launch
105114
for i := 0; i < concurrency; i++ {
106115
go b.processRequests()
107116
}
108-
return b, nil
117+
return nil
109118
}
110119

111120
// AddrRequest schedules a request to the backend to lookup information related
@@ -152,10 +161,6 @@ func (b *BtcdBackend) Finish() {
152161
b.client.Disconnect()
153162
}
154163

155-
func (b *BtcdBackend) ChainHeight() uint32 {
156-
return b.chainHeight
157-
}
158-
159164
func (b *BtcdBackend) processRequests() {
160165
for {
161166
select {

backend/electrum/blockchain.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ func (n *Node) BlockchainAddressGetHistory(address string) ([]*Transaction, erro
226226
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get
227227
func (n *Node) BlockchainTransactionGet(txid string) (string, error) {
228228
var hex string
229-
err := n.request("blockchain.transaction.get", []interface{}{txid, false}, &hex)
229+
// some servers don't handle the second parameter (even though they advertise version 1.2)
230+
// so we leave it out.
231+
err := n.request("blockchain.transaction.get", []interface{}{txid}, &hex)
230232
return hex, err
231233
}
232234

0 commit comments

Comments
 (0)