Skip to content

Commit

Permalink
feat(): add NewPeek / RequestBlock / RespondBlock
Browse files Browse the repository at this point in the history
  • Loading branch information
n33pm committed Mar 20, 2024
1 parent 4cd4273 commit 4268cbc
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 133 deletions.
6 changes: 6 additions & 0 deletions pkg/peerprotocol/fullnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ func NewFullNodeProtocol(connection *Connection) (*FullNodeProtocol, error) {
func (c *FullNodeProtocol) RequestPeers() error {
return c.connection.Do(protocols.ProtocolMessageTypeRequestPeers, &protocols.RequestPeers{})
}

// RequestBlock asks the current peer to respond with a block
func (c *FullNodeProtocol) RequestBlock(heigth uint32, includeTransactionBlock bool) error {
return c.connection.Do(protocols.ProtocolMessageTypeRequestBlock, &protocols.RequestBlock{Height: heigth, IncludeTransactionBlock: includeTransactionBlock})
}

21 changes: 21 additions & 0 deletions pkg/protocols/fullnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,24 @@ type RequestPeers struct{}
type RespondPeers struct {
PeerList []types.TimestampedPeerInfo `streamable:""`
}

// NewPeek is the format for the new_peak response
type NewPeek struct {
HeaderHash types.Bytes32 `streamable:""`
Height uint32 `streamable:""`
Weight types.Uint128 `streamable:""`
ForkPointWithPreviousPeak uint32 `streamable:""`
UnfinishedRewardBlockHash types.Bytes32 `streamable:""`
}

// RequestBlock is the format for the request_block request
type RequestBlock struct {
Height uint32 `streamable:""`
IncludeTransactionBlock bool `streamable:""`
}

// RespondBlock is the format for the respond_block response
type RespondBlock struct {
Block types.FullBlock `streamable:""`
}

21 changes: 21 additions & 0 deletions pkg/protocols/fullnode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,24 @@ func TestRespondPeers(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, encodedBytes, reencodedBytes)
}

func TestRequestBlock(t *testing.T) {
blockHeight := uint32(2347)
hexStr := "00000000000000000000000000000000000040340000092b000000000000000000000001adcac4c82bc188b377ded9d77e4ba90ec871f9c0b8e4798a1d23a65900e0c10d3d9bd31d291abb6b4c6ed7c151032ddf690745b22d2f6f10f2c0ac3f8e14f5150e18f115f00182fd324dffdf8c57792e9d26adc9841ea0d8214afae9f44c5bd3f06af06bcaee17dca035be794c36c423288cef2fdf5900b66a2f7e9c163da148e9101ca260b084f678b4ae0ca5772b5750b7f44394efcfc351800f21eac65d900ddebbbf8e47732000000100b18870d5b5fcd7baa9445c1f8458beac4dcd10fa9254646a5c6b11d018be82a803487d8518448052600935b18943f7e7db758db3ac1631018e3d18336505e452c768506d44094bf209c2b015ff2cedfd7aa6e9256e13b4652adb8c1452c501a933404761806f4e11ce2e138602fb17ee0383c071b8fb7f47679501b77a1d53c18093ebf53269a43650bb78737e3da4150d34927b5c3cfd994d77b34f061376d104e64dfafbf9eb313f0b0aa88059b7d95af5c8ee0e4ea4737a7172109fa990e5587917e5c6b07237de592d4309c319095311b920cf5963c4e7243c9de9fc3d3cadc69bfcc503fe28551e6886f4a308cc9383eb552516908323ca4bb7a006c70d01c188b377ded9d77e4ba90ec871f9c0b8e4798a1d23a65900e0c10d3d9bd31d2900000000056000000200ae256619f34f64c60b9c3821ea69e178cc601e49cc4b246176c17c51ed694b8584da0393e82142029f717214e0b44a31aaaf9875f27079f40de19ddec6b3360269a5161c2f2769f675f8e7ce8b457fa7bcc0a86e1f9330b9dd4e559b38137a0d010083b3a189e345f2af24515e39ea92b13bacd6dd8c14b257725bd93cdbb63ea88f135e3acf7e6ae5b960a6cd8db0cb3d020512990bba2ce011ea58cad1262b28e0db72e4309c7a2695908bfa3aefeb89335407626f7da8dbe10ca6f926de15a981c188b377ded9d77e4ba90ec871f9c0b8e4798a1d23a65900e0c10d3d9bd31d290000000005cac4c80100424506b6b6957111f455a922fcc02a6456170c2d2a9a77a58f35d95bc14e0d91be5efb9d73b4c3142b2d9ca8445697164088fb2f3b7569239820e62166c58b2009ac1c20347fec68b93fbeba55080cdf09b671b17b29360464a22785fff5b81a010001103b14520e67df9edf58dcd3028c37d779b20e2a9a1d8870cd8d34b81305e92d0000000000028f1202001d872926b6b4512e9f86240409a4dcb529a789f33bd45d5c3403d8557d20731122d457feb0a4c2ba79411e4213a14a9437a4fa71c88bdf2f60085c0a6b154f2790325aeade83148ce0138a1df6479010ad831be24942d2effeca810d0f1baf37020195489a1064ce6366a92284e10eb4bacd284f7d7e085ecd62782fc7b1703d4bd395f65a469c5c37ae8a16b0756a1f5ad80407bf1c588749e90f68150ef14a407551eecccc329849f48e161aebf55d7085129e307588d1853e5e037fab7259df29cf4d9ef9dac40874f3a589f8eeaf10825bcb9e9b90074adc72cf6f3382ae65e900000000001b78cf00003e7378f866dabdd35ee5e2c5cfb7115b725c2e90df25dbba2590ff6d4b4e56feebdc774d01aee49e95e529e6979e6f47259c3611b3396fbdca3a2c4dd82fac68836eee4a71bc7f75a12f3febcdc1f80bf201b2ff5b9d994df9c3fda9df0a5b17010001adaa67a9a870fd370fe16fcbb65c4be294dfb6579600323d6e1e597b11256dee00000000001b78cf03005e37c168d5761771d986f4766c6972ef9b0653f3adf917d335e7cefd790cbecddecf021f9df463a6a801e8eedc73aa569f5c027bdec416813f2ff334c88fe72051f1568e27811b011d35da29099004eb09ba6a038844832bd725009d18a1b81701000001000000006403002c6a887316e81f4273ab2beb3ac84f31825a31f4afd0e723545b75acea10caf64b5dda4ddffa13b985086b2ee88fa5a1d4748d34f9b8ccd8b40418a8291d3e0491b4ad5433d7e9e1902f9e73398cff543372d6ba60bf07c21161e236da73f7010e05010000000064000057b0969c023691431262bd495c50cb005f3388a4750566a98b35761827e5f94a5673cb0bd89013313db595e51dc0c19cd3b426806d79420320f4e84031bd3f0befba686e74483ddc8ea239d27a37048474a09dd920d0b5f2fb91571f63d7762401000101020000017e0000d09482e77fb1dcf7c0229a46692a4f38f991688149f676a876ba0c3e166f3994646381a763404f5584d76283ecafc2fe678a0a81b1e75e73a36d19159182600395ac30168044f5f427071e76d97dd4b70eab06be6fa3ac24793a4488780e6509010000000000000091b4ab6fab4f3e3f7912e28e385a195a4a4e65b325bfbe313114da885eaf74e642179d0200889b70e2b2032c4d44406a4e9177f4158fb7609b930876137437ef3de3af1c8e7e8500273ab77a3f6e40cceccd5d491ac291ec8af137c100aff9504007e1de0aeb572d73cb348d529da8defef95b8bd815e2f2814b7b528596fb1a6a48f26b130100000000000001b454bca04db73ba1c4c19c470866e511a71a73600837ac8467cba42b57705132b16c050100908b4e5520fdf9edaaf02fa81e54a1b59aacc90195ed7f96456b56f5d606b2d70d99f3270c8de66a19526d221f1b003e23e4b90def8b62f2c764861c61d724779fa7d397e5ab91c30106fe4957e674a3d2ff65c06d1808a91b32c23c08c9c312010000020000017e02001f906ed8f095bad9d70c3d5b792b7b3d6a50ee8f6f3fab32876e99910d4be748f01e450030e237dc7aef7380d266179a98a6305125e1f3ac2cacab93dde0402e6e4ae45d445d9cbe59026d1229cf241bd7e8486dcf3d20a2d2f01e48370ce14502010000000000061ae4907e5e332380a2689e77ea9a485fbb5d6a3dd84c5aa179d3d9a003fecc43f35b270300aca0158c98ff464f7942257f901ea60f2a95ad1f9376c17b42a0bba97bd86e5b65ed9e93526334c6ed0ceb1b2032d2af24547573569c2801fb2d8f42b0ad8c5daff72f0bc62dc2503f9385f00c719a7ddf9261166fef6114afbea3966ce1af050100000000000012504890a945b69bc719b5cc9c5059a348d5034c5401f0267d6a1128837178a10b3afce301002b418db30870754b5d0abaa3a4fd1060591a85ff964997ca4d29e42a71d019c22e1e025397642a89c0e13bb1f742c5ed97de55f97c0ab63e640e10be2b2ebe1bcbd629b8906cbb0420a1f5ab1583090ec08a341217d85756ccf2815846f7fd2102010001020000017e02009a50a6536d487c9a7fad852bc3ab9ed2dca8d633164a647f2cdf20468fb02ab4445dd6ce85d16641aee46e1340a530e0917caadc0f5ec9fc7fc1b928698c4a12ff306deb3689d18bf6c90af772bba1151cb1b82c4f319b86b0def633bd80c51805000000000000061ae4d251d5e5e51661e483dd39862ecfbed6ef3a48529b2b522a431c67862d9bb6548b0200d42a2de42e351a2b5b0c3e9aa3adc3806033f233147e1cc8e77984601e2fdb888712f45bdec670e65b9a38825f0e84a91ae4606b548a55a19cb4336dd2e5b569ff6867f1c28714cfb06314849e55896a025ae0d57c751864b5c8ca1d35e3245701000000000000125048e8c5405ea099da345f6f8fe9d092f9ea51b6a7fe48b37be3891b05c051db9393e101006a30c805895b64dc89886fe508c3c3f0fa2a25e4bc17f9be12a37ec32e03ce7c5548130ff2bc98e8a19452e5da259ba95b1c3b7e687463dc70e704948b9709097331606e05dbd94aea96017668943475f160cc3e91de3448dde4ed81cec2d0130100008416fadbf6c13544d2be22bf69ac81679c237e3852972af2694c1c26466025ce2a0b38d5feda302fb27579e85bbe01ae70ea0871d20601d0ed7f3abc25ebf9989825bc9b4470bf03fe10007531435dc7b5332552800aa120b36f3baa02abf9e1cea41d2961b6132fcaea28ca35b425cb9bb2a23c71fa8af3c44509dfbb4e64670000000001aee44571244f660c4c22e0e2f85d51c2bbdd75e8217e04afa159b603739aa0ab133cae0ef53fbcb5a2ad59cd976bffdf18cd2cd96c64790c9a11549a873e99db22e6ce56e2105ea2c37a6d1cafc1cfc3cc03bdcc8eea62bee18ed99de59fb828cea41d2961b6132fcaea28ca35b425cb9bb2a23c71fa8af3c44509dfbb4e64670000000000000000000000000000000000000000000000000000000003a2c7c9b14fca84c30a94fddee8dbd01327103f6f3db7b45ed493603ff39edd2c0702c1404ebdbf1216619572fe54d4320919a60be60dea18928fbc108e638a7edf8df714ce1b708dada423602913cd59be9e018dc128ed51c32e959e87a2a3e603cb49000000000000000000"

// Hex to bytes
encodedBytes, err := hex.DecodeString(hexStr)
assert.NoError(t, err)

rb := &protocols.RespondBlock{}

err = streamable.Unmarshal(encodedBytes, rb)
assert.NoError(t, err)

assert.Equal(t, rb.Block.RewardChainBlock.Height, blockHeight)

// Test going the other direction
reencodedBytes, err := streamable.Marshal(rb)
assert.NoError(t, err)
assert.Equal(t, encodedBytes, reencodedBytes)
}
9 changes: 9 additions & 0 deletions pkg/protocols/messagetypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ const (

// there are many more of these in Chia - only listing the ones current is use for now

// ProtocolMessageTypeNewPeak new_peak
ProtocolMessageTypeNewPeak ProtocolMessageType = 20

// ProtocolMessageTypeRequestBlock request_block
ProtocolMessageTypeRequestBlock ProtocolMessageType = 26

// ProtocolMessageTypeRespondBlock respond_block
ProtocolMessageTypeRespondBlock ProtocolMessageType = 27

// ProtocolMessageTypeRequestPeers request_peers
ProtocolMessageTypeRequestPeers ProtocolMessageType = 43

Expand Down
119 changes: 66 additions & 53 deletions pkg/streamable/streamable.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,60 +60,11 @@ func unmarshalStruct(bytes []byte, t reflect.Type, tv reflect.Value) ([]byte, er
return bytes, nil
}

func unmarshalSlice(bytes []byte, t reflect.Type, v reflect.Value) ([]byte, error) {
var err error
var newVal []byte

// Slice/List is 4 byte prefix (number of items) and then serialization of each item
// Get 4 byte length prefix
var length []byte
length, bytes, err = util.ShiftNBytes(4, bytes)
if err != nil {
return nil, err
}
numItems := binary.BigEndian.Uint32(length)

sliceKind := t.Elem().Kind()
switch sliceKind {
case reflect.Uint8: // same as byte
// In this case, numItems == numBytes, because its a uint8
newVal, bytes, err = util.ShiftNBytes(uint(numItems), bytes)
if err != nil {
return bytes, err
}
if !v.CanSet() {
return bytes, fmt.Errorf("field %s is not settable", v.String())
}

sliceReflect := reflect.MakeSlice(v.Type(), 0, 0)
for _, newValBytes := range newVal {
sliceReflect = reflect.Append(sliceReflect, reflect.ValueOf(newValBytes))
}
v.Set(sliceReflect)
case reflect.Struct:
sliceReflect := reflect.MakeSlice(v.Type(), 0, 0)
for j := uint32(0); j < numItems; j++ {
newValue := reflect.Indirect(reflect.New(v.Type().Elem()))
bytes, err = unmarshalStruct(bytes, t.Elem(), newValue)
if err != nil {
return nil, err
}
sliceReflect = reflect.Append(sliceReflect, newValue)
}
v.Set(sliceReflect)
default:
return bytes, fmt.Errorf("encountered type inside slice that is not implemented")
}

return bytes, nil
}

// Struct field is used to parse out the streamable tag
// Not needed for anything else
// When recursively calling this on a wrapper type like mo.Option, pass the parent/wrapping StructField
func unmarshalField(bytes []byte, fieldType reflect.Type, fieldValue reflect.Value, structField reflect.StructField) ([]byte, error) {
var tagPresent bool
if _, tagPresent = structField.Tag.Lookup(tagName); !tagPresent {
if _, tagPresent := structField.Tag.Lookup(tagName); !tagPresent {
// Continuing because the tag isn't present
return bytes, nil
}
Expand Down Expand Up @@ -201,11 +152,37 @@ func unmarshalField(bytes []byte, fieldType reflect.Type, fieldValue reflect.Val
}
newInt := util.BytesToUint64(newVal)
fieldValue.SetUint(newInt)
case reflect.Array:
switch fieldValue.Type().Elem().Kind() {
case reflect.Uint8:
for i := 0; i < fieldValue.Len(); i++ {
optionalField := fieldValue.Index(1)
optionalType := optionalField.Type()
bytes, err = unmarshalField(bytes, optionalType, fieldValue.Index(i), structField)
if err != nil {
return bytes, err
}
}
}
case reflect.Slice:
bytes, err = unmarshalSlice(bytes, fieldType, fieldValue)
var length []byte
length, bytes, err = util.ShiftNBytes(4, bytes)
if err != nil {
return bytes, err
return nil, err
}
numItems := binary.BigEndian.Uint32(length)

sliceReflect := reflect.MakeSlice(fieldValue.Type(), 0, 0)
for j := uint32(0); j < numItems; j++ {
newValue := reflect.Indirect(reflect.New(fieldValue.Type().Elem()))
bytes, err = unmarshalField(bytes, fieldType.Elem(), newValue, structField)
if err != nil {
return nil, err
}
sliceReflect = reflect.Append(sliceReflect, newValue)
}

fieldValue.Set(sliceReflect)
case reflect.String:
// 4 byte size prefix, then []byte which can be converted to utf-8 string
// Get 4 byte length prefix
Expand All @@ -222,6 +199,18 @@ func unmarshalField(bytes []byte, fieldType reflect.Type, fieldValue reflect.Val
return nil, err
}
fieldValue.SetString(string(strBytes))
case reflect.Struct:
bytes, err = unmarshalStruct(bytes, fieldType, fieldValue)
if err != nil {
return bytes, err
}
case reflect.Bool:
var boolByte []byte
boolByte, bytes, err = util.ShiftNBytes(1, bytes)
if err != nil {
return bytes, err
}
fieldValue.SetBool(boolByte[0] == boolTrue)
default:
return bytes, fmt.Errorf("unimplemented type %s", fieldValue.Kind())
}
Expand Down Expand Up @@ -311,6 +300,24 @@ func marshalField(finalBytes []byte, fieldType reflect.Type, fieldValue reflect.
finalBytes = append(finalBytes, util.Uint32ToBytes(newInt)...)
case reflect.Uint64:
finalBytes = append(finalBytes, util.Uint64ToBytes(fieldValue.Uint())...)
case reflect.Array:
switch fieldType.Elem().Kind() {
case reflect.Uint8:
// special case as byte-string
for i := 0; i < fieldValue.Len(); i++ {
finalBytes, err = marshalField(finalBytes, fieldType.Elem(), fieldValue.Index(i), structField)
if err != nil {
return finalBytes, err
}
}
default:
return finalBytes, fmt.Errorf("unimplemented type %s", fieldType.Elem().Kind())
}
case reflect.Struct:
finalBytes, err = marshalStruct(finalBytes, fieldType, fieldValue)
if err != nil {
return finalBytes, err
}
case reflect.Slice:
finalBytes, err = marshalSlice(finalBytes, fieldType, fieldValue)
if err != nil {
Expand All @@ -323,6 +330,12 @@ func marshalField(finalBytes []byte, fieldType reflect.Type, fieldValue reflect.
finalBytes = append(finalBytes, util.Uint32ToBytes(numBytes)...)

finalBytes = append(finalBytes, strBytes...)
case reflect.Bool:
if fieldValue.Bool() {
finalBytes = append(finalBytes, boolTrue)
} else {
finalBytes = append(finalBytes, boolFalse)
}
default:
return finalBytes, fmt.Errorf("unimplemented type %s", fieldValue.Kind())
}
Expand All @@ -344,7 +357,7 @@ func marshalSlice(finalBytes []byte, t reflect.Type, v reflect.Value) ([]byte, e
// This is the easy case - already a slice of bytes
finalBytes = append(finalBytes, v.Bytes()...)
case reflect.Struct:
for j := 0; j < v.Len(); j++ {
for j := 0; j < int(numItems); j++ {
currentStruct := v.Index(j)

finalBytes, err = marshalStruct(finalBytes, currentStruct.Type(), currentStruct)
Expand Down
54 changes: 26 additions & 28 deletions pkg/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,40 +43,38 @@ type BlockRecord struct {

// FullBlock a full block
// https://github.com/Chia-Network/chia-blockchain/blob/0befdec071f49708e26c7638656874492c52600a/chia/types/full_block.py#L16
// @TODO Streamable
type FullBlock struct {
FinishedSubSlots []EndOfSubSlotBundle `json:"finished_sub_slots"`
RewardChainBlock RewardChainBlock `json:"reward_chain_block"`
ChallengeChainSPProof mo.Option[VDFProof] `json:"challenge_chain_sp_proof"`
ChallengeChainIPProof VDFProof `json:"challenge_chain_ip_proof"`
RewardChainSPProof mo.Option[VDFProof] `json:"reward_chain_sp_proof"`
RewardChainIPProof VDFProof `json:"reward_chain_ip_proof"`
InfusedChallengeChainIPProof mo.Option[VDFProof] `json:"infused_challenge_chain_ip_proof"`
Foliage Foliage `json:"foliage"`
FoliageTransactionBlock mo.Option[FoliageTransactionBlock] `json:"foliage_transaction_block"`
TransactionsInfo mo.Option[TransactionsInfo] `json:"transactions_info"`
TransactionsGenerator mo.Option[SerializedProgram] `json:"transactions_generator"`
TransactionsGeneratorRefList []uint32 `json:"transactions_generator_ref_list"`
FinishedSubSlots []EndOfSubSlotBundle `json:"finished_sub_slots" streamable:""`
RewardChainBlock RewardChainBlock `json:"reward_chain_block" streamable:""`
ChallengeChainSPProof mo.Option[VDFProof] `json:"challenge_chain_sp_proof" streamable:""`
ChallengeChainIPProof VDFProof `json:"challenge_chain_ip_proof" streamable:""`
RewardChainSPProof mo.Option[VDFProof] `json:"reward_chain_sp_proof" streamable:""`
RewardChainIPProof VDFProof `json:"reward_chain_ip_proof" streamable:""`
InfusedChallengeChainIPProof mo.Option[VDFProof] `json:"infused_challenge_chain_ip_proof" streamable:""`
Foliage Foliage `json:"foliage" streamable:""`
FoliageTransactionBlock mo.Option[FoliageTransactionBlock] `json:"foliage_transaction_block" streamable:""`
TransactionsInfo mo.Option[TransactionsInfo] `json:"transactions_info" streamable:""`
TransactionsGenerator mo.Option[SerializedProgram] `json:"transactions_generator" streamable:""`
TransactionsGeneratorRefList []uint32 `json:"transactions_generator_ref_list" streamable:""`
}

// RewardChainBlock Reward Chain Block
// https://github.com/Chia-Network/chia-blockchain/blob/0befdec071f49708e26c7638656874492c52600a/chia/types/blockchain_format/reward_chain_block.py#L30
// @TODO Streamable
type RewardChainBlock struct {
Weight Uint128 `json:"weight"`
Height uint32 `json:"height"`
TotalIters Uint128 `json:"total_iters"`
SignagePointIndex uint8 `json:"signage_point_index"`
POSSSCCChallengeHash Bytes32 `json:"pos_ss_cc_challenge_hash"`
ProofOfSpace ProofOfSpace `json:"proof_of_space"`
ChallengeChainSPVDF mo.Option[VDFInfo] `json:"challenge_chain_sp_vdf"`
ChallengeChainSPSignature G2Element `json:"challenge_chain_sp_signature"`
ChallengeChainIPVDF VDFInfo `json:"challenge_chain_ip_vdf"`
RewardChainSPVDF mo.Option[VDFInfo] `json:"reward_chain_sp_vdf"` // Not present for first sp in slot
RewardChainSPSignature G2Element `json:"reward_chain_sp_signature"`
RewardChainIPVDF VDFInfo `json:"reward_chain_ip_vdf"`
InfusedChallengeChainIPVDF mo.Option[VDFInfo] `json:"infused_challenge_chain_ip_vdf"` // Iff deficit < 16
IsTransactionBlock bool `json:"is_transaction_block"`
Weight Uint128 `json:"weight" streamable:""`
Height uint32 `json:"height" streamable:""`
TotalIters Uint128 `json:"total_iters" streamable:""`
SignagePointIndex uint8 `json:"signage_point_index" streamable:""`
POSSSCCChallengeHash Bytes32 `json:"pos_ss_cc_challenge_hash" streamable:""`
ProofOfSpace ProofOfSpace `json:"proof_of_space" streamable:""`
ChallengeChainSPVDF mo.Option[VDFInfo] `json:"challenge_chain_sp_vdf" streamable:""`
ChallengeChainSPSignature G2Element `json:"challenge_chain_sp_signature" streamable:""`
ChallengeChainIPVDF VDFInfo `json:"challenge_chain_ip_vdf" streamable:""`
RewardChainSPVDF mo.Option[VDFInfo] `json:"reward_chain_sp_vdf" streamable:""` // Not present for first sp in slot
RewardChainSPSignature G2Element `json:"reward_chain_sp_signature" streamable:""`
RewardChainIPVDF VDFInfo `json:"reward_chain_ip_vdf" streamable:""`
InfusedChallengeChainIPVDF mo.Option[VDFInfo] `json:"infused_challenge_chain_ip_vdf" streamable:""` // Iff deficit < 16
IsTransactionBlock bool `json:"is_transaction_block" streamable:""`
}

// BlockCountMetrics metrics from get_block_count_metrics endpoint
Expand Down
Loading

0 comments on commit 4268cbc

Please sign in to comment.