Add GetUtxo. Mostly works.
This commit is contained in:
parent
ddc841924b
commit
b549587296
2 changed files with 184 additions and 0 deletions
|
@ -29,6 +29,7 @@ type rescanOptions struct {
|
||||||
watchAddrs []btcutil.Address
|
watchAddrs []btcutil.Address
|
||||||
watchOutPoints []wire.OutPoint
|
watchOutPoints []wire.OutPoint
|
||||||
watchTXIDs []chainhash.Hash
|
watchTXIDs []chainhash.Hash
|
||||||
|
txIdx uint32
|
||||||
quit <-chan struct{}
|
quit <-chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +108,14 @@ func WatchTXIDs(watchTXIDs ...chainhash.Hash) RescanOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxIdx specifies a hint transaction index into the block in which the UTXO
|
||||||
|
// is created (eg, coinbase is 0, next transaction is 1, etc.)
|
||||||
|
func TxIdx(txIdx uint32) RescanOption {
|
||||||
|
return func(ro *rescanOptions) {
|
||||||
|
ro.txIdx = txIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// QuitChan specifies the quit channel. This can be used by the caller to let
|
// QuitChan specifies the quit channel. This can be used by the caller to let
|
||||||
// an indefinite rescan (one with no EndBlock set) know it should gracefully
|
// an indefinite rescan (one with no EndBlock set) know it should gracefully
|
||||||
// shut down. If this isn't specified, an end block MUST be specified as Rescan
|
// shut down. If this isn't specified, an end block MUST be specified as Rescan
|
||||||
|
@ -438,3 +447,136 @@ func notifyBlock(block *btcutil.Block, outPoints *[]wire.OutPoint,
|
||||||
}
|
}
|
||||||
return relevantTxs, nil
|
return relevantTxs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUtxo gets the appropriate TxOut or errors if it's spent. The option
|
||||||
|
// WatchOutPoints (with a single outpoint) is required. StartBlock can be used
|
||||||
|
// to give a hint about which block the transaction is in, and TxIdx can be used
|
||||||
|
// to give a hint of which transaction in the block matches it (coinbase is 0,
|
||||||
|
// first normal transaction is 1, etc.).
|
||||||
|
func (s *ChainService) GetUtxo(options ...RescanOption) (*wire.TxOut, error) {
|
||||||
|
ro := defaultRescanOptions()
|
||||||
|
ro.startBlock = &waddrmgr.BlockStamp{
|
||||||
|
Hash: *s.chainParams.GenesisHash,
|
||||||
|
Height: 0,
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
option(ro)
|
||||||
|
}
|
||||||
|
if len(ro.watchOutPoints) != 1 {
|
||||||
|
return nil, fmt.Errorf("Must pass exactly one OutPoint.")
|
||||||
|
}
|
||||||
|
watchList := [][]byte{
|
||||||
|
builder.OutPointToFilterEntry(ro.watchOutPoints[0]),
|
||||||
|
ro.watchOutPoints[0].Hash[:],
|
||||||
|
}
|
||||||
|
// Track our position in the chain.
|
||||||
|
curHeader, curHeight, err := s.LatestBlock()
|
||||||
|
curStamp := &waddrmgr.BlockStamp{
|
||||||
|
Hash: curHeader.BlockHash(),
|
||||||
|
Height: int32(curHeight),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find our earliest possible block.
|
||||||
|
if (ro.startBlock.Hash != chainhash.Hash{}) {
|
||||||
|
_, height, err := s.GetBlockByHash(ro.startBlock.Hash)
|
||||||
|
if err == nil {
|
||||||
|
ro.startBlock.Height = int32(height)
|
||||||
|
} else {
|
||||||
|
ro.startBlock.Hash = chainhash.Hash{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ro.startBlock.Hash == chainhash.Hash{}) {
|
||||||
|
if ro.startBlock.Height == 0 {
|
||||||
|
ro.startBlock.Hash = *s.chainParams.GenesisHash
|
||||||
|
} else {
|
||||||
|
header, err := s.GetBlockByHeight(
|
||||||
|
uint32(ro.startBlock.Height))
|
||||||
|
if err == nil {
|
||||||
|
ro.startBlock.Hash = header.BlockHash()
|
||||||
|
} else {
|
||||||
|
ro.startBlock.Hash = *s.chainParams.GenesisHash
|
||||||
|
ro.startBlock.Height = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Tracef("Starting scan for output spend from known block %d (%s) "+
|
||||||
|
"back to block %d (%s)", curStamp.Height, curStamp.Hash)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Check the basic filter for the spend and the extended filter
|
||||||
|
// for the transaction in which the outpout is funded.
|
||||||
|
filter := s.GetCFilter(curStamp.Hash, false,
|
||||||
|
ro.queryOptions...)
|
||||||
|
if filter == nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get basic filter for "+
|
||||||
|
"block %d (%s)", curStamp.Height, curStamp.Hash)
|
||||||
|
}
|
||||||
|
matched, err := filter.MatchAny(builder.DeriveKey(
|
||||||
|
&curStamp.Hash), watchList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
filter = s.GetCFilter(curStamp.Hash, true,
|
||||||
|
ro.queryOptions...)
|
||||||
|
if filter == nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get extended "+
|
||||||
|
"filter for block %d (%s)",
|
||||||
|
curStamp.Height, curStamp.Hash)
|
||||||
|
}
|
||||||
|
matched, err = filter.MatchAny(builder.DeriveKey(
|
||||||
|
&curStamp.Hash), watchList)
|
||||||
|
}
|
||||||
|
// If either is matched, download the block and check to see
|
||||||
|
// what we have.
|
||||||
|
if matched {
|
||||||
|
block := s.GetBlockFromNetwork(curStamp.Hash,
|
||||||
|
ro.queryOptions...)
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't get "+
|
||||||
|
"block %d (%s)",
|
||||||
|
curStamp.Height, curStamp.Hash)
|
||||||
|
}
|
||||||
|
// If we've spent the output in this block, return an
|
||||||
|
// error stating that the output is spent.
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
for _, ti := range tx.MsgTx().TxIn {
|
||||||
|
if ti.PreviousOutPoint ==
|
||||||
|
ro.watchOutPoints[0] {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"OutPoint %s has been "+
|
||||||
|
"spent",
|
||||||
|
ro.watchOutPoints[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we found the transaction that created the output,
|
||||||
|
// then it's not spent and we can return the TxOut.
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
if *(tx.Hash()) ==
|
||||||
|
ro.watchOutPoints[0].Hash {
|
||||||
|
return tx.MsgTx().
|
||||||
|
TxOut[ro.watchOutPoints[0].
|
||||||
|
Index], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, iterate backwards until we've gone too
|
||||||
|
// far.
|
||||||
|
curStamp.Height--
|
||||||
|
if curStamp.Height < ro.startBlock.Height {
|
||||||
|
return nil, fmt.Errorf("Couldn't find "+
|
||||||
|
"transaction %s",
|
||||||
|
ro.watchOutPoints[0].Hash)
|
||||||
|
}
|
||||||
|
header, err := s.GetBlockByHeight(
|
||||||
|
uint32(curStamp.Height))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
curStamp.Hash = header.BlockHash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -433,6 +433,36 @@ func TestSetup(t *testing.T) {
|
||||||
t.Fatalf("Couldn't rescan chain for transaction %s: %s",
|
t.Fatalf("Couldn't rescan chain for transaction %s: %s",
|
||||||
tx1.TxHash(), err)
|
tx1.TxHash(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call GetUtxo for our output in tx1 to see if it's spent.
|
||||||
|
ourIndex := 1 << 30 // Should work on 32-bit systems
|
||||||
|
for i, txo := range tx1.TxOut {
|
||||||
|
if bytes.Equal(txo.PkScript, script1) {
|
||||||
|
ourIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ourOutPoint wire.OutPoint
|
||||||
|
if ourIndex != 1<<30 {
|
||||||
|
ourOutPoint = wire.OutPoint{
|
||||||
|
Hash: tx1.TxHash(),
|
||||||
|
Index: uint32(ourIndex),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Couldn't find the index of our output in transaction"+
|
||||||
|
" %s", tx1.TxHash())
|
||||||
|
}
|
||||||
|
txo, err := svc.GetUtxo(
|
||||||
|
spvchain.WatchOutPoints(ourOutPoint),
|
||||||
|
spvchain.StartBlock(&waddrmgr.BlockStamp{Height: 801}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't get UTXO %s: %s", ourOutPoint, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(txo.PkScript, script1) {
|
||||||
|
t.Fatalf("UTXO's script doesn't match expected script for %s",
|
||||||
|
ourOutPoint)
|
||||||
|
}
|
||||||
|
|
||||||
// Start a rescan with notifications in another goroutine. We'll kill
|
// Start a rescan with notifications in another goroutine. We'll kill
|
||||||
// it with a quit channel at the end and make sure we got the expected
|
// it with a quit channel at the end and make sure we got the expected
|
||||||
// results.
|
// results.
|
||||||
|
@ -642,6 +672,18 @@ func TestSetup(t *testing.T) {
|
||||||
t.Fatalf("Rescan event logs incorrect.\nWant: %s\nGot: %s\n",
|
t.Fatalf("Rescan event logs incorrect.\nWant: %s\nGot: %s\n",
|
||||||
wantLog, gotLog)
|
wantLog, gotLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check and make sure the previous UTXO is now spent.
|
||||||
|
// TODO: Uncomment this (right now it causes a deadlock.)
|
||||||
|
/*_, err = svc.GetUtxo(
|
||||||
|
spvchain.WatchOutPoints(ourOutPoint),
|
||||||
|
spvchain.StartBlock(&waddrmgr.BlockStamp{Height: 801}),
|
||||||
|
)
|
||||||
|
if err.Error() != fmt.Sprintf("OutPoint %s has been spent",
|
||||||
|
ourOutPoint) {
|
||||||
|
t.Fatalf("UTXO %s not seen as spent: %s", ourOutPoint, err)
|
||||||
|
}*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// csd does a connect-sync-disconnect between nodes in order to support
|
// csd does a connect-sync-disconnect between nodes in order to support
|
||||||
|
|
Loading…
Reference in a new issue