dcrd/blockchain/common.go
John C. Vernaleo 1954bf24bb Work on improving the use of analysis tools in goclean.sh
Corrected or added many comments.

Update test code to follow format go vet wants.

The code doesn't pass the checks 100% yet.  That will come in a later
commit but these all seemed related so I'd rather get them in now.
2016-02-12 15:24:32 -05:00

645 lines
17 KiB
Go

package blockchain
import (
"bytes"
"encoding/binary"
"fmt"
"sort"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
// DebugBlockHeaderString dumps a verbose message containing information about
// the block header of a block.
func DebugBlockHeaderString(chainParams *chaincfg.Params, block *dcrutil.Block) string {
bh := block.MsgBlock().Header
var buffer bytes.Buffer
str := fmt.Sprintf("Version: %v\n", bh.Version)
buffer.WriteString(str)
str = fmt.Sprintf("Previous block: %v\n", bh.PrevBlock)
buffer.WriteString(str)
str = fmt.Sprintf("Merkle root (reg): %v\n", bh.MerkleRoot)
buffer.WriteString(str)
str = fmt.Sprintf("Merkle root (stk): %v\n", bh.StakeRoot)
buffer.WriteString(str)
str = fmt.Sprintf("VoteBits: %v\n", bh.VoteBits)
buffer.WriteString(str)
str = fmt.Sprintf("FinalState: %v\n", bh.FinalState)
buffer.WriteString(str)
str = fmt.Sprintf("Voters: %v\n", bh.Voters)
buffer.WriteString(str)
str = fmt.Sprintf("FreshStake: %v\n", bh.FreshStake)
buffer.WriteString(str)
str = fmt.Sprintf("Revocations: %v\n", bh.Revocations)
buffer.WriteString(str)
str = fmt.Sprintf("PoolSize: %v\n", bh.PoolSize)
buffer.WriteString(str)
str = fmt.Sprintf("Timestamp: %v\n", bh.Timestamp)
buffer.WriteString(str)
bitsBig := CompactToBig(bh.Bits)
if bitsBig.Cmp(bigZero) != 0 {
bitsBig.Div(chainParams.PowLimit, bitsBig)
}
diff := bitsBig.Int64()
str = fmt.Sprintf("Bits: %v (Difficulty: %v)\n", bh.Bits, diff)
buffer.WriteString(str)
str = fmt.Sprintf("SBits: %v (In coins: %v)\n", bh.SBits,
float64(bh.SBits)/dcrutil.AtomsPerCoin)
buffer.WriteString(str)
str = fmt.Sprintf("Nonce: %v \n", bh.Nonce)
buffer.WriteString(str)
str = fmt.Sprintf("Height: %v \n", bh.Height)
buffer.WriteString(str)
str = fmt.Sprintf("Size: %v \n", bh.Size)
buffer.WriteString(str)
return buffer.String()
}
// DebugBlockString dumps a verbose message containing information about
// the transactions of a block.
func DebugBlockString(block *dcrutil.Block) string {
if block == nil {
return "block pointer nil"
}
var buffer bytes.Buffer
hash := block.Sha()
str := fmt.Sprintf("Block Header: %v Height: %v \n",
hash, block.Height())
buffer.WriteString(str)
str = fmt.Sprintf("Block contains %v regular transactions "+
"and %v stake transactions \n",
len(block.Transactions()),
len(block.STransactions()))
buffer.WriteString(str)
str = fmt.Sprintf("List of regular transactions \n")
buffer.WriteString(str)
for i, tx := range block.Transactions() {
str = fmt.Sprintf("Index: %v, Hash: %v \n", i, tx.Sha())
buffer.WriteString(str)
}
if len(block.STransactions()) == 0 {
return buffer.String()
}
str = fmt.Sprintf("List of stake transactions \n")
buffer.WriteString(str)
for i, stx := range block.STransactions() {
txTypeStr := ""
txType := stake.DetermineTxType(stx)
switch txType {
case stake.TxTypeSStx:
txTypeStr = "SStx"
case stake.TxTypeSSGen:
txTypeStr = "SSGen"
case stake.TxTypeSSRtx:
txTypeStr = "SSRtx"
default:
txTypeStr = "Error"
}
str = fmt.Sprintf("Index: %v, Type: %v, Hash: %v \n",
i, txTypeStr, stx.Sha())
buffer.WriteString(str)
}
return buffer.String()
}
// DebugMsgTxString dumps a verbose message containing information about the
// contents of a transaction.
func DebugMsgTxString(msgTx *wire.MsgTx) string {
tx := dcrutil.NewTx(msgTx)
isSStx, _ := stake.IsSStx(tx)
isSSGen, _ := stake.IsSSGen(tx)
var sstxType []bool
var sstxPkhs [][]byte
var sstxAmts []int64
var sstxRules [][]bool
var sstxLimits [][]uint16
if isSStx {
sstxType, sstxPkhs, sstxAmts, _, sstxRules, sstxLimits =
stake.GetSStxStakeOutputInfo(tx)
}
var buffer bytes.Buffer
hash := msgTx.TxSha()
str := fmt.Sprintf("Transaction hash: %v, Version %v, Locktime: %v, "+
"Expiry %v\n\n", hash, msgTx.Version, msgTx.LockTime, msgTx.Expiry)
buffer.WriteString(str)
str = fmt.Sprintf("==INPUTS==\nNumber of inputs: %v\n\n",
len(msgTx.TxIn))
buffer.WriteString(str)
for i, input := range msgTx.TxIn {
str = fmt.Sprintf("Input number: %v\n", i)
buffer.WriteString(str)
str = fmt.Sprintf("Previous outpoint hash: %v, ",
input.PreviousOutPoint.Hash)
buffer.WriteString(str)
str = fmt.Sprintf("Previous outpoint index: %v, ",
input.PreviousOutPoint.Index)
buffer.WriteString(str)
str = fmt.Sprintf("Previous outpoint tree: %v \n",
input.PreviousOutPoint.Tree)
buffer.WriteString(str)
str = fmt.Sprintf("Sequence: %v \n",
input.Sequence)
buffer.WriteString(str)
str = fmt.Sprintf("ValueIn: %v \n",
input.ValueIn)
buffer.WriteString(str)
str = fmt.Sprintf("BlockHeight: %v \n",
input.BlockHeight)
buffer.WriteString(str)
str = fmt.Sprintf("BlockIndex: %v \n",
input.BlockIndex)
buffer.WriteString(str)
str = fmt.Sprintf("Raw signature script: %x \n", input.SignatureScript)
buffer.WriteString(str)
sigScr, _ := txscript.DisasmString(input.SignatureScript)
str = fmt.Sprintf("Disasmed signature script: %v \n\n",
sigScr)
buffer.WriteString(str)
}
str = fmt.Sprintf("==OUTPUTS==\nNumber of outputs: %v\n\n",
len(msgTx.TxOut))
buffer.WriteString(str)
for i, output := range msgTx.TxOut {
str = fmt.Sprintf("Output number: %v\n", i)
buffer.WriteString(str)
coins := float64(output.Value) / 1e8
str = fmt.Sprintf("Output amount: %v atoms or %v coins\n", output.Value,
coins)
buffer.WriteString(str)
// SStx OP_RETURNs, dump pkhs and amts committed
if isSStx && i != 0 && i%2 == 1 {
coins := float64(sstxAmts[i/2]) / 1e8
str = fmt.Sprintf("SStx commit amount: %v atoms or %v coins\n",
sstxAmts[i/2], coins)
buffer.WriteString(str)
str = fmt.Sprintf("SStx commit address: %x\n",
sstxPkhs[i/2])
buffer.WriteString(str)
str = fmt.Sprintf("SStx address type is P2SH: %v\n",
sstxType[i/2])
buffer.WriteString(str)
str = fmt.Sprintf("SStx all address types is P2SH: %v\n",
sstxType)
buffer.WriteString(str)
str = fmt.Sprintf("Voting is fee limited: %v\n",
sstxLimits[i/2][0])
buffer.WriteString(str)
if sstxRules[i/2][0] {
str = fmt.Sprintf("Voting limit imposed: %v\n",
sstxLimits[i/2][0])
buffer.WriteString(str)
}
str = fmt.Sprintf("Revoking is fee limited: %v\n",
sstxRules[i/2][1])
buffer.WriteString(str)
if sstxRules[i/2][1] {
str = fmt.Sprintf("Voting limit imposed: %v\n",
sstxLimits[i/2][1])
buffer.WriteString(str)
}
}
// SSGen block/block height OP_RETURN.
if isSSGen && i == 0 {
blkHash, blkHeight, _ := stake.GetSSGenBlockVotedOn(tx)
str = fmt.Sprintf("SSGen block hash voted on: %v, height: %v\n",
blkHash, blkHeight)
buffer.WriteString(str)
}
if isSSGen && i == 1 {
vb := stake.GetSSGenVoteBits(tx)
str = fmt.Sprintf("SSGen vote bits: %v\n", vb)
buffer.WriteString(str)
}
str = fmt.Sprintf("Raw script: %x \n", output.PkScript)
buffer.WriteString(str)
scr, _ := txscript.DisasmString(output.PkScript)
str = fmt.Sprintf("Disasmed script: %v \n\n", scr)
buffer.WriteString(str)
}
return buffer.String()
}
// DebugTicketDataString writes the contents of a ticket data struct
// as a string.
func DebugTicketDataString(td *stake.TicketData) string {
var buffer bytes.Buffer
str := fmt.Sprintf("SStxHash: %v\n", td.SStxHash)
buffer.WriteString(str)
str = fmt.Sprintf("SpendHash: %v\n", td.SpendHash)
buffer.WriteString(str)
str = fmt.Sprintf("BlockHeight: %v\n", td.BlockHeight)
buffer.WriteString(str)
str = fmt.Sprintf("Prefix: %v\n", td.Prefix)
buffer.WriteString(str)
str = fmt.Sprintf("Missed: %v\n", td.Missed)
buffer.WriteString(str)
str = fmt.Sprintf("Expired: %v\n", td.Expired)
buffer.WriteString(str)
return buffer.String()
}
// DebugTicketDBLiveString prints out the number of tickets in each
// bucket of the ticket database as a string.
func DebugTicketDBLiveString(tmdb *stake.TicketDB, chainParams *chaincfg.Params) (string, error) {
var buffer bytes.Buffer
buffer.WriteString("\n")
for i := 0; i < stake.BucketsSize; i++ {
bucketTickets, err := tmdb.DumpLiveTickets(uint8(i))
if err != nil {
return "", err
}
str := fmt.Sprintf("%v: %v\t", i, len(bucketTickets))
buffer.WriteString(str)
// Add newlines.
if (i+1)%4 == 0 {
buffer.WriteString("\n")
}
}
return buffer.String(), nil
}
// DebugTicketDBLiveBucketString returns a string containing the ticket hashes
// found in a specific bucket of the live ticket database. If the verbose flag
// is called, it dumps the contents of the ticket data as well.
func DebugTicketDBLiveBucketString(tmdb *stake.TicketDB, bucket uint8, verbose bool) (string, error) {
var buffer bytes.Buffer
str := fmt.Sprintf("Contents of live ticket bucket %v:\n", bucket)
buffer.WriteString(str)
bucketTickets, err := tmdb.DumpLiveTickets(bucket)
if err != nil {
return "", err
}
for hash, td := range bucketTickets {
str = fmt.Sprintf("%v\n", hash)
buffer.WriteString(str)
if verbose {
str = fmt.Sprintf("%v\n", DebugTicketDataString(td))
buffer.WriteString(str)
}
}
return buffer.String(), nil
}
// DebugTicketDBSpentBucketString prints the contents of the spent tickets
// database bucket indicated to a string that is returned. If the verbose
// flag is indicated, the contents of each ticket are printed as well.
func DebugTicketDBSpentBucketString(tmdb *stake.TicketDB, height int64, verbose bool) (string, error) {
var buffer bytes.Buffer
str := fmt.Sprintf("Contents of spent ticket bucket height %v:\n", height)
buffer.WriteString(str)
bucketTickets, err := tmdb.DumpSpentTickets(height)
if err != nil {
return "", err
}
for hash, td := range bucketTickets {
missedStr := ""
if td.Missed {
missedStr = "Missed"
} else {
missedStr = "Spent"
}
str = fmt.Sprintf("%v (%v)\n", hash, missedStr)
buffer.WriteString(str)
if verbose {
str = fmt.Sprintf("%v\n", DebugTicketDataString(td))
buffer.WriteString(str)
}
}
return buffer.String(), nil
}
// DebugTicketDBMissedString prints out the contents of the missed ticket
// database to a string. If verbose is indicated, the ticket data itself
// is printed along with the ticket hashes.
func DebugTicketDBMissedString(tmdb *stake.TicketDB, verbose bool) (string, error) {
var buffer bytes.Buffer
str := fmt.Sprintf("Contents of missed ticket database:\n")
buffer.WriteString(str)
bucketTickets, err := tmdb.DumpMissedTickets()
if err != nil {
return "", err
}
for hash, td := range bucketTickets {
str = fmt.Sprintf("%v\n", hash)
buffer.WriteString(str)
if verbose {
str = fmt.Sprintf("%v\n", DebugTicketDataString(td))
buffer.WriteString(str)
}
}
return buffer.String(), nil
}
// writeTicketDataToBuf writes some ticket data into a buffer as serialized
// data.
func writeTicketDataToBuf(buf *bytes.Buffer, td *stake.TicketData) {
buf.Write(td.SStxHash[:])
buf.Write(td.SpendHash[:])
// OK for our purposes.
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(td.BlockHeight))
buf.Write(b)
buf.Write([]byte{byte(td.Prefix)})
if td.Missed {
buf.Write([]byte{0x01})
} else {
buf.Write([]byte{0x00})
}
if td.Expired {
buf.Write([]byte{0x01})
} else {
buf.Write([]byte{0x00})
}
}
// DebugTxStoreData returns a string containing information about the data
// stored in the given TxStore.
func DebugTxStoreData(txs TxStore) string {
if txs == nil {
return ""
}
var buffer bytes.Buffer
for _, txd := range txs {
str := fmt.Sprintf("Hash: %v\n", txd.Hash)
buffer.WriteString(str)
str = fmt.Sprintf("Height: %v\n", txd.BlockHeight)
buffer.WriteString(str)
str = fmt.Sprintf("Tx: %v\n", txd.Tx)
buffer.WriteString(str)
str = fmt.Sprintf("Spent: %v\n", txd.Spent)
buffer.WriteString(str)
str = fmt.Sprintf("Err: %v\n\n", txd.Err)
buffer.WriteString(str)
}
return buffer.String()
}
// TicketDbThumbprint takes all the tickets in the respective ticket db,
// sorts them, hashes their contents into a list, and then hashes that list.
// The resultant hash is the thumbprint of the ticket database, and should
// be the same across all clients that are synced to the same block. Returns
// an array of hashes len 3, containing (1) live tickets (2) spent tickets
// and (3) missed tickets.
// Do NOT use on mainnet or in production. For debug use only! Make sure
// the blockchain is frozen when you call this function.
func TicketDbThumbprint(tmdb *stake.TicketDB, chainParams *chaincfg.Params) ([]*chainhash.Hash, error) {
// Container for the three master hashes to go into.
dbThumbprints := make([]*chainhash.Hash, 3, 3)
// (1) Live tickets.
allLiveTickets := stake.NewTicketDataSliceEmpty()
for i := 0; i < stake.BucketsSize; i++ {
bucketTickets, err := tmdb.DumpLiveTickets(uint8(i))
if err != nil {
return nil, err
}
for _, td := range bucketTickets {
allLiveTickets = append(allLiveTickets, td)
}
}
// Sort by the number data hash, since we already have this implemented
// and it's also unique.
sort.Sort(allLiveTickets)
// Create a buffer, dump all the data into it, and hash.
var buf bytes.Buffer
for _, td := range allLiveTickets {
writeTicketDataToBuf(&buf, td)
}
liveHash := chainhash.HashFunc(buf.Bytes())
liveThumbprint, err := chainhash.NewHash(liveHash[:])
if err != nil {
return nil, err
}
dbThumbprints[0] = liveThumbprint
// (2) Spent tickets.
height := tmdb.GetTopBlock()
allSpentTickets := stake.NewTicketDataSliceEmpty()
for i := int64(chainParams.StakeEnabledHeight); i <= height; i++ {
bucketTickets, err := tmdb.DumpSpentTickets(i)
if err != nil {
return nil, err
}
for _, td := range bucketTickets {
allSpentTickets = append(allSpentTickets, td)
}
}
sort.Sort(allSpentTickets)
buf.Reset() // Flush buffer
for _, td := range allSpentTickets {
writeTicketDataToBuf(&buf, td)
}
spentHash := chainhash.HashFunc(buf.Bytes())
spentThumbprint, err := chainhash.NewHash(spentHash[:])
if err != nil {
return nil, err
}
dbThumbprints[1] = spentThumbprint
// (3) Missed tickets.
allMissedTickets := stake.NewTicketDataSliceEmpty()
missedTickets, err := tmdb.DumpMissedTickets()
if err != nil {
return nil, err
}
for _, td := range missedTickets {
allMissedTickets = append(allMissedTickets, td)
}
sort.Sort(allMissedTickets)
buf.Reset() // Flush buffer
missedHash := chainhash.HashFunc(buf.Bytes())
missedThumbprint, err := chainhash.NewHash(missedHash[:])
if err != nil {
return nil, err
}
dbThumbprints[2] = missedThumbprint
return dbThumbprints, nil
}
// findWhereDoubleSpent determines where a tx was previously doublespent.
// VERY INTENSIVE BLOCKCHAIN SCANNING, USE TO DEBUG SIMULATED BLOCKCHAINS
// ONLY.
func (b *BlockChain) findWhereDoubleSpent(block *dcrutil.Block) error {
height := int64(1)
heightEnd := block.Height()
hashes, err := b.db.FetchHeightRange(height, heightEnd)
if err != nil {
return err
}
var allTxs []*dcrutil.Tx
txs := block.Transactions()[1:]
stxs := block.STransactions()
allTxs = append(txs, stxs...)
for _, hash := range hashes {
curBlock, err := b.getBlockFromHash(&hash)
if err != nil {
return err
}
log.Errorf("Cur block %v", curBlock.Height())
for _, localTx := range allTxs {
for _, localTxIn := range localTx.MsgTx().TxIn {
for _, tx := range curBlock.Transactions()[1:] {
for _, txIn := range tx.MsgTx().TxIn {
if txIn.PreviousOutPoint == localTxIn.PreviousOutPoint {
log.Errorf("Double spend of {hash: %v, idx: %v,"+
" tree: %b}, previously found in tx %v "+
"of block %v txtree regular",
txIn.PreviousOutPoint.Hash,
txIn.PreviousOutPoint.Index,
txIn.PreviousOutPoint.Tree,
tx.Sha(),
hash)
}
}
}
for _, tx := range curBlock.STransactions() {
for _, txIn := range tx.MsgTx().TxIn {
if txIn.PreviousOutPoint == localTxIn.PreviousOutPoint {
log.Errorf("Double spend of {hash: %v, idx: %v,"+
" tree: %b}, previously found in tx %v "+
"of block %v txtree stake\n",
txIn.PreviousOutPoint.Hash,
txIn.PreviousOutPoint.Index,
txIn.PreviousOutPoint.Tree,
tx.Sha(),
hash)
}
}
}
}
}
}
for _, localTx := range stxs {
for _, localTxIn := range localTx.MsgTx().TxIn {
for _, tx := range txs {
for _, txIn := range tx.MsgTx().TxIn {
if txIn.PreviousOutPoint == localTxIn.PreviousOutPoint {
log.Errorf("Double spend of {hash: %v, idx: %v,"+
" tree: %b}, previously found in tx %v "+
"of cur block stake txtree\n",
txIn.PreviousOutPoint.Hash,
txIn.PreviousOutPoint.Index,
txIn.PreviousOutPoint.Tree,
tx.Sha())
}
}
}
}
}
return nil
}