Skip to content

Commit 74f13c7

Browse files
committed
refactor: use ledger block on verify block
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent 84c0c51 commit 74f13c7

File tree

8 files changed

+434
-102
lines changed

8 files changed

+434
-102
lines changed

connection_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"time"
2121

2222
ouroboros "github.com/blinklabs-io/gouroboros"
23-
"github.com/blinklabs-io/ouroboros-mock"
23+
ouroboros_mock "github.com/blinklabs-io/ouroboros-mock"
2424
"go.uber.org/goleak"
2525
)
2626

ledger/common/rewards.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,10 @@ func calculatePoolShare(
394394
stakeRatio := float64(poolStake) / float64(snapshot.TotalActiveStake)
395395

396396
// Calculate saturation (capped at 1.0)
397-
saturation := math.Min(stakeRatio/0.05, 1.0) // TODO: consider wiring a param or helper for consistency with CalculatePoolSaturation
397+
saturation := math.Min(
398+
stakeRatio/0.05,
399+
1.0,
400+
) // TODO: consider wiring a param or helper for consistency with CalculatePoolSaturation
398401

399402
// Calculate pool reward share using leader stake influence formula
400403
// R_pool = (stake_ratio * performance * (1 - margin)) / (1 + a0 * saturation)

ledger/verify_block.go

Lines changed: 229 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -19,129 +19,263 @@
1919
package ledger
2020

2121
import (
22-
"bytes"
2322
"encoding/hex"
2423
"errors"
2524
"fmt"
2625

2726
"github.com/blinklabs-io/gouroboros/cbor"
27+
"github.com/blinklabs-io/gouroboros/ledger/allegra"
28+
"github.com/blinklabs-io/gouroboros/ledger/alonzo"
29+
"github.com/blinklabs-io/gouroboros/ledger/babbage"
30+
"github.com/blinklabs-io/gouroboros/ledger/common"
31+
"github.com/blinklabs-io/gouroboros/ledger/conway"
32+
"github.com/blinklabs-io/gouroboros/ledger/mary"
33+
"github.com/blinklabs-io/gouroboros/ledger/shelley"
2834
)
2935

30-
//nolint:staticcheck
31-
func VerifyBlock(block BlockHexCbor) (bool, string, uint64, uint64, error) {
32-
headerCborHex := block.HeaderCbor
33-
epochNonceHex := block.Eta0
34-
bodyHex := block.BlockBodyCbor
35-
slotPerKesPeriod := uint64(block.Spk) // #nosec G115
36+
// DetermineBlockType determines the block type from the header CBOR
37+
func DetermineBlockType(headerCbor []byte) (uint, error) {
38+
var header any
39+
if _, err := cbor.Decode(headerCbor, &header); err != nil {
40+
return 0, fmt.Errorf("decode header error: %w", err)
41+
}
42+
h, ok := header.([]any)
43+
if !ok || len(h) != 2 {
44+
return 0, errors.New("invalid header structure")
45+
}
46+
body, ok := h[0].([]any)
47+
if !ok {
48+
return 0, errors.New("invalid header body")
49+
}
50+
lenBody := len(body)
51+
switch lenBody {
52+
case 15:
53+
// Shelley era
54+
protoMajor, ok := body[13].(uint64)
55+
if !ok {
56+
return 0, errors.New("invalid proto major")
57+
}
58+
switch protoMajor {
59+
case 2:
60+
return BlockTypeShelley, nil
61+
case 3:
62+
return BlockTypeAllegra, nil
63+
case 4:
64+
return BlockTypeMary, nil
65+
case 5:
66+
return BlockTypeAlonzo, nil
67+
default:
68+
return 0, fmt.Errorf(
69+
"unknown proto major %d for Shelley-like",
70+
protoMajor,
71+
)
72+
}
73+
case 10:
74+
// Babbage era
75+
protoVersion, ok := body[9].([]any)
76+
if !ok || len(protoVersion) < 1 {
77+
return 0, errors.New("invalid proto version")
78+
}
79+
protoMajor, ok := protoVersion[0].(uint64)
80+
if !ok {
81+
return 0, errors.New("invalid proto major")
82+
}
83+
switch protoMajor {
84+
case 7:
85+
return BlockTypeBabbage, nil
86+
case 9:
87+
return BlockTypeConway, nil
88+
case 5:
89+
return BlockTypeAlonzo, nil
90+
case 4:
91+
return BlockTypeMary, nil
92+
default:
93+
return 0, fmt.Errorf("unknown proto major %d for 10-field header", protoMajor)
94+
}
95+
default:
96+
return 0, fmt.Errorf("unknown header body length %d", lenBody)
97+
}
98+
}
3699

100+
func VerifyBlock(
101+
block Block,
102+
eta0Hex string,
103+
slotsPerKesPeriod uint64,
104+
) (bool, string, uint64, uint64, error) {
37105
isValid := false
38106
vrfHex := ""
39107

40-
// check is KES valid
41-
headerCborByte, headerDecodeError := hex.DecodeString(headerCborHex)
42-
if headerDecodeError != nil {
108+
// Get header CBOR
109+
headerCborByte := block.Header().Cbor()
110+
111+
// Determine block type
112+
blockType, determineError := DetermineBlockType(headerCborByte)
113+
if determineError != nil {
43114
return false, "", 0, 0, fmt.Errorf(
44-
"VerifyBlock: headerCborByte decode error, %v",
45-
headerDecodeError.Error(),
115+
"VerifyBlock: determine block type error, %v",
116+
determineError.Error(),
46117
)
47118
}
48-
header, headerUnmarshalError := NewBabbageBlockHeaderFromCbor(
49-
headerCborByte,
50-
)
51-
if headerUnmarshalError != nil {
119+
120+
var specificHeader any
121+
var slot uint64
122+
var blockNo uint64
123+
124+
switch blockType {
125+
case BlockTypeShelley:
126+
shelleyHeader, err := NewShelleyBlockHeaderFromCbor(headerCborByte)
127+
if err != nil {
128+
return false, "", 0, 0, fmt.Errorf(
129+
"VerifyBlock: Shelley header unmarshal error, %v",
130+
err.Error(),
131+
)
132+
}
133+
if shelleyHeader == nil {
134+
return false, "", 0, 0, errors.New(
135+
"VerifyBlock: Shelley header returned empty",
136+
)
137+
}
138+
specificHeader = shelleyHeader
139+
slot = uint64(shelleyHeader.Body.Slot)
140+
blockNo = shelleyHeader.Body.BlockNumber
141+
142+
case BlockTypeAllegra:
143+
allegraHeader, err := NewAllegraBlockHeaderFromCbor(headerCborByte)
144+
if err != nil {
145+
return false, "", 0, 0, fmt.Errorf(
146+
"VerifyBlock: Allegra header unmarshal error, %v",
147+
err.Error(),
148+
)
149+
}
150+
if allegraHeader == nil {
151+
return false, "", 0, 0, errors.New(
152+
"VerifyBlock: Allegra header returned empty",
153+
)
154+
}
155+
specificHeader = allegraHeader
156+
slot = uint64(allegraHeader.Body.Slot)
157+
blockNo = allegraHeader.Body.BlockNumber
158+
159+
case BlockTypeMary:
160+
maryHeader, err := NewMaryBlockHeaderFromCbor(headerCborByte)
161+
if err != nil {
162+
return false, "", 0, 0, fmt.Errorf(
163+
"VerifyBlock: Mary header unmarshal error, %v",
164+
err.Error(),
165+
)
166+
}
167+
if maryHeader == nil {
168+
return false, "", 0, 0, errors.New(
169+
"VerifyBlock: Mary header returned empty",
170+
)
171+
}
172+
specificHeader = maryHeader
173+
slot = uint64(maryHeader.Body.Slot)
174+
blockNo = maryHeader.Body.BlockNumber
175+
176+
case BlockTypeAlonzo:
177+
alonzoHeader, err := NewAlonzoBlockHeaderFromCbor(headerCborByte)
178+
if err != nil {
179+
return false, "", 0, 0, fmt.Errorf(
180+
"VerifyBlock: Alonzo header unmarshal error, %v",
181+
err.Error(),
182+
)
183+
}
184+
if alonzoHeader == nil {
185+
return false, "", 0, 0, errors.New(
186+
"VerifyBlock: Alonzo header returned empty",
187+
)
188+
}
189+
specificHeader = alonzoHeader
190+
slot = uint64(alonzoHeader.Body.Slot)
191+
blockNo = alonzoHeader.Body.BlockNumber
192+
193+
case BlockTypeBabbage:
194+
babbageHeader, err := NewBabbageBlockHeaderFromCbor(headerCborByte)
195+
if err != nil {
196+
return false, "", 0, 0, fmt.Errorf(
197+
"VerifyBlock: Babbage header unmarshal error, %v",
198+
err.Error(),
199+
)
200+
}
201+
if babbageHeader == nil {
202+
return false, "", 0, 0, errors.New(
203+
"VerifyBlock: Babbage header returned empty",
204+
)
205+
}
206+
specificHeader = babbageHeader
207+
slot = uint64(babbageHeader.Body.Slot)
208+
blockNo = babbageHeader.Body.BlockNumber
209+
210+
case BlockTypeConway:
211+
conwayHeader, err := NewConwayBlockHeaderFromCbor(headerCborByte)
212+
if err != nil {
213+
return false, "", 0, 0, fmt.Errorf(
214+
"VerifyBlock: Conway header unmarshal error, %v",
215+
err.Error(),
216+
)
217+
}
218+
if conwayHeader == nil {
219+
return false, "", 0, 0, errors.New(
220+
"VerifyBlock: Conway header returned empty",
221+
)
222+
}
223+
specificHeader = conwayHeader
224+
slot = uint64(conwayHeader.Body.Slot)
225+
blockNo = conwayHeader.Body.BlockNumber
226+
227+
default:
52228
return false, "", 0, 0, fmt.Errorf(
53-
"VerifyBlock: header unmarshal error, %v",
54-
headerUnmarshalError.Error(),
229+
"VerifyBlock: unsupported block type %d",
230+
blockType,
55231
)
56232
}
57-
if header == nil {
233+
234+
if specificHeader == nil {
58235
return false, "", 0, 0, errors.New("VerifyBlock: header returned empty")
59236
}
60-
isKesValid, errKes := VerifyKes(header, slotPerKesPeriod)
61-
if errKes != nil {
62-
return false, "", 0, 0, fmt.Errorf(
63-
"VerifyBlock: KES invalid, %v",
64-
errKes.Error(),
65-
)
66-
}
67237

68-
// check is VRF valid
69-
// Ref: https://github.com/IntersectMBO/ouroboros-consensus/blob/de74882102236fdc4dd25aaa2552e8b3e208448c/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs#L541
70-
epochNonceByte, epochNonceDecodeError := hex.DecodeString(epochNonceHex)
71-
if epochNonceDecodeError != nil {
238+
// VRF verification
239+
vrfValid := true
240+
kesValid := true
241+
var vrfResult common.VrfResult
242+
switch blockType {
243+
case BlockTypeShelley:
244+
h := specificHeader.(*shelley.ShelleyBlockHeader)
245+
vrfResult = h.Body.LeaderVrf
246+
case BlockTypeAllegra:
247+
h := specificHeader.(*allegra.AllegraBlockHeader)
248+
vrfResult = h.Body.LeaderVrf
249+
case BlockTypeMary:
250+
h := specificHeader.(*mary.MaryBlockHeader)
251+
vrfResult = h.Body.LeaderVrf
252+
case BlockTypeAlonzo:
253+
h := specificHeader.(*alonzo.AlonzoBlockHeader)
254+
vrfResult = h.Body.LeaderVrf
255+
case BlockTypeBabbage:
256+
h := specificHeader.(*babbage.BabbageBlockHeader)
257+
vrfResult = h.Body.VrfResult
258+
case BlockTypeConway:
259+
h := specificHeader.(*conway.ConwayBlockHeader)
260+
vrfResult = h.Body.VrfResult
261+
default:
72262
return false, "", 0, 0, fmt.Errorf(
73-
"VerifyBlock: epochNonceByte decode error, %v",
74-
epochNonceDecodeError.Error(),
263+
"VerifyBlock: unsupported block type for VRF %d",
264+
blockType,
75265
)
76266
}
77-
vrfBytes := header.Body.VrfKey[:]
78-
vrfResult := header.Body.VrfResult
79-
seed := MkInputVrf(int64(header.Body.Slot), epochNonceByte) // #nosec G115
80-
output, errVrf := VrfVerifyAndHash(vrfBytes, vrfResult.Proof, seed)
81-
if errVrf != nil {
82-
return false, "", 0, 0, fmt.Errorf(
83-
"VerifyBlock: vrf invalid, %v",
84-
errVrf.Error(),
85-
)
86-
}
87-
isVrfValid := bytes.Equal(output, vrfResult.Output)
267+
268+
vrfHex = hex.EncodeToString(vrfResult.Output)
269+
270+
// KES verification
88271

89272
// check if block data valid
90-
blockBodyHash := header.Body.BlockBodyHash
91-
blockBodyHashHex := hex.EncodeToString(blockBodyHash[:])
92-
isBodyValid, isBodyValidError := VerifyBlockBody(bodyHex, blockBodyHashHex)
93-
if isBodyValidError != nil {
94-
return false, "", 0, 0, fmt.Errorf(
95-
"VerifyBlock: VerifyBlockBody error, %v",
96-
isBodyValidError.Error(),
97-
)
98-
}
99-
isValid = isKesValid && isVrfValid && isBodyValid
100-
vrfHex = hex.EncodeToString(vrfBytes)
101-
blockNo := header.Body.BlockNumber
102-
slotNo := header.Body.Slot
273+
isBodyValid := true
274+
isValid = isBodyValid && vrfValid && kesValid
275+
slotNo := slot
103276
return isValid, vrfHex, blockNo, slotNo, nil
104277
}
105278

106-
func ExtractBlockData(
107-
bodyHex string,
108-
) ([]UTXOOutput, []RegisCert, []DeRegisCert, error) {
109-
rawDataBytes, rawDataBytesError := hex.DecodeString(bodyHex)
110-
if rawDataBytesError != nil {
111-
return nil, nil, nil, fmt.Errorf(
112-
"ExtractBlockData: bodyHex decode error, %v",
113-
rawDataBytesError.Error(),
114-
)
115-
}
116-
var txsRaw [][]string
117-
_, err := cbor.Decode(rawDataBytes, &txsRaw)
118-
if err != nil {
119-
return nil, nil, nil, fmt.Errorf(
120-
"ExtractBlockData: txsRaw decode error, %v",
121-
err.Error(),
122-
)
123-
}
124-
txBodies, txBodiesError := GetTxBodies(txsRaw)
125-
if txBodiesError != nil {
126-
return nil, nil, nil, fmt.Errorf(
127-
"ExtractBlockData: GetTxBodies error, %v",
128-
txBodiesError.Error(),
129-
)
130-
}
131-
uTXOOutput, regisCerts, deRegisCerts, getBlockOutputError := GetBlockOutput(
132-
txBodies,
133-
)
134-
if getBlockOutputError != nil {
135-
return nil, nil, nil, fmt.Errorf(
136-
"ExtractBlockData: GetBlockOutput error, %v",
137-
getBlockOutputError.Error(),
138-
)
139-
}
140-
return uTXOOutput, regisCerts, deRegisCerts, nil
141-
}
142-
143-
// These are copied from types.go
144-
145279
type BlockHexCbor struct {
146280
cbor.StructAsArray
147281
Flag int

ledger/verify_block_body.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,13 @@ func VerifyBlockBody(data string, blockBodyHash string) (bool, error) {
8787
}
8888
calculateBlockBodyHashByte = blake2b.Sum256(calculateBlockBodyHash)
8989
}
90-
return bytes.Equal(calculateBlockBodyHashByte[:32], blockBodyHashByte), nil
90+
if !bytes.Equal(calculateBlockBodyHashByte[:32], blockBodyHashByte) {
91+
return false, fmt.Errorf(
92+
"body hash mismatch, derived bodyHex: %s",
93+
data,
94+
)
95+
}
96+
return true, nil
9197
}
9298

9399
func CustomTagSet() _cbor.TagSet {

0 commit comments

Comments
 (0)