-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
client/asset/btc: SPV Wallet Transaction History #2550
Conversation
client/asset/btc/btc.go
Outdated
btc.txHistoryDB.Store(txHistoryDB) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create tx history db: %v", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you store on err here on purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope.
client/asset/btc/btc.go
Outdated
copyWt := *wt | ||
copyWt.Submitted = true | ||
btc.pendingTxs[hash] = ©Wt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should work?
copyWt := *wt | |
copyWt.Submitted = true | |
btc.pendingTxs[hash] = ©Wt | |
wt.Submitted = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a mutex.
{ | ||
const blockQueryBuffer = 3 | ||
var blockToQuery uint64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the new block of code to scope these variables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I just put it there so it could be collapsed in the editor.
client/asset/btc/btc.go
Outdated
} | ||
recentTxs, err := btc.node.listTransactionsSinceBlock(int32(blockToQuery)) | ||
if err != nil { | ||
btc.log.Errorf("Error listing transactions since block %d: %v", tip-6, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btc.log.Errorf("Error listing transactions since block %d: %v", tip-6, err) | |
btc.log.Errorf("Error listing transactions since block %d: %v", blockToQuery, err) |
client/asset/btc/btc.go
Outdated
|
||
var fee uint64 | ||
if tx.Fee != nil { | ||
fee = toSatoshi(-*tx.Fee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess fee will always be reported as positive? I think dcrwallet returns a negative for fees. I have not checked yet just thinking out loud.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fee is also negative in btcwallet. That's why I'm negating it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok.
client/asset/btc/btc.go
Outdated
if tx.BlockNumber > 0 && tip > tx.BlockNumber { | ||
confs = tip - tx.BlockNumber + 1 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if tx.BlockNumber > 0 && tip > tx.BlockNumber { | |
confs = tip - tx.BlockNumber + 1 | |
} | |
if tx.BlockNumber > 0 && tip >= tx.BlockNumber { | |
confs = tip - tx.BlockNumber + 1 | |
} |
} else if gtr.BlockHash == "" && tx.BlockNumber != 0 { | ||
tx.BlockNumber = 0 | ||
updated = true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this indicate the tx had confs and now does not? So a reorg? If so the final if updated
should not remove this one from pendingTxs?
client/asset/btc/btc.go
Outdated
// TxHistory returns all the transactions the wallet has made. This | ||
// includes the ETH wallet and all token wallets. If refID is nil, then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TxHistory returns all the transactions the wallet has made. This | |
// includes the ETH wallet and all token wallets. If refID is nil, then | |
// TxHistory returns all the transactions the wallet has made. If refID is nil, then |
if !wt.Confirmed { | ||
txs = append(txs, &wt) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't they all be unconfirmed if in the pending txn bucket?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't just look at the pending transactions. The elements with blockPrefix
come before the elements with pendingPrefix
. This looks at those as well.
it := txn.NewIterator(opts) | ||
defer it.Close() | ||
|
||
for it.Seek(maxPendingKey); it.Valid(); it.Next() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for it.Seek(maxPendingKey); it.Valid(); it.Next() { | |
for it.Seek(maxPendingKey); it.ValidForPrefix(pendingPrefix); it.Next() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This search checks both pendingPrefix
and blockPrefix
.
This diff implements `TransactionHistory` for btc SPV wallets. A database is created to store all the transactions related to the wallet. Although the SPV wallet stores some transaction information already, the list transactions output is unreliable and confusing, so it is better to not parse it every time the tx history is requested. Also, it is not possible to differentiate a send and a swap transaction. Incoming transactions are discovered by querying `ListTransactionsSinceBlock` every block. There is also special handling for bond creation transactions which are not immediately submitted.
b30df47
to
02ed2dd
Compare
client/asset/btc/btc.go
Outdated
@@ -450,7 +450,8 @@ type BTCCloneCFG struct { | |||
// OmitRPCOptionsArg is for clones that don't take an options argument. | |||
OmitRPCOptionsArg bool | |||
// AssetID is the asset ID of the clone. | |||
AssetID uint32 | |||
AssetID uint32 | |||
SupportTxHistory bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This setting is only used internally. It's also curious, since we have a WalletHistorian
interface, so we should just implement that on the appropriate wallets.
How about buck54321/dcrdex@2acb714...buck54321:dcrdex:btctxhistorianinterface ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I was just putting off testing with LTC and BCH.. I've tested now though and they are both working as well.
client/asset/btc/btc.go
Outdated
// If past is true, the transactions prior to the refID are returned, otherwise | ||
// the transactions after the refID are returned. n is the number of | ||
// transactions to return. If n is <= 0, all the transactions will be returned. | ||
func (btc *intermediaryWallet) TxHistory(n int, refID *dex.Bytes, past bool) ([]*asset.WalletTransaction, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just make this func (btc *ExchangeWalletSPV)
, no?
client/asset/btc/electrum_client.go
Outdated
func (wc *electrumWallet) listTransactionsSinceBlock(blockHeight int32) ([]btcjson.ListTransactionsResult, error) { | ||
return nil, fmt.Errorf("not supported") | ||
} | ||
|
||
// part of btc.Wallet interface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to use a custom interface.
client/asset/btc/txdb.go
Outdated
if currBlockKey[0] == pendingPrefix[0] { | ||
if tx.BlockNumber > 0 { | ||
needNewBlockKey = true | ||
} | ||
} else if currBlockKey[0] == blockPrefix[0] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a helper function?
func hasPrefix(b, prefix []byte) bool {
if len(b) < len(prefix) {
return false
}
return bytes.Equal(b[:len(prefix)], prefix)
}
client/asset/btc/btc.go
Outdated
return | ||
} | ||
|
||
var hash chainhash.Hash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
txHash
txHistoryDB := btc.txDB() | ||
if txHistoryDB == nil { | ||
return | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we want to add the tx to the pendingTxs
map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this isn't like ETH where we're using the pendingTxs
to calculate a pending balance. pendingTxs
is just now getting added in this PR. It's just used to keep track of which transactions in the db may need to be updated whenever there's a new block.
client/asset/btc/btc.go
Outdated
|
||
for _, tx := range recentTxs { | ||
if tx.Category == "receive" { | ||
hash, err := chainhash.NewHashFromStr(tx.TxID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
txHash
type txDB interface { | ||
storeTx(tx *extendedWalletTx) error | ||
markTxAsSubmitted(txID dex.Bytes) error | ||
getTxs(n int, refID *dex.Bytes, past bool) ([]*asset.WalletTransaction, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document getTxs
and other complex areas of code. I see a line like this in checkPendingTxs
_, err = txHistoryDB.getTxs(1, &txID, false)
and it raises a bunch of questions. Does the past
argument matter if n
is 1
? Is refID
just a starting point for listing txs, or does that exact key have to be in the db? If you are just checking for the presence of a tx in the db, wouldn't a getTx
method be appropriate?
I have to look at the implementation for review anyway, but we don't need to doom others to the same fate for eternity. Good docs save man hours.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's just getting a single transaction, and you're right, a getTx
method would be nicer. I'll add some docs.
log: log}, nil | ||
} | ||
|
||
func (db *badgerTxDB) findFreeBlockKey(txn *badger.Txn, blockNumber uint64) ([]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could have sworn I already commented on this, but apparently not.
Why not just use the tx's block index?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the change, then I realized that btcwallet is not returning the block index. I guess we could update btcwallet, ltcwallet, and bchwallet, but is it really worth it? We have to do the this free key finding loop for pending transactions anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof. No. Just use findFreeBlockKey
.
@@ -5690,11 +5858,16 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time | |||
return nil, nil, fmt.Errorf("failed to fund bond tx: %w", err) | |||
} | |||
|
|||
var txIDToRemoveFromHistory *chainhash.Hash // will be non-nil if tx was added to history |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noting that a tx hash != a tx ID. A tx ID is a byte-reversed string of the tx hash.
|
||
pendingTxsCopy := make(map[chainhash.Hash]*extendedWalletTx, len(btc.pendingTxs)) | ||
btc.pendingTxsMtx.RLock() | ||
for hash, tx := range btc.pendingTxs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not going to hold this up, but please don't use hash
as a variable. Use h
when it unambiguous what kind of hash it is, as it is here, or use txHash
or blockHash
anywhere. Not only is hash
ambiguous in many contexts, it's also the name of a standard library Go package.
This diff implements
TransactionHistory
for btc SPV wallets. A database is created to store all the transactions related to the wallet. Although the SPV wallet stores some transaction information already, the list transactions output is unreliable and confusing, so it is better to not parse it every time the tx history is requested. Also, it is not possible to differentiate a send and a swap transaction.Incoming transactions are discovered by querying
ListTransactionsSinceBlock
every block. There is also special handling for bond creation transactions which are not immediately submitted.