Browse Source

Merge pull request #588 from tendermint/comments_cleanup

Comments and cleanup
pull/583/merge
Ethan Buchman 8 years ago
committed by GitHub
parent
commit
695ad5fe2d
7 changed files with 128 additions and 95 deletions
  1. +25
    -14
      mempool/mempool.go
  2. +16
    -6
      mempool/reactor.go
  3. +33
    -0
      rpc/grpc/grpc_test.go
  4. +0
    -18
      rpc/test/grpc_test.go
  5. +0
    -36
      rpc/test/main_test.go
  6. +4
    -4
      scripts/txs/random.sh
  7. +50
    -17
      types/block.go

+ 25
- 14
mempool/mempool.go View File

@ -50,6 +50,9 @@ TODO: Better handle abci client errors. (make it automatically handle connection
const cacheSize = 100000
// Mempool is an ordered in-memory pool for transactions before they are proposed in a consensus round.
// Transaction validity is checked using the CheckTx abci message before the transaction is added to the pool.
// The Mempool uses a concurrent list structure for storing transactions that can be efficiently accessed by multiple concurrent readers.
type Mempool struct {
config *cfg.MempoolConfig
@ -72,6 +75,7 @@ type Mempool struct {
logger log.Logger
}
// NewMempool returns a new Mempool with the given configuration and connection to an application.
func NewMempool(config *cfg.MempoolConfig, proxyAppConn proxy.AppConnMempool) *Mempool {
mempool := &Mempool{
config: config,
@ -90,7 +94,7 @@ func NewMempool(config *cfg.MempoolConfig, proxyAppConn proxy.AppConnMempool) *M
return mempool
}
// SetLogger allows you to set your own Logger.
// SetLogger sets the Logger.
func (mem *Mempool) SetLogger(l log.Logger) {
mem.logger = l
}
@ -110,21 +114,22 @@ func (mem *Mempool) initWAL() {
}
}
// consensus must be able to hold lock to safely update
// Lock locks the mempool. The consensus must be able to hold lock to safely update.
func (mem *Mempool) Lock() {
mem.proxyMtx.Lock()
}
// Unlock unlocks the mempool.
func (mem *Mempool) Unlock() {
mem.proxyMtx.Unlock()
}
// Number of transactions in the mempool clist
// Size returns the number of transactions in the mempool.
func (mem *Mempool) Size() int {
return mem.txs.Len()
}
// Remove all transactions from mempool and cache
// Flush removes all transactions from the mempool and cache
func (mem *Mempool) Flush() {
mem.proxyMtx.Lock()
defer mem.proxyMtx.Unlock()
@ -137,14 +142,15 @@ func (mem *Mempool) Flush() {
}
}
// Return the first element of mem.txs for peer goroutines to call .NextWait() on.
// Blocks until txs has elements.
// TxsFrontWait returns the first transaction in the ordered list for peer goroutines to call .NextWait() on.
// It blocks until the mempool is not empty (ie. until the internal `mem.txs` has at least one element)
func (mem *Mempool) TxsFrontWait() *clist.CElement {
return mem.txs.FrontWait()
}
// Try a new transaction in the mempool.
// Potentially blocking if we're blocking on Update() or Reap().
// CheckTx executes a new transaction against the application to determine its validity
// and whether it should be added to the mempool.
// It blocks if we're waiting on Update() or Reap().
// cb: A callback from the CheckTx command.
// It gets called from another goroutine.
// CONTRACT: Either cb will get called, or err returned.
@ -256,8 +262,8 @@ func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) {
}
}
// Get the valid transactions remaining
// If maxTxs is -1, there is no cap on returned transactions.
// Reap returns a list of transactions currently in the mempool.
// If maxTxs is -1, there is no cap on the number of returned transactions.
func (mem *Mempool) Reap(maxTxs int) types.Txs {
mem.proxyMtx.Lock()
defer mem.proxyMtx.Unlock()
@ -286,8 +292,7 @@ func (mem *Mempool) collectTxs(maxTxs int) types.Txs {
return txs
}
// Tell mempool that these txs were committed.
// Mempool will discard these txs.
// Update informs the mempool that the given txs were committed and can be discarded.
// NOTE: this should be called *after* block is committed by consensus.
// NOTE: unsafe; Lock/Unlock must be managed by caller
func (mem *Mempool) Update(height int, txs types.Txs) {
@ -354,19 +359,21 @@ func (mem *Mempool) recheckTxs(goodTxs []types.Tx) {
//--------------------------------------------------------------------------------
// A transaction that successfully ran
// mempoolTx is a transaction that successfully ran
type mempoolTx struct {
counter int64 // a simple incrementing counter
height int64 // height that this tx had been validated in
tx types.Tx //
}
// Height returns the height for this transaction
func (memTx *mempoolTx) Height() int {
return int(atomic.LoadInt64(&memTx.height))
}
//--------------------------------------------------------------------------------
// txCache maintains a cache of transactions.
type txCache struct {
mtx sync.Mutex
size int
@ -374,6 +381,7 @@ type txCache struct {
list *list.List // to remove oldest tx when cache gets too big
}
// newTxCache returns a new txCache.
func newTxCache(cacheSize int) *txCache {
return &txCache{
size: cacheSize,
@ -382,6 +390,7 @@ func newTxCache(cacheSize int) *txCache {
}
}
// Reset resets the txCache to empty.
func (cache *txCache) Reset() {
cache.mtx.Lock()
cache.map_ = make(map[string]struct{}, cacheSize)
@ -389,6 +398,7 @@ func (cache *txCache) Reset() {
cache.mtx.Unlock()
}
// Exists returns true if the given tx is cached.
func (cache *txCache) Exists(tx types.Tx) bool {
cache.mtx.Lock()
_, exists := cache.map_[string(tx)]
@ -396,7 +406,7 @@ func (cache *txCache) Exists(tx types.Tx) bool {
return exists
}
// Returns false if tx is in cache.
// Push adds the given tx to the txCache. It returns false if tx is already in the cache.
func (cache *txCache) Push(tx types.Tx) bool {
cache.mtx.Lock()
defer cache.mtx.Unlock()
@ -418,6 +428,7 @@ func (cache *txCache) Push(tx types.Tx) bool {
return true
}
// Remove removes the given tx from the cache.
func (cache *txCache) Remove(tx types.Tx) {
cache.mtx.Lock()
delete(cache.map_, string(tx))


+ 16
- 6
mempool/reactor.go View File

@ -30,6 +30,7 @@ type MempoolReactor struct {
evsw types.EventSwitch
}
// NewMempoolReactor returns a new MempoolReactor with the given config and mempool.
func NewMempoolReactor(config *cfg.MempoolConfig, mempool *Mempool) *MempoolReactor {
memR := &MempoolReactor{
config: config,
@ -39,7 +40,8 @@ func NewMempoolReactor(config *cfg.MempoolConfig, mempool *Mempool) *MempoolReac
return memR
}
// Implements Reactor
// GetChannels implements Reactor.
// It returns the list of channels for this reactor.
func (memR *MempoolReactor) GetChannels() []*p2p.ChannelDescriptor {
return []*p2p.ChannelDescriptor{
&p2p.ChannelDescriptor{
@ -49,17 +51,19 @@ func (memR *MempoolReactor) GetChannels() []*p2p.ChannelDescriptor {
}
}
// Implements Reactor
// AddPeer implements Reactor.
// It starts a broadcast routine ensuring all txs are forwarded to the given peer.
func (memR *MempoolReactor) AddPeer(peer *p2p.Peer) {
go memR.broadcastTxRoutine(peer)
}
// Implements Reactor
// RemovePeer implements Reactor.
func (memR *MempoolReactor) RemovePeer(peer *p2p.Peer, reason interface{}) {
// broadcast routine checks if peer is gone and returns
}
// Implements Reactor
// Receive implements Reactor.
// It adds any received transactions to the mempool.
func (memR *MempoolReactor) Receive(chID byte, src *p2p.Peer, msgBytes []byte) {
_, msg, err := DecodeMessage(msgBytes)
if err != nil {
@ -84,15 +88,17 @@ func (memR *MempoolReactor) Receive(chID byte, src *p2p.Peer, msgBytes []byte) {
}
}
// Just an alias for CheckTx since broadcasting happens in peer routines
// BroadcastTx is an alias for Mempool.CheckTx. Broadcasting itself happens in peer routines.
func (memR *MempoolReactor) BroadcastTx(tx types.Tx, cb func(*abci.Response)) error {
return memR.Mempool.CheckTx(tx, cb)
}
// PeerState describes the state of a peer.
type PeerState interface {
GetHeight() int
}
// Peer describes a peer.
type Peer interface {
IsRunning() bool
Send(byte, interface{}) bool
@ -141,7 +147,7 @@ func (memR *MempoolReactor) broadcastTxRoutine(peer Peer) {
}
}
// implements events.Eventable
// SetEventSwitch implements events.Eventable.
func (memR *MempoolReactor) SetEventSwitch(evsw types.EventSwitch) {
memR.evsw = evsw
}
@ -153,6 +159,7 @@ const (
msgTypeTx = byte(0x01)
)
// MempoolMessage is a message sent or received by the MempoolReactor.
type MempoolMessage interface{}
var _ = wire.RegisterInterface(
@ -160,6 +167,7 @@ var _ = wire.RegisterInterface(
wire.ConcreteType{&TxMessage{}, msgTypeTx},
)
// DecodeMessage decodes a byte-array into a MempoolMessage.
func DecodeMessage(bz []byte) (msgType byte, msg MempoolMessage, err error) {
msgType = bz[0]
n := new(int)
@ -170,10 +178,12 @@ func DecodeMessage(bz []byte) (msgType byte, msg MempoolMessage, err error) {
//-------------------------------------
// TxMessage is a MempoolMessage containing a transaction.
type TxMessage struct {
Tx types.Tx
}
// String returns a string representation of the TxMessage.
func (m *TxMessage) String() string {
return fmt.Sprintf("[TxMessage %v]", m.Tx)
}

+ 33
- 0
rpc/grpc/grpc_test.go View File

@ -0,0 +1,33 @@
package core_grpc_test
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"github.com/tendermint/abci/example/dummy"
"github.com/tendermint/tendermint/rpc/grpc"
"github.com/tendermint/tendermint/rpc/test"
)
func TestMain(m *testing.M) {
// start a tendermint node (and merkleeyes) in the background to test against
app := dummy.NewDummyApplication()
node := rpctest.StartTendermint(app)
code := m.Run()
// and shut down proper at the end
node.Stop()
node.Wait()
os.Exit(code)
}
func TestBroadcastTx(t *testing.T) {
require := require.New(t)
res, err := rpctest.GetGRPCClient().BroadcastTx(context.Background(), &core_grpc.RequestBroadcastTx{[]byte("this is a tx")})
require.Nil(err, "%+v", err)
require.EqualValues(0, res.CheckTx.Code)
require.EqualValues(0, res.DeliverTx.Code)
}

+ 0
- 18
rpc/test/grpc_test.go View File

@ -1,18 +0,0 @@
package rpctest
import (
"testing"
"golang.org/x/net/context"
"github.com/stretchr/testify/require"
core_grpc "github.com/tendermint/tendermint/rpc/grpc"
)
func TestBroadcastTx(t *testing.T) {
require := require.New(t)
res, err := GetGRPCClient().BroadcastTx(context.Background(), &core_grpc.RequestBroadcastTx{[]byte("this is a tx")})
require.Nil(err, "%+v", err)
require.EqualValues(0, res.CheckTx.Code)
require.EqualValues(0, res.DeliverTx.Code)
}

+ 0
- 36
rpc/test/main_test.go View File

@ -1,36 +0,0 @@
/*
package tests contain integration tests and helper functions for testing
the RPC interface
In particular, it allows us to spin up a tendermint node in process, with
a live RPC server, which we can use to verify our rpc calls. It provides
all data structures, enabling us to do more complex tests (like node_test.go)
that introspect the blocks themselves to validate signatures and the like.
It currently only spins up one node, it would be interesting to expand it
to multiple nodes to see the real effects of validating partially signed
blocks.
*/
package rpctest
import (
"os"
"testing"
"github.com/tendermint/abci/example/dummy"
nm "github.com/tendermint/tendermint/node"
)
var node *nm.Node
func TestMain(m *testing.M) {
// start a tendermint node (and merkleeyes) in the background to test against
app := dummy.NewDummyApplication()
node = StartTendermint(app)
code := m.Run()
// and shut down proper at the end
node.Stop()
node.Wait()
os.Exit(code)
}

+ 4
- 4
scripts/txs/random.sh View File

@ -10,10 +10,10 @@ PORT=$2
for i in `seq 1 $N`; do
# store key value pair
KEY="abcd$i"
VALUE="dcba$i"
echo "$KEY:$VALUE"
curl 127.0.0.1:$PORT/broadcast_tx_sync?tx=\"$(toHex $KEY=$VALUE)\"
KEY=$(head -c 10 /dev/urandom)
VALUE="$i"
echo $(toHex $KEY=$VALUE)
curl 127.0.0.1:$PORT/broadcast_tx_sync?tx=0x$(toHex $KEY=$VALUE)
done

+ 50
- 17
types/block.go View File

@ -10,7 +10,7 @@ import (
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
. "github.com/tendermint/tmlibs/common"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/merkle"
)
@ -19,12 +19,14 @@ const (
DefaultBlockPartSize = 65536 // 64kB TODO: put part size in parts header?
)
// Block defines the atomic unit of a Tendermint blockchain
type Block struct {
*Header `json:"header"`
*Data `json:"data"`
LastCommit *Commit `json:"last_commit"`
}
// MakeBlock returns a new block and corresponding part set from the given information
// TODO: version
func MakeBlock(height int, chainID string, txs []Tx, commit *Commit,
prevBlockID BlockID, valHash, appHash []byte, partSize int) (*Block, *PartSet) {
@ -47,14 +49,14 @@ func MakeBlock(height int, chainID string, txs []Tx, commit *Commit,
return block, block.MakePartSet(partSize)
}
// Basic validation that doesn't involve state data.
// ValidateBasic performs basic validation that doesn't involve state data.
func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockID BlockID,
lastBlockTime time.Time, appHash []byte) error {
if b.ChainID != chainID {
return errors.New(Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID))
return errors.New(cmn.Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID))
}
if b.Height != lastBlockHeight+1 {
return errors.New(Fmt("Wrong Block.Header.Height. Expected %v, got %v", lastBlockHeight+1, b.Height))
return errors.New(cmn.Fmt("Wrong Block.Header.Height. Expected %v, got %v", lastBlockHeight+1, b.Height))
}
/* TODO: Determine bounds for Time
See blockchain/reactor "stopSyncingDurationMinutes"
@ -64,13 +66,13 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockID B
}
*/
if b.NumTxs != len(b.Data.Txs) {
return errors.New(Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", len(b.Data.Txs), b.NumTxs))
return errors.New(cmn.Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", len(b.Data.Txs), b.NumTxs))
}
if !b.LastBlockID.Equals(lastBlockID) {
return errors.New(Fmt("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID))
return errors.New(cmn.Fmt("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID))
}
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
return errors.New(Fmt("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash()))
return errors.New(cmn.Fmt("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash()))
}
if b.Header.Height != 1 {
if err := b.LastCommit.ValidateBasic(); err != nil {
@ -78,15 +80,16 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockID B
}
}
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
return errors.New(Fmt("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash()))
return errors.New(cmn.Fmt("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash()))
}
if !bytes.Equal(b.AppHash, appHash) {
return errors.New(Fmt("Wrong Block.Header.AppHash. Expected %X, got %v", appHash, b.AppHash))
return errors.New(cmn.Fmt("Wrong Block.Header.AppHash. Expected %X, got %v", appHash, b.AppHash))
}
// NOTE: the AppHash and ValidatorsHash are validated later.
return nil
}
// FillHeader fills in any remaining header fields that are a function of the block data
func (b *Block) FillHeader() {
if b.LastCommitHash == nil {
b.LastCommitHash = b.LastCommit.Hash()
@ -96,7 +99,7 @@ func (b *Block) FillHeader() {
}
}
// Computes and returns the block hash.
// Hash computes and returns the block hash.
// If the block is incomplete, block hash is nil for safety.
func (b *Block) Hash() data.Bytes {
// fmt.Println(">>", b.Data)
@ -107,13 +110,14 @@ func (b *Block) Hash() data.Bytes {
return b.Header.Hash()
}
// MakePartSet returns a PartSet containing parts of a serialized block.
// This is the form in which the block is gossipped to peers.
func (b *Block) MakePartSet(partSize int) *PartSet {
return NewPartSetFromData(wire.BinaryBytes(b), partSize)
}
// Convenience.
// A nil block never hashes to anything.
// Nothing hashes to a nil hash.
// HashesTo is a convenience function that checks if a block hashes to the given argument.
// A nil block never hashes to anything, and nothing hashes to a nil hash.
func (b *Block) HashesTo(hash []byte) bool {
if len(hash) == 0 {
return false
@ -124,10 +128,12 @@ func (b *Block) HashesTo(hash []byte) bool {
return bytes.Equal(b.Hash(), hash)
}
// String returns a string representation of the block
func (b *Block) String() string {
return b.StringIndented("")
}
// StringIndented returns a string representation of the block
func (b *Block) StringIndented(indent string) string {
if b == nil {
return "nil-Block"
@ -143,6 +149,7 @@ func (b *Block) StringIndented(indent string) string {
indent, b.Hash())
}
// StringShort returns a shortened string representation of the block
func (b *Block) StringShort() string {
if b == nil {
return "nil-Block"
@ -153,6 +160,7 @@ func (b *Block) StringShort() string {
//-----------------------------------------------------------------------------
// Header defines the structure of a Tendermint block header
type Header struct {
ChainID string `json:"chain_id"`
Height int `json:"height"`
@ -165,6 +173,7 @@ type Header struct {
AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block
}
// Hash returns the hash of the header.
// NOTE: hash is nil if required fields are missing.
func (h *Header) Hash() data.Bytes {
if len(h.ValidatorsHash) == 0 {
@ -183,6 +192,7 @@ func (h *Header) Hash() data.Bytes {
})
}
// StringIndented returns a string representation of the header
func (h *Header) StringIndented(indent string) string {
if h == nil {
return "nil-Header"
@ -212,6 +222,7 @@ func (h *Header) StringIndented(indent string) string {
//-------------------------------------
// Commit contains the evidence that a block was committed by a set of validators.
// NOTE: Commit is empty for height 1, but never nil.
type Commit struct {
// NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order.
@ -223,9 +234,10 @@ type Commit struct {
// Volatile
firstPrecommit *Vote
hash data.Bytes
bitArray *BitArray
bitArray *cmn.BitArray
}
// FirstPrecommit returns the first non-nil precommit in the commit
func (commit *Commit) FirstPrecommit() *Vote {
if len(commit.Precommits) == 0 {
return nil
@ -242,6 +254,7 @@ func (commit *Commit) FirstPrecommit() *Vote {
return nil
}
// Height returns the height of the commit
func (commit *Commit) Height() int {
if len(commit.Precommits) == 0 {
return 0
@ -249,6 +262,7 @@ func (commit *Commit) Height() int {
return commit.FirstPrecommit().Height
}
// Round returns the round of the commit
func (commit *Commit) Round() int {
if len(commit.Precommits) == 0 {
return 0
@ -256,10 +270,12 @@ func (commit *Commit) Round() int {
return commit.FirstPrecommit().Round
}
// Type returns the vote type of the commit, which is always VoteTypePrecommit
func (commit *Commit) Type() byte {
return VoteTypePrecommit
}
// Size returns the number of votes in the commit
func (commit *Commit) Size() int {
if commit == nil {
return 0
@ -267,24 +283,30 @@ func (commit *Commit) Size() int {
return len(commit.Precommits)
}
func (commit *Commit) BitArray() *BitArray {
// BitArray returns a BitArray of which validators voted in this commit
func (commit *Commit) BitArray() *cmn.BitArray {
if commit.bitArray == nil {
commit.bitArray = NewBitArray(len(commit.Precommits))
commit.bitArray = cmn.NewBitArray(len(commit.Precommits))
for i, precommit := range commit.Precommits {
// TODO: need to check the BlockID otherwise we could be counting conflicts,
// not just the one with +2/3 !
commit.bitArray.SetIndex(i, precommit != nil)
}
}
return commit.bitArray
}
// GetByIndex returns the vote corresponding to a given validator index
func (commit *Commit) GetByIndex(index int) *Vote {
return commit.Precommits[index]
}
// IsCommit returns true if there is at least one vote
func (commit *Commit) IsCommit() bool {
return len(commit.Precommits) != 0
}
// ValidateBasic performs basic validation that doesn't involve state data.
func (commit *Commit) ValidateBasic() error {
if commit.BlockID.IsZero() {
return errors.New("Commit cannot be for nil block")
@ -319,6 +341,7 @@ func (commit *Commit) ValidateBasic() error {
return nil
}
// Hash returns the hash of the commit
func (commit *Commit) Hash() data.Bytes {
if commit.hash == nil {
bs := make([]interface{}, len(commit.Precommits))
@ -330,6 +353,7 @@ func (commit *Commit) Hash() data.Bytes {
return commit.hash
}
// StringIndented returns a string representation of the commit
func (commit *Commit) StringIndented(indent string) string {
if commit == nil {
return "nil-Commit"
@ -349,6 +373,7 @@ func (commit *Commit) StringIndented(indent string) string {
//-----------------------------------------------------------------------------
// Data contains the set of transactions included in the block
type Data struct {
// Txs that will be applied by state @ block.Height+1.
@ -360,6 +385,7 @@ type Data struct {
hash data.Bytes
}
// Hash returns the hash of the data
func (data *Data) Hash() data.Bytes {
if data.hash == nil {
data.hash = data.Txs.Hash() // NOTE: leaves of merkle tree are TxIDs
@ -367,11 +393,12 @@ func (data *Data) Hash() data.Bytes {
return data.hash
}
// StringIndented returns a string representation of the transactions
func (data *Data) StringIndented(indent string) string {
if data == nil {
return "nil-Data"
}
txStrings := make([]string, MinInt(len(data.Txs), 21))
txStrings := make([]string, cmn.MinInt(len(data.Txs), 21))
for i, tx := range data.Txs {
if i == 20 {
txStrings[i] = fmt.Sprintf("... (%v total)", len(data.Txs))
@ -388,24 +415,29 @@ func (data *Data) StringIndented(indent string) string {
//--------------------------------------------------------------------------------
// BlockID defines the unique ID of a block as its Hash and its PartSetHeader
type BlockID struct {
Hash data.Bytes `json:"hash"`
PartsHeader PartSetHeader `json:"parts"`
}
// IsZero returns true if this is the BlockID for a nil-block
func (blockID BlockID) IsZero() bool {
return len(blockID.Hash) == 0 && blockID.PartsHeader.IsZero()
}
// Equals returns true if the BlockID matches the given BlockID
func (blockID BlockID) Equals(other BlockID) bool {
return bytes.Equal(blockID.Hash, other.Hash) &&
blockID.PartsHeader.Equals(other.PartsHeader)
}
// Key returns a machine-readable string representation of the BlockID
func (blockID BlockID) Key() string {
return string(blockID.Hash) + string(wire.BinaryBytes(blockID.PartsHeader))
}
// WriteSignBytes writes the canonical bytes of the BlockID to the given writer for digital signing
func (blockID BlockID) WriteSignBytes(w io.Writer, n *int, err *error) {
if blockID.IsZero() {
wire.WriteTo([]byte("null"), w, n, err)
@ -415,6 +447,7 @@ func (blockID BlockID) WriteSignBytes(w io.Writer, n *int, err *error) {
}
// String returns a human readable string representation of the BlockID
func (blockID BlockID) String() string {
return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader)
}

Loading…
Cancel
Save