From 0e8af209bd02b29eef4386de37737c342ddbe665 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 24 May 2017 17:34:31 -0700 Subject: [PATCH] lnwallet/btcwallet: update BlockChainIO implementation to be backend aware --- lnwallet/btcwallet/blockchain.go | 149 ++++++++++++++++++++++--------- lnwallet/btcwallet/btcwallet.go | 53 ++++++++--- 2 files changed, 149 insertions(+), 53 deletions(-) diff --git a/lnwallet/btcwallet/blockchain.go b/lnwallet/btcwallet/blockchain.go index 051b8fe6..93f6549e 100644 --- a/lnwallet/btcwallet/blockchain.go +++ b/lnwallet/btcwallet/blockchain.go @@ -3,10 +3,17 @@ package btcwallet import ( "encoding/hex" "errors" + "fmt" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/wire" + + "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/lnwallet" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcwallet/chain" + "github.com/roasbeef/btcwallet/waddrmgr" ) var ( @@ -20,56 +27,98 @@ var ( // // This method is a part of the lnwallet.BlockChainIO interface. func (b *BtcWallet) GetBestBlock() (*chainhash.Hash, int32, error) { - return b.rpc.GetBestBlock() + switch backend := b.chain.(type) { + + case *chain.SPVChain: + header, height, err := backend.CS.LatestBlock() + if err != nil { + return nil, -1, err + } + + blockHash := header.BlockHash() + return &blockHash, int32(height), nil + + case *chain.RPCClient: + return backend.GetBestBlock() + + default: + return nil, -1, fmt.Errorf("unknown backend") + } } // GetUtxo returns the original output referenced by the passed outpoint. // // This method is a part of the lnwallet.BlockChainIO interface. -func (b *BtcWallet) GetUtxo(op *wire.OutPoint, _ uint32) (*wire.TxOut, error) { - txout, err := b.rpc.GetTxOut(&op.Hash, op.Index, false) - if err != nil { - return nil, err - } else if txout == nil { - return nil, ErrOutputSpent +func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut, error) { + switch backend := b.chain.(type) { + + case *chain.SPVChain: + spendReport, err := backend.CS.GetUtxo( + neutrino.WatchOutPoints(*op), + neutrino.StartBlock(&waddrmgr.BlockStamp{ + Height: int32(heightHint), + }), + ) + if err != nil { + return nil, err + } + + if spendReport != nil && spendReport.SpendingTx != nil { + return nil, ErrOutputSpent + } + + return spendReport.Output, nil + + case *chain.RPCClient: + txout, err := backend.GetTxOut(&op.Hash, op.Index, false) + if err != nil { + return nil, err + } else if txout == nil { + return nil, ErrOutputSpent + } + + pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex) + if err != nil { + return nil, err + } + + return &wire.TxOut{ + // Sadly, gettxout returns the output value in BTC + // instead of satoshis. + Value: int64(txout.Value * 1e8), + PkScript: pkScript, + }, nil + + default: + return nil, fmt.Errorf("unknown backend") } - - pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex) - if err != nil { - return nil, err - } - - return &wire.TxOut{ - // Sadly, gettxout returns the output value in BTC - // instead of satoshis. - Value: int64(txout.Value * 1e8), - PkScript: pkScript, - }, nil -} - -// GetTransaction returns the full transaction identified by the passed -// transaction ID. -// -// This method is a part of the lnwallet.BlockChainIO interface. -func (b *BtcWallet) GetTransaction(txid *chainhash.Hash) (*wire.MsgTx, error) { - tx, err := b.rpc.GetRawTransaction(txid) - if err != nil { - return nil, err - } - - return tx.MsgTx(), nil } // GetBlock returns a raw block from the server given its hash. // // This method is a part of the lnwallet.BlockChainIO interface. func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) { - block, err := b.rpc.GetBlock(blockHash) - if err != nil { - return nil, err - } + switch backend := b.chain.(type) { - return block, nil + case *chain.SPVChain: + block, err := backend.CS.GetBlockFromNetwork(*blockHash) + if err != nil { + return nil, err + } + + return block.MsgBlock(), nil + + case *chain.RPCClient: + block, err := backend.GetBlock(blockHash) + if err != nil { + return nil, err + } + + return block, nil + + default: + return nil, fmt.Errorf("unknown backend") + } } // GetBlockHash returns the hash of the block in the best blockchain at the @@ -77,12 +126,28 @@ func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) // // This method is a part of the lnwallet.BlockChainIO interface. func (b *BtcWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) { - blockHash, err := b.rpc.GetBlockHash(blockHeight) - if err != nil { - return nil, err - } + switch backend := b.chain.(type) { - return blockHash, nil + case *chain.SPVChain: + blockHeader, err := backend.CS.GetBlockByHeight(uint32(blockHeight)) + if err != nil { + return nil, err + } + + blockHash := blockHeader.BlockHash() + return &blockHash, nil + + case *chain.RPCClient: + blockHash, err := backend.GetBlockHash(blockHeight) + if err != nil { + return nil, err + } + + return blockHash, nil + + default: + return nil, fmt.Errorf("unknown backend") + } } // A compile time check to ensure that BtcWallet implements the BlockChainIO diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 4a3c0f35..3e06f5f5 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -614,11 +614,30 @@ func (b *BtcWallet) IsSynced() (bool, error) { // Grab the best chain state the wallet is currently aware of. syncState := b.wallet.Manager.SyncedTo() - // Next, query the btcd node to grab the info about the tip of the main - // chain. - bestHash, bestHeight, err := b.rpc.GetBestBlock() - if err != nil { - return false, err + var ( + bestHash *chainhash.Hash + bestHeight int32 + err error + ) + + // Next, query the chain backend to grab the info about the tip of the + // main chain. + switch backend := b.cfg.ChainSource.(type) { + case *chain.SPVChain: + header, height, err := backend.CS.LatestBlock() + if err != nil { + return false, err + } + + bh := header.BlockHash() + bestHash = &bh + bestHeight = int32(height) + + case *chain.RPCClient: + bestHash, bestHeight, err = backend.GetBestBlock() + if err != nil { + return false, err + } } // If the wallet hasn't yet fully synced to the node's best chain tip, @@ -628,12 +647,24 @@ func (b *BtcWallet) IsSynced() (bool, error) { } // If the wallet is on par with the current best chain tip, then we - // still may not yet be synced as the btcd node may still be catching - // up to the main chain. So we'll grab the block header in order to - // make a guess based on the current time stamp. - blockHeader, err := b.rpc.GetBlockHeader(bestHash) - if err != nil { - return false, err + // still may not yet be synced as the chain backend may still be + // catching up to the main chain. So we'll grab the block header in + // order to make a guess based on the current time stamp. + var blockHeader *wire.BlockHeader + switch backend := b.cfg.ChainSource.(type) { + + case *chain.SPVChain: + bh, _, err := backend.CS.GetBlockByHash(*bestHash) + if err != nil { + return false, err + } + blockHeader = &bh + + case *chain.RPCClient: + blockHeader, err = backend.GetBlockHeader(bestHash) + if err != nil { + return false, err + } } // If the timestamp no the best header is more than 2 hours in the