Skip to content

Commit

Permalink
blockchain/standalone: Implement a new module.
Browse files Browse the repository at this point in the history
This implements a new module named blockchain/standalone which aims to
provide several of the standalone functions currently available in
blockchain and ultimately replace them in the next major version.

The primary goal of offering these functions via a separate module is to
reduce the required dependencies to a minimum as compared to the
blockchain module.  It will be ideal for applications such as
lightweight clients that need to ensure basic security properties hold
and calculate appropriate vote subsidies and block explorers.

For example, some things an SPV wallet needs to prove are that the block
headers all connect together, that they satisfy the proof of work
requirements, and that a given transaction tree is valid for a given
header.

The new module currently only provides functions related to proof of
work, however, future commits will provide functions for merkle root
calculation, subsidy calculation, and coinbase transaction
identification.  It is being done in stages to help ease review since it
is consensus critical code.

Finally, it also includes comprehensive tests, full package
documentation, and basic usage examples.
  • Loading branch information
davecgh committed Aug 6, 2019
1 parent cf97a27 commit 2c7d204
Show file tree
Hide file tree
Showing 9 changed files with 918 additions and 0 deletions.
57 changes: 57 additions & 0 deletions blockchain/standalone/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
standalone
==========

[![Build Status](https://img.shields.io/travis/decred/dcrd.svg)](https://travis-ci.org/decred/dcrd)
[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/decred/dcrd/blockchain/standalone)

Package standalone provides standalone functions useful for working with the
Decred blockchain consensus rules.

The primary goal of offering these functions via a separate module is to reduce
the required dependencies to a minimum as compared to the blockchain module.

It is ideal for applications such as lightweight clients that need to ensure
basic security properties hold and calculate appropriate vote subsidies and
block explorers.

For example, some things an SPV wallet needs to prove are that the block headers
all connect together, that they satisfy the proof of work requirements, and that
a given transaction tree is valid for a given header.

The provided functions fall into the following categories:

- Proof-of-work
- Converting to and from the compact target difficulty representation
- Calculating work values based on the compact target difficulty
- Checking a block hash satisfies a target difficulty and that target
difficulty is within a valid range
- Merkle root calculation (WIP - not yet available)
- Subsidy calculation (WIP - not yet available)
- Coinbase transaction identification (WIP - not yet available)

## Installation and Updating

```bash
$ go get -u github.com/decred/dcrd/blockchain/standalone
```

## Examples

* [CompactToBig Example](https://godoc.org/github.com/decred/dcrd/blockchain/standalone#example-CompactToBig)
Demonstrates how to convert the compact "bits" in a block header which
represent the target difficulty to a big integer and display it using the
typical hex notation.

* [BigToCompact Example](https://godoc.org/github.com/decred/dcrd/blockchain/standalone#example-BigToCompact)
Demonstrates how to convert a target difficulty into the compact "bits" in a
block header which represent that target difficulty.

* [CheckProofOfWork Example](https://godoc.org/github.com/decred/dcrd/blockchain/standalone#example-CheckProofOfWork)
Demonstrates checking the proof of work of a block hash against
a target difficulty.

## License

Package standalone is licensed under the [copyfree](http://copyfree.org) ISC
License.
39 changes: 39 additions & 0 deletions blockchain/standalone/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

/*
Package standalone provides standalone functions useful for working with the
Decred blockchain consensus rules.
The primary goal of offering these functions via a separate module is to reduce
the required dependencies to a minimum as compared to the blockchain module.
It is ideal for applications such as lightweight clients that need to ensure
basic security properties hold and calculate appropriate vote subsidies and
block explorers.
For example, some things an SPV wallet needs to prove are that the block headers
all connect together, that they satisfy the proof of work requirements, and that
a given transaction tree is valid for a given header.
The provided functions fall into the following categories:
- Proof-of-work
- Converting to and from the compact target difficulty representation
- Calculating work values based on the compact target difficulty
- Checking a block hash satisfies a target difficulty and that target
difficulty is within a valid range
- Merkle root calculation (WIP - not yet available)
- Subsidy calculation (WIP - not yet available)
- Coinbase transaction identification (WIP - not yet available)
Errors
Errors returned by this package are of type standalone.RuleError. This allows
the caller to differentiate between errors further up the call stack through
type assertions. In addition, callers can programmatically determine the
specific rule violation by examining the ErrorCode field of the type asserted
standalone.RuleError.
*/
package standalone
66 changes: 66 additions & 0 deletions blockchain/standalone/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package standalone

import (
"fmt"
)

// ErrorCode identifies a kind of error.
type ErrorCode int

// These constants are used to identify a specific RuleError.
const (
// ErrUnexpectedDifficulty indicates specified bits do not align with
// the expected value either because it doesn't match the calculated
// value based on difficulty rules or it is out of the valid range.
ErrUnexpectedDifficulty ErrorCode = iota

// ErrHighHash indicates the block does not hash to a value which is
// lower than the required target difficultly.
ErrHighHash

// numErrorCodes is the maximum error code number used in tests.
numErrorCodes
)

// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrUnexpectedDifficulty: "ErrUnexpectedDifficulty",
ErrHighHash: "ErrHighHash",
}

// String returns the ErrorCode as a human-readable name.
func (e ErrorCode) String() string {
if s := errorCodeStrings[e]; s != "" {
return s
}
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
}

// RuleError identifies a rule violation. The caller can use type assertions to
// determine if a failure was specifically due to a rule violation and access
// the ErrorCode field to ascertain the specific reason for the rule violation.
type RuleError struct {
ErrorCode ErrorCode // Describes the kind of error
Description string // Human readable description of the issue
}

// Error satisfies the error interface and prints human-readable errors.
func (e RuleError) Error() string {
return e.Description
}

// ruleError creates an RuleError given a set of arguments.
func ruleError(c ErrorCode, desc string) RuleError {
return RuleError{ErrorCode: c, Description: desc}
}

// IsErrorCode returns whether or not the provided error is a rule error with
// the provided error code.
func IsErrorCode(err error, c ErrorCode) bool {
e, ok := err.(RuleError)
return ok && e.ErrorCode == c
}
103 changes: 103 additions & 0 deletions blockchain/standalone/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package standalone

import (
"testing"
)

// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
func TestErrorCodeStringer(t *testing.T) {
tests := []struct {
in ErrorCode
want string
}{
{ErrUnexpectedDifficulty, "ErrUnexpectedDifficulty"},
{ErrHighHash, "ErrHighHash"},
{0xffff, "Unknown ErrorCode (65535)"},
}

// Detect additional error codes that don't have the stringer added.
if len(tests)-1 != int(numErrorCodes) {
t.Errorf("It appears an error code was added without adding an " +
"associated stringer test")
}

t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.String()
if result != test.want {
t.Errorf("String #%d\n got: %s want: %s", i, result, test.want)
continue
}
}
}

// TestRuleError tests the error output for the RuleError type.
func TestRuleError(t *testing.T) {
tests := []struct {
in RuleError
want string
}{{
RuleError{Description: "duplicate block"},
"duplicate block",
}, {
RuleError{Description: "human-readable error"},
"human-readable error",
},
}

t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("Error #%d\n got: %s want: %s", i, result, test.want)
continue
}
}
}

// TestIsErrorCode ensures IsErrorCode works as intended.
func TestIsErrorCode(t *testing.T) {
tests := []struct {
name string
err error
code ErrorCode
want bool
}{{
name: "ErrUnexpectedDifficulty testing for ErrUnexpectedDifficulty",
err: ruleError(ErrUnexpectedDifficulty, ""),
code: ErrUnexpectedDifficulty,
want: true,
}, {
name: "ErrHighHash testing for ErrHighHash",
err: ruleError(ErrHighHash, ""),
code: ErrHighHash,
want: true,
}, {
name: "ErrHighHash error testing for ErrUnexpectedDifficulty",
err: ruleError(ErrHighHash, ""),
code: ErrUnexpectedDifficulty,
want: false,
}, {
name: "ErrHighHash error testing for unknown error code",
err: ruleError(ErrHighHash, ""),
code: 0xffff,
want: false,
}, {
name: "nil error testing for ErrUnexpectedDifficulty",
err: nil,
code: ErrUnexpectedDifficulty,
want: false,
}}
for _, test := range tests {
result := IsErrorCode(test.err, test.code)
if result != test.want {
t.Errorf("%s: unexpected result -- got: %v want: %v", test.name,
result, test.want)
continue
}
}
}
78 changes: 78 additions & 0 deletions blockchain/standalone/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2014-2016 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package standalone_test

import (
"fmt"
"math/big"

"github.com/decred/dcrd/blockchain/standalone"
"github.com/decred/dcrd/chaincfg/chainhash"
)

// This example demonstrates how to convert the compact "bits" in a block header
// which represent the target difficulty to a big integer and display it using
// the typical hex notation.
func ExampleCompactToBig() {
// Convert the bits from block 1 in the main chain.
bits := uint32(453115903)
targetDifficulty := standalone.CompactToBig(bits)

// Display it in hex.
fmt.Printf("%064x\n", targetDifficulty.Bytes())

// Output:
// 000000000001ffff000000000000000000000000000000000000000000000000
}

// This example demonstrates how to convert a target difficulty into the compact
// "bits" in a block header which represent that target difficulty.
func ExampleBigToCompact() {
// Convert the target difficulty from block 1 in the main chain to compact
// form.
t := "000000000001ffff000000000000000000000000000000000000000000000000"
targetDifficulty, success := new(big.Int).SetString(t, 16)
if !success {
fmt.Println("invalid target difficulty")
return
}
bits := standalone.BigToCompact(targetDifficulty)

fmt.Println(bits)

// Output:
// 453115903
}

// This example demonstrates checking the proof of work of a block hash against
// a target difficulty.
func ExampleCheckProofOfWork() {
// This is the pow limit for mainnet and would ordinarily come from chaincfg
// params, however, it is hard coded here for the purposes of the example.
l := "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
powLimit, success := new(big.Int).SetString(l, 16)
if !success {
fmt.Println("invalid pow limit")
return
}

// Check the proof of work for block 1 in the main chain.
h := "000000000000437482b6d47f82f374cde539440ddb108b0a76886f0d87d126b9"
hash, err := chainhash.NewHashFromStr(h)
if err != nil {
fmt.Printf("failed to parse hash: %v\n", err)
return
}
bits := uint32(453115903)

if err := standalone.CheckProofOfWork(hash, bits, powLimit); err != nil {
fmt.Printf("proof of work check failed: %v\n", err)
return
}

// Output:
//
}
5 changes: 5 additions & 0 deletions blockchain/standalone/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/decred/dcrd/blockchain/standalone

go 1.11

require github.com/decred/dcrd/chaincfg/chainhash v1.0.1
4 changes: 4 additions & 0 deletions blockchain/standalone/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/dchest/blake256 v1.0.0 h1:6gUgI5MHdz9g0TdrgKqXsoDX+Zjxmm1Sc6OsoGru50I=
github.com/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY=
github.com/decred/dcrd/chaincfg/chainhash v1.0.1 h1:0vG7U9+dSjSCaHQKdoSKURK2pOb47+b+8FK5q4+Je7M=
github.com/decred/dcrd/chaincfg/chainhash v1.0.1/go.mod h1:OVfvaOsNLS/A1y4Eod0Ip/Lf8qga7VXCQjUQLbkY0Go=
Loading

0 comments on commit 2c7d204

Please sign in to comment.