summaryrefslogtreecommitdiff
path: root/ledger/ledgercore/catchpointlabel.go
blob: b80a0bc1e2513ee63349d1350859662c9dfe394a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright (C) 2019-2024 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package ledgercore

import (
	"encoding/base32"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/algorand/go-algorand/crypto"
	"github.com/algorand/go-algorand/data/basics"
	"github.com/algorand/go-algorand/logging"
	"github.com/algorand/go-algorand/protocol"
)

var base32Encoder = base32.StdEncoding.WithPadding(base32.NoPadding)

// ErrCatchpointParsingFailed is used when we attempt to parse and catchpoint label and failing doing so.
var ErrCatchpointParsingFailed = errors.New("catchpoint parsing failed")

// CatchpointLabelMaker is used for abstract the creation of different catchpoints versions.
// Different catchpoint version might hash different fields.
type CatchpointLabelMaker interface {
	// buffer returns a image used for hashing. (concatenating all fields in the label)
	buffer() []byte
	// round returns the catchpoint label round
	round() basics.Round
	// message returns a printable string containing all the relevant fields in the label.
	message() string
}

// CatchpointLabelMakerV6 represent a single catchpoint label maker, matching catchpoints of version V6 and below.
type CatchpointLabelMakerV6 struct {
	ledgerRound          basics.Round
	ledgerRoundBlockHash crypto.Digest
	balancesMerkleRoot   crypto.Digest
	totals               AccountTotals
}

// MakeCatchpointLabelMakerV6 creates a V6 catchpoint label given the catchpoint label parameters.
func MakeCatchpointLabelMakerV6(ledgerRound basics.Round, ledgerRoundBlockHash *crypto.Digest,
	balancesMerkleRoot *crypto.Digest, totals AccountTotals) *CatchpointLabelMakerV6 {
	return &CatchpointLabelMakerV6{
		ledgerRound:          ledgerRound,
		ledgerRoundBlockHash: *ledgerRoundBlockHash,
		balancesMerkleRoot:   *balancesMerkleRoot,
		totals:               totals,
	}
}

func (l *CatchpointLabelMakerV6) buffer() []byte {
	encodedTotals := protocol.EncodeReflect(&l.totals)
	buffer := make([]byte, 2*crypto.DigestSize+len(encodedTotals))
	copy(buffer[:], l.ledgerRoundBlockHash[:])
	copy(buffer[crypto.DigestSize:], l.balancesMerkleRoot[:])
	copy(buffer[crypto.DigestSize*2:], encodedTotals)

	return buffer
}

func (l *CatchpointLabelMakerV6) round() basics.Round {
	return l.ledgerRound
}

func (l *CatchpointLabelMakerV6) message() string {
	return fmt.Sprintf("round=%d, block digest=%s, accounts digest=%s", l.ledgerRound, l.ledgerRoundBlockHash, l.balancesMerkleRoot)
}

// CatchpointLabelMakerCurrent represent a single catchpoint maker, matching catchpoints of version V7 and above.
type CatchpointLabelMakerCurrent struct {
	v6Label            CatchpointLabelMakerV6
	spVerificationHash crypto.Digest
}

// MakeCatchpointLabelMakerCurrent creates a catchpoint label given the catchpoint label parameters.
func MakeCatchpointLabelMakerCurrent(ledgerRound basics.Round, ledgerRoundBlockHash *crypto.Digest,
	balancesMerkleRoot *crypto.Digest, totals AccountTotals, spVerificationContextHash *crypto.Digest) *CatchpointLabelMakerCurrent {
	return &CatchpointLabelMakerCurrent{
		v6Label:            *MakeCatchpointLabelMakerV6(ledgerRound, ledgerRoundBlockHash, balancesMerkleRoot, totals),
		spVerificationHash: *spVerificationContextHash,
	}
}

func (l *CatchpointLabelMakerCurrent) buffer() []byte {
	v6Buffer := l.v6Label.buffer()

	return append(v6Buffer, l.spVerificationHash[:]...)
}

func (l *CatchpointLabelMakerCurrent) round() basics.Round {
	return l.v6Label.round()
}

func (l *CatchpointLabelMakerCurrent) message() string {
	return fmt.Sprintf("%s spver digest=%s", l.v6Label.message(), l.spVerificationHash)
}

// MakeLabel returns the user-facing representation of this catchpoint label. ( i.e. the "label" )
func MakeLabel(l CatchpointLabelMaker) string {
	hash := crypto.Hash(l.buffer())
	encodedHash := base32Encoder.EncodeToString(hash[:])
	out := fmt.Sprintf("%d#%s", l.round(), encodedHash)
	logging.Base().Infof("Creating a catchpoint label %s for %s", out, l.message())
	return out
}

// ParseCatchpointLabel parse the given label and breaks it into the round and hash components. In case of a parsing failure,
// the returned err is non-nil.
func ParseCatchpointLabel(label string) (round basics.Round, hash crypto.Digest, err error) {
	err = ErrCatchpointParsingFailed
	splitted := strings.Split(label, "#")
	if len(splitted) != 2 {
		return
	}
	var uintRound uint64
	// first portion is a round number.
	uintRound, err = strconv.ParseUint(splitted[0], 10, 64)
	if err != nil {
		return
	}
	round = basics.Round(uintRound)
	var hashBytes []byte
	hashBytes, err = base32Encoder.DecodeString(splitted[1])
	if err != nil {
		return
	}
	if len(hashBytes) > crypto.DigestSize {
		err = ErrCatchpointParsingFailed
		return
	}
	copy(hash[:], hashBytes[:])
	err = nil
	return
}