Skip to content

Commit

Permalink
add block monitor test. various touchups
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed Aug 31, 2019
1 parent b6cb5df commit cd26107
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 24 deletions.
5 changes: 1 addition & 4 deletions server/asset/dcr/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import (
)

// The dcrBlock structure should hold a minimal amount of information about a
// block needed to verify UTXO validity. The stake related transaction hashes
// are stored for quick tx tree checks. More sophisticated methods such as
// examining the pubkey scripts may be used in the future to make this structure
// even smaller.
// block needed to verify UTXO validity.
type dcrBlock struct {
hash chainhash.Hash
height int64
Expand Down
2 changes: 1 addition & 1 deletion server/asset/dcr/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var (
// DCRConfig is passed to the constructor.
type DCRConfig struct {
// Net should one of "mainnet", "testnet3", "simnet". Any other value will
// cause an tidyConfig error.
// cause a tidyConfig error.
Net string
// DcrdUser is the RPC username provided to dcrd configuration as the rpcuser
// parameter.
Expand Down
17 changes: 11 additions & 6 deletions server/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type dcrNode interface {
GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
}

// dcrBackend is an asset backend for Decred. It has utilities for fetching UTXO
// dcrBackend is an asset backend for Decred. It has methods for fetching UTXO
// information and subscribing to block updates. It maintains a cache of block
// data for quick lookups. dcrBackend implements asset.DEXAsset, so provides
// exported methods for DEX-related blockchain info.
Expand Down Expand Up @@ -127,6 +127,8 @@ func (dcr *dcrBackend) UTXO(txid string, vout uint32, redeemScript []byte) (asse
return dcr.utxo(txHash, vout, redeemScript)
}

// Transaction is part of the asset.DEXTx interface, so includes methods for
// checking spent utxos and validating swap contracts.
func (dcr *dcrBackend) Transaction(txid string) (asset.DEXTx, error) {
txHash, err := chainhash.NewHashFromStr(txid)
if err != nil {
Expand Down Expand Up @@ -270,7 +272,8 @@ func (dcr *dcrBackend) onBlockConnected(serializedHeader []byte, _ [][]byte) {
log.Errorf("error decoding serialized header: %v", err)
return
}
dcr.anyQ <- blockHeader.BlockHash()
h := blockHeader.BlockHash()
dcr.anyQ <- &h
}

// Get the UTXO, populating the block data along the way. Only spendable UTXOs
Expand Down Expand Up @@ -303,8 +306,7 @@ func (dcr *dcrBackend) utxo(txHash *chainhash.Hash, vout uint32, redeemScript []
return nil, fmt.Errorf("error parsing utxo script addresses")
}

// Most supported script types are P2PKH, with or without a leading stake-tree
// byte, e.g. OP_SSGEN, OP_SSRTX
// Most supported script types are P2PKH.
sigScriptSize := P2PKHSigScriptSize
// If it's a P2SH, the size must be calculated based on other factors.
if scriptType.isP2SH() {
Expand Down Expand Up @@ -355,7 +357,10 @@ func (dcr *dcrBackend) utxo(txHash *chainhash.Hash, vout uint32, redeemScript []
pkScript: pkScript,
redeemScript: redeemScript,
numSigs: scriptAddrs.nRequired,
size: uint32(sigScriptSize),
// The total size associated with the wire.TxIn. See
// (wire.TxIn).SerializeSizeWitness and
// and (wire.TxIn).SerializeSizePrefix
spendSize: uint32(sigScriptSize) + txInOverhead,
}, nil
}

Expand Down Expand Up @@ -442,7 +447,7 @@ func (dcr *dcrBackend) VerifySignature(msg, pkBytes, sigBytes []byte) bool {
}

// connectNodeRPC attempts to create a new websocket connection to a dcrd node
// with the given credentials and optional notification handlers.
// with the given credentials and notification handlers.
func connectNodeRPC(host, user, pass, cert string,
notifications *rpcclient.NotificationHandlers) (*rpcclient.Client, error) {

Expand Down
21 changes: 12 additions & 9 deletions server/asset/dcr/dcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ func TestTidyConfig(t *testing.T) {
}
}

// The remaining tests use the testBlockchain which, is a stub for
// The remaining tests use the testBlockchain which is a stub for
// rpcclient.Client. UTXOs, transactions and blocks are added to the blockchain
// as jsonrpc types to be requested by the dcrBackend.
//
// General formula for testing
// 1. Create a dcrBackend with the node field set to a dcrNode-implementing stub
// 2. Create a fake UTXO and all of the necessary jsonrpc-type blocks and
// 1. Create a dcrBackend with the node field set to a testNode
// 2. Create a fake UTXO and all of the associated jsonrpc-type blocks and
// transactions and add the to the test blockchain.
// 3. Verify the dcrBackend and UTXO methods are returning whatever is expected.
// 4. Optionally add more blocks and/or transactions to the blockchain and check
Expand All @@ -124,7 +124,7 @@ func randomHash() *chainhash.Hash {
return hash
}

// A fake blockchain to be used for RPC calls by the dcrNode.
// A fake "blockchain" to be used for RPC calls by the dcrNode.
type testBlockChain struct {
txOuts map[string]*chainjson.GetTxOutResult
txRaws map[chainhash.Hash]*chainjson.TxRawResult
Expand All @@ -136,7 +136,8 @@ type testBlockChain struct {
// node stub to request.
var testChain testBlockChain

// This must be called before using the testNode.
// This must be called before using the testNode, and should be called
// in-between independent tests.
func cleanTestChain() {
testChain = testBlockChain{
txOuts: make(map[string]*chainjson.GetTxOutResult),
Expand Down Expand Up @@ -303,7 +304,7 @@ func genPubkey() ([]byte, []byte) {

// A pay-to-script-hash pubkey script.
func newP2PKHScript() ([]byte, []byte, []byte) {
script := make([]byte, 0, SwapContractSize)
script := make([]byte, 0, P2PKHSigScriptSize)
script = addHex(script, "76a914")
pubkey, pkHash := genPubkey()
script = append(script, pkHash...)
Expand Down Expand Up @@ -335,6 +336,7 @@ func testMsgTxRegular() *testMsgTx {
}
}

// Information about a swap contract.
type testMsgTxSwap struct {
tx *wire.MsgTx
vout uint32
Expand All @@ -345,7 +347,7 @@ type testMsgTxSwap struct {
// Create a swap (initialization) contract with random pubkeys and return the
// pubkey script and addresses.
func testSwapContract() ([]byte, dcrutil.Address) {
contract := make([]byte, 0, SwapContractSize)
contract := make([]byte, 0, 25)
contract = addHex(contract, "6382012088c020") // This snippet checks the size of the secret and hashes it.
// hashed secret
secretKey := randomBytes(32)
Expand All @@ -362,6 +364,7 @@ func testSwapContract() ([]byte, dcrutil.Address) {
return contract, receiverAddr
}

// Create a transaction with a P2SH swap output at vout 0.
func testMsgTxSwapInit() *testMsgTxSwap {
msgTx := wire.NewMsgTx()
contract, recipient := testSwapContract()
Expand Down Expand Up @@ -410,6 +413,7 @@ func testMsgTxVote() *testMsgTx {
}
}

// Information about a transaction with a P2SH output.
type testMsgTxP2SH struct {
tx *wire.MsgTx
pubkeys [][]byte
Expand Down Expand Up @@ -558,7 +562,7 @@ func TestUTXOs(t *testing.T) {
}
// While we're here, check the spend script size is correct.
scriptSize := utxo.ScriptSize()
if scriptSize != P2PKHSigScriptSize {
if scriptSize != P2PKHSigScriptSize+txInOverhead {
t.Fatalf("case 1 - unexpected spend script size reported. expected %d, got %d", P2PKHSigScriptSize, scriptSize)
}
// Now "mine" the transaction.
Expand Down Expand Up @@ -1136,5 +1140,4 @@ func TestAuxiliary(t *testing.T) {
if utxo.TxID() != txid {
t.Fatalf("utxo txid doesn't match")
}

}
43 changes: 42 additions & 1 deletion server/asset/dcr/live_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// +build dcrlive
//
// These tests can be run by using the -tags flag.
// Since at least one live test runs for an hour, you should run live tests
// individually using the -run flag. All of these tests will only run with the
// 'dcrlive' build tag, specified with the -tags flag.
//
// go test -v -tags dcrlive -run LiveUTXO
// -----------------------------------
Expand All @@ -12,6 +14,11 @@
// go test -v -tags dcrlive -run CacheAdvantage
// -----------------------------------------
// Check the difference between using the block cache and requesting via RPC.
//
// go test -v -tags dcrlive -run BlockMonitor -timeout 61m
// ------------------------------------------
// Monitor the block chain for a while and make sure that the block cache is
// updating appropriately.

package dcr

Expand Down Expand Up @@ -318,3 +325,37 @@ func TestCacheAdvantage(t *testing.T) {
}
t.Logf("%d cached blocks retreived in %.3f ms", numBlocks, float64(time.Since(start).Nanoseconds())/1e6)
}

// TestBlockMonitor is a live test that connects to dcrd and listens for block
// updates, checking the state of the cache along the way.
func TestBlockMonitor(t *testing.T) {
testDuration := 60 * time.Minute
fmt.Printf("Starting BlockMonitor test. Test will last for %d minutes\n", int(testDuration.Minutes()))
blockChan := dcr.BlockChannel(5)
expire := time.NewTimer(testDuration).C
lastHeight := dcr.blockCache.tipHeight()
out:
for {
select {
case height := <-blockChan:
if height > lastHeight {
t.Logf("block received for height %d", height)
} else {
reorgDepth := lastHeight - height + 1
t.Logf("block received for block %d causes a %d block reorg", height, reorgDepth)
}
tipHeight := dcr.blockCache.tipHeight()
if tipHeight != height {
t.Fatalf("unexpected height after block notification. expected %d, received %d", height, tipHeight)
}
_, err := dcr.getMainchainDcrBlock(height)
if err != nil {
t.Fatalf("error getting newly connected block at height %d", height)
}
case <-dcr.ctx.Done():
break out
case <-expire:
break out
}
}
}
2 changes: 2 additions & 0 deletions server/asset/dcr/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const (
P2PKHSigScriptSize = 1 + 73 + 1 + 33
// All pubkey scripts are assumed to be version 0.
currentScriptVersion = 0
// Overhead for a wire.TxIn with a script length < 254.
txInOverhead = 58
)

// parseScriptType creates a dcrScriptType bitmap for the script type. A script
Expand Down
2 changes: 1 addition & 1 deletion server/asset/dcr/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (tx *Tx) Confirmations() (int64, error) {
mainchainBlock = nil
} else {
mainchainBlock, found = tx.dcr.blockCache.atHeight(uint32(tx.height))
if !found {
if !found || mainchainBlock.hash != tx.blockHash {
return -1, fmt.Errorf("new block not found for utxo moved from orphaned block")
}
}
Expand Down
4 changes: 2 additions & 2 deletions server/asset/dcr/utxo.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type UTXO struct {
pkScript []byte
redeemScript []byte
numSigs int
size uint32
spendSize uint32
}

// Check that UTXO satisfies the asset.UTXO interface
Expand Down Expand Up @@ -156,7 +156,7 @@ func countMatches(pubkeys [][]byte, addrs []dcrutil.Address, hasher func([]byte)
// ScriptSize returns the maximum spend script size of the UTXO, in bytes.
// This is a method of the asset.UTXO interface.
func (utxo *UTXO) ScriptSize() uint32 {
return utxo.size
return utxo.spendSize
}

// TxHash is the transaction hash. TxHash is a method of the asset.UTXO
Expand Down

0 comments on commit cd26107

Please sign in to comment.