diff --git a/ledger/allegra.go b/ledger/allegra.go index cfd4f566..19e18e2b 100644 --- a/ledger/allegra.go +++ b/ledger/allegra.go @@ -166,6 +166,10 @@ func (t AllegraTransaction) CollateralReturn() TransactionOutput { return t.Body.CollateralReturn() } +func (t AllegraTransaction) Certificates() []Certificate { + return t.Body.Certificates() +} + func (t AllegraTransaction) Metadata() *cbor.Value { return t.TxMetadata } diff --git a/ledger/alonzo.go b/ledger/alonzo.go index 5a5cd842..1b04e52d 100644 --- a/ledger/alonzo.go +++ b/ledger/alonzo.go @@ -276,6 +276,10 @@ func (t AlonzoTransaction) CollateralReturn() TransactionOutput { return t.Body.CollateralReturn() } +func (t AlonzoTransaction) Certificates() []Certificate { + return t.Body.Certificates() +} + func (t AlonzoTransaction) Metadata() *cbor.Value { return t.TxMetadata } diff --git a/ledger/babbage.go b/ledger/babbage.go index b2596c42..d4b781c0 100644 --- a/ledger/babbage.go +++ b/ledger/babbage.go @@ -448,6 +448,10 @@ func (t BabbageTransaction) CollateralReturn() TransactionOutput { return t.Body.CollateralReturn() } +func (t BabbageTransaction) Certificates() []Certificate { + return t.Body.Certificates() +} + func (t BabbageTransaction) Metadata() *cbor.Value { return t.TxMetadata } diff --git a/ledger/byron.go b/ledger/byron.go index c03e1668..cd451ba9 100644 --- a/ledger/byron.go +++ b/ledger/byron.go @@ -185,6 +185,11 @@ func (t *ByronTransaction) CollateralReturn() TransactionOutput { return nil } +func (t *ByronTransaction) Certificates() []Certificate { + // No certificates in Byron + return nil +} + func (t *ByronTransaction) Metadata() *cbor.Value { return t.Attributes } diff --git a/ledger/certs.go b/ledger/certs.go new file mode 100644 index 00000000..7095b618 --- /dev/null +++ b/ledger/certs.go @@ -0,0 +1,451 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ledger + +import ( + "fmt" + "net" + + "github.com/blinklabs-io/gouroboros/cbor" + utxorpc "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" +) + +const ( + CertificateTypeStakeRegistration = 0 + CertificateTypeStakeDeregistration = 1 + CertificateTypeStakeDelegation = 2 + CertificateTypePoolRegistration = 3 + CertificateTypePoolRetirement = 4 + CertificateTypeGenesisKeyDelegation = 5 + CertificateTypeMoveInstantaneousRewards = 6 +) + +type CertificateWrapper struct { + Type uint + Certificate Certificate +} + +func (c *CertificateWrapper) UnmarshalCBOR(data []byte) error { + // Determine cert type + certType, err := cbor.DecodeIdFromList(data) + if err != nil { + return err + } + var tmpCert Certificate + switch certType { + case CertificateTypeStakeRegistration: + tmpCert = &StakeRegistrationCertificate{} + case CertificateTypeStakeDeregistration: + tmpCert = &StakeDeregistrationCertificate{} + case CertificateTypeStakeDelegation: + tmpCert = &StakeDelegationCertificate{} + case CertificateTypePoolRegistration: + tmpCert = &PoolRegistrationCertificate{} + case CertificateTypePoolRetirement: + tmpCert = &PoolRetirementCertificate{} + case CertificateTypeGenesisKeyDelegation: + tmpCert = &GenesisKeyDelegationCertificate{} + case CertificateTypeMoveInstantaneousRewards: + tmpCert = &MoveInstantaneousRewardsCertificate{} + default: + return fmt.Errorf("unknown certificate type: %d", certType) + } + // Decode cert + if _, err := cbor.Decode(data, tmpCert); err != nil { + return err + } + c.Type = uint(certType) + c.Certificate = tmpCert + return nil +} + +func (c *CertificateWrapper) MarshalCBOR() ([]byte, error) { + return cbor.Encode(c.Certificate) +} + +type Certificate interface { + isCertificate() + Cbor() []byte + Utxorpc() *utxorpc.Certificate +} + +const ( + StakeCredentialTypeAddrKeyHash = 0 + StakeCredentialTypeScriptHash = 1 +) + +type StakeCredential struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CredType uint + StakeCredential []byte +} + +func (c *StakeCredential) Utxorpc() *utxorpc.StakeCredential { + ret := &utxorpc.StakeCredential{} + switch c.CredType { + case StakeCredentialTypeAddrKeyHash: + ret.StakeCredential = &utxorpc.StakeCredential_AddrKeyHash{ + AddrKeyHash: c.StakeCredential[:], + } + case StakeCredentialTypeScriptHash: + ret.StakeCredential = &utxorpc.StakeCredential_ScriptHash{ + ScriptHash: c.StakeCredential[:], + } + } + return ret +} + +type StakeRegistrationCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + StakeRegistration StakeCredential +} + +func (c StakeRegistrationCertificate) isCertificate() {} + +func (c *StakeRegistrationCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *StakeRegistrationCertificate) Utxorpc() *utxorpc.Certificate { + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_StakeRegistration{ + StakeRegistration: c.StakeRegistration.Utxorpc(), + }, + } +} + +type StakeDeregistrationCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + StakeDeregistration StakeCredential +} + +func (c StakeDeregistrationCertificate) isCertificate() {} + +func (c *StakeDeregistrationCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *StakeDeregistrationCertificate) Utxorpc() *utxorpc.Certificate { + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_StakeDeregistration{ + StakeDeregistration: c.StakeDeregistration.Utxorpc(), + }, + } +} + +type StakeDelegationCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + StakeCredential *StakeCredential + PoolKeyHash PoolKeyHash +} + +func (c StakeDelegationCertificate) isCertificate() {} + +func (c *StakeDelegationCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *StakeDelegationCertificate) Utxorpc() *utxorpc.Certificate { + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_StakeDelegation{ + StakeDelegation: &utxorpc.StakeDelegationCert{ + StakeCredential: c.StakeCredential.Utxorpc(), + PoolKeyhash: c.PoolKeyHash[:], + }, + }, + } +} + +type AddrKeyHash Blake2b224 +type PoolKeyHash Blake2b224 +type PoolMetadataHash Blake2b256 +type VrfKeyHash Blake2b256 + +type PoolMetadata struct { + cbor.StructAsArray + Url string + Hash PoolMetadataHash +} + +func (p *PoolMetadata) Utxorpc() *utxorpc.PoolMetadata { + return &utxorpc.PoolMetadata{ + Url: p.Url, + Hash: p.Hash[:], + } +} + +const ( + PoolRelayTypeSingleHostAddress = 0 + PoolRelayTypeSingleHostName = 1 + PoolRelayTypeMultiHostName = 2 +) + +type PoolRelay struct { + Type int + Port *uint32 + Ipv4 *net.IP + Ipv6 *net.IP + Hostname *string +} + +func (p *PoolRelay) UnmarshalCBOR(data []byte) error { + tmpId, err := cbor.DecodeIdFromList(data) + if err != nil { + return err + } + p.Type = tmpId + switch tmpId { + case PoolRelayTypeSingleHostAddress: + var tmpData struct { + cbor.StructAsArray + Type uint + Port *uint32 + Ipv4 *net.IP + Ipv6 *net.IP + } + if _, err := cbor.Decode(data, &tmpData); err != nil { + return err + } + p.Port = tmpData.Port + p.Ipv4 = tmpData.Ipv4 + p.Ipv6 = tmpData.Ipv6 + case PoolRelayTypeSingleHostName: + var tmpData struct { + cbor.StructAsArray + Type uint + Port *uint32 + Hostname *string + } + if _, err := cbor.Decode(data, &tmpData); err != nil { + return err + } + p.Port = tmpData.Port + p.Hostname = tmpData.Hostname + case PoolRelayTypeMultiHostName: + var tmpData struct { + cbor.StructAsArray + Type uint + Hostname *string + } + if _, err := cbor.Decode(data, &tmpData); err != nil { + return err + } + p.Hostname = tmpData.Hostname + default: + return fmt.Errorf("invalid relay type: %d", tmpId) + } + return nil +} + +func (p *PoolRelay) Utxorpc() *utxorpc.Relay { + ret := &utxorpc.Relay{} + if p.Port != nil { + ret.Port = *p.Port + } + if p.Ipv4 != nil { + ret.IpV4 = []byte(*p.Ipv4) + } + if p.Ipv6 != nil { + ret.IpV6 = []byte(*p.Ipv6) + } + if p.Port != nil { + ret.Port = *p.Port + } + return ret +} + +type PoolRegistrationCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + Operator PoolKeyHash + VrfKeyHash VrfKeyHash + Pledge uint64 + Cost uint64 + Margin cbor.Rat + RewardAccount AddrKeyHash + PoolOwners []AddrKeyHash + Relays []PoolRelay + PoolMetadata *PoolMetadata +} + +func (c PoolRegistrationCertificate) isCertificate() {} + +func (c *PoolRegistrationCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *PoolRegistrationCertificate) Utxorpc() *utxorpc.Certificate { + tmpPoolOwners := make([][]byte, len(c.PoolOwners)) + for i, owner := range c.PoolOwners { + tmpPoolOwners[i] = owner[:] + } + tmpRelays := make([]*utxorpc.Relay, len(c.Relays)) + for i, relay := range c.Relays { + tmpRelays[i] = relay.Utxorpc() + } + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_PoolRegistration{ + PoolRegistration: &utxorpc.PoolRegistrationCert{ + Operator: c.Operator[:], + VrfKeyhash: c.VrfKeyHash[:], + Pledge: c.Pledge, + Cost: c.Cost, + Margin: &utxorpc.RationalNumber{ + Numerator: int32(c.Margin.Num().Int64()), + Denominator: uint32(c.Margin.Denom().Uint64()), + }, + RewardAccount: c.RewardAccount[:], + PoolOwners: tmpPoolOwners, + Relays: tmpRelays, + PoolMetadata: c.PoolMetadata.Utxorpc(), + }, + }, + } +} + +type PoolRetirementCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + PoolKeyHash PoolKeyHash + Epoch uint64 +} + +func (c PoolRetirementCertificate) isCertificate() {} + +func (c *PoolRetirementCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *PoolRetirementCertificate) Utxorpc() *utxorpc.Certificate { + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_PoolRetirement{ + PoolRetirement: &utxorpc.PoolRetirementCert{ + PoolKeyhash: c.PoolKeyHash[:], + Epoch: c.Epoch, + }, + }, + } +} + +type GenesisKeyDelegationCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + GenesisHash []byte + GenesisDelegateHash []byte + VrfKeyHash VrfKeyHash +} + +func (c GenesisKeyDelegationCertificate) isCertificate() {} + +func (c *GenesisKeyDelegationCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *GenesisKeyDelegationCertificate) Utxorpc() *utxorpc.Certificate { + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_GenesisKeyDelegation{ + GenesisKeyDelegation: &utxorpc.GenesisKeyDelegationCert{ + GenesisHash: c.GenesisHash[:], + GenesisDelegateHash: c.GenesisDelegateHash[:], + VrfKeyhash: c.VrfKeyHash[:], + }, + }, + } +} + +type MirSource int32 + +const ( + MirSourceUnspecified MirSource = 0 + MirSourceReserves MirSource = 1 + MirSourceTreasury MirSource = 2 +) + +type MoveInstantaneousRewardsCertificateReward struct { + Source uint + Rewards map[*StakeCredential]uint64 + OtherPot uint64 +} + +func (r *MoveInstantaneousRewardsCertificateReward) UnmarshalCBOR(data []byte) error { + // Try to parse as map + tmpMapData := struct { + cbor.StructAsArray + Source uint + Rewards map[*StakeCredential]uint64 + }{} + if _, err := cbor.Decode(data, &tmpMapData); err == nil { + r.Rewards = tmpMapData.Rewards + r.Source = tmpMapData.Source + return nil + } + // Try to parse as coin + tmpCoinData := struct { + cbor.StructAsArray + Source uint + Coin uint64 + }{} + if _, err := cbor.Decode(data, &tmpCoinData); err == nil { + r.OtherPot = tmpCoinData.Coin + r.Source = tmpMapData.Source + return nil + } + return fmt.Errorf("failed to decode as known types") +} + +type MoveInstantaneousRewardsCertificate struct { + cbor.StructAsArray + cbor.DecodeStoreCbor + CertType uint + Reward MoveInstantaneousRewardsCertificateReward +} + +func (c MoveInstantaneousRewardsCertificate) isCertificate() {} + +func (c *MoveInstantaneousRewardsCertificate) UnmarshalCBOR(cborData []byte) error { + return c.UnmarshalCbor(cborData, c) +} + +func (c *MoveInstantaneousRewardsCertificate) Utxorpc() *utxorpc.Certificate { + var tmpMirTargets []*utxorpc.MirTarget + for stakeCred, deltaCoin := range c.Reward.Rewards { + tmpMirTargets = append( + tmpMirTargets, + &utxorpc.MirTarget{ + StakeCredential: stakeCred.Utxorpc(), + DeltaCoin: int64(deltaCoin), + }, + ) + } + return &utxorpc.Certificate{ + Certificate: &utxorpc.Certificate_MirCert{ + MirCert: &utxorpc.MirCert{ + From: utxorpc.MirSource(c.Reward.Source), + To: tmpMirTargets, + OtherPot: c.Reward.OtherPot, + }, + }, + } +} diff --git a/ledger/conway.go b/ledger/conway.go index 3b4930ce..b2c70d9d 100644 --- a/ledger/conway.go +++ b/ledger/conway.go @@ -172,6 +172,10 @@ func (t ConwayTransaction) CollateralReturn() TransactionOutput { return t.Body.CollateralReturn() } +func (t ConwayTransaction) Certificates() []Certificate { + return t.Body.Certificates() +} + func (t ConwayTransaction) Metadata() *cbor.Value { return t.TxMetadata } diff --git a/ledger/mary.go b/ledger/mary.go index 85e2080b..94d71c24 100644 --- a/ledger/mary.go +++ b/ledger/mary.go @@ -183,6 +183,10 @@ func (t MaryTransaction) CollateralReturn() TransactionOutput { return t.Body.CollateralReturn() } +func (t MaryTransaction) Certificates() []Certificate { + return t.Body.Certificates() +} + func (t MaryTransaction) Metadata() *cbor.Value { return t.TxMetadata } diff --git a/ledger/shelley.go b/ledger/shelley.go index 08c41515..c8af6166 100644 --- a/ledger/shelley.go +++ b/ledger/shelley.go @@ -162,13 +162,12 @@ func (h *ShelleyBlockHeader) Era() Era { type ShelleyTransactionBody struct { cbor.DecodeStoreCbor - hash string - TxInputs []ShelleyTransactionInput `cbor:"0,keyasint,omitempty"` - TxOutputs []ShelleyTransactionOutput `cbor:"1,keyasint,omitempty"` - TxFee uint64 `cbor:"2,keyasint,omitempty"` - Ttl uint64 `cbor:"3,keyasint,omitempty"` - // TODO: figure out how to parse properly - Certificates cbor.RawMessage `cbor:"4,keyasint,omitempty"` + hash string + TxInputs []ShelleyTransactionInput `cbor:"0,keyasint,omitempty"` + TxOutputs []ShelleyTransactionOutput `cbor:"1,keyasint,omitempty"` + TxFee uint64 `cbor:"2,keyasint,omitempty"` + Ttl uint64 `cbor:"3,keyasint,omitempty"` + TxCertificates []CertificateWrapper `cbor:"4,keyasint,omitempty"` // TODO: figure out how to parse this correctly // We keep the raw CBOR because it can contain a map with []byte keys, which // Go does not allow @@ -231,6 +230,14 @@ func (b *ShelleyTransactionBody) CollateralReturn() TransactionOutput { return nil } +func (b *ShelleyTransactionBody) Certificates() []Certificate { + ret := make([]Certificate, len(b.TxCertificates)) + for i, cert := range b.TxCertificates { + ret[i] = cert.Certificate + } + return ret +} + func (b *ShelleyTransactionBody) Utxorpc() *utxorpc.Tx { var txi []*utxorpc.TxInput var txo []*utxorpc.TxOutput @@ -377,6 +384,10 @@ func (t ShelleyTransaction) CollateralReturn() TransactionOutput { return t.Body.CollateralReturn() } +func (t ShelleyTransaction) Certificates() []Certificate { + return t.Body.Certificates() +} + func (t ShelleyTransaction) Metadata() *cbor.Value { return t.TxMetadata } diff --git a/ledger/tx.go b/ledger/tx.go index f19fcc4d..43434ca6 100644 --- a/ledger/tx.go +++ b/ledger/tx.go @@ -43,6 +43,7 @@ type TransactionBody interface { ReferenceInputs() []TransactionInput Collateral() []TransactionInput CollateralReturn() TransactionOutput + Certificates() []Certificate Utxorpc() *utxorpc.Tx } diff --git a/ledger/verify_block_body.go b/ledger/verify_block_body.go index 162b62d2..42fe5f1c 100644 --- a/ledger/verify_block_body.go +++ b/ledger/verify_block_body.go @@ -190,44 +190,35 @@ func GetBlockOutput(txBodies []BabbageTransactionBody) ([]UTXOOutput, []RegisCer // We will only focus on: // pool_registration = (3, pool_params) // pool_retirement = (4, pool_keyhash, epoch) - txCertsBytes := tx.Certificates - if txCertsBytes != nil { - var certs []interface{} - _, err := cbor.Decode(txCertsBytes, &certs) - if err != nil { - return nil, nil, nil, fmt.Errorf("GetBlockOutput: decode txCertsBytes, tx index %v, error, %v", txIndex, err.Error()) - } - for certIndex, cert := range certs { - certBytes := cert.([]interface{}) - // For type like this, Haskell cbor will have an int flag at first byte, to detect which struct to be used - flagByte := certBytes[0].(uint64) - if flagByte == 3 { - poolIdBytes := certBytes[1].([]byte) - vrfKeyHashBytes := certBytes[2].([]byte) - vrfKeyHashHex := hex.EncodeToString(vrfKeyHashBytes) - poolId, poolIdError := PoolIdToBech32(poolIdBytes) - if poolIdError != nil { - return nil, nil, nil, fmt.Errorf("GetBlockOutput: RegisSPO => PoolIdToBech32 , tx index %v, cert index %v, error, %v", txIndex, certIndex, poolIdError.Error()) - } - regisCerts = append(regisCerts, RegisCert{ - RegisPoolId: poolId, - RegisPoolVrf: vrfKeyHashHex, - TxIndex: txIndex, - }) - } else if flagByte == 4 { - // pool_retirement - poolIdBytes := certBytes[1].([]byte) - poolId, poolIdError := PoolIdToBech32(poolIdBytes) - if poolIdError != nil { - return nil, nil, nil, fmt.Errorf("GetBlockOutput: RetireSPO => PoolIdToBech32, tx index %v, cert index %v, error, %v", txIndex, certIndex, poolIdError.Error()) - } - retireEpoch := certBytes[2].(uint64) - deRegisCerts = append(deRegisCerts, DeRegisCert{ - DeRegisPoolId: poolId, - DeRegisEpoch: strconv.FormatUint(retireEpoch, 10), - TxIndex: txIndex, - }) + for certIndex, cert := range tx.Certificates() { + switch v := cert.(type) { + case *PoolRegistrationCertificate: + poolIdBytes := v.Operator[:] + vrfKeyHashBytes := v.VrfKeyHash[:] + vrfKeyHashHex := hex.EncodeToString(vrfKeyHashBytes) + poolId, poolIdError := PoolIdToBech32(poolIdBytes) + if poolIdError != nil { + return nil, nil, nil, fmt.Errorf("GetBlockOutput: RegisSPO => PoolIdToBech32 , tx index %v, cert index %v, error, %v", txIndex, certIndex, poolIdError.Error()) + } + regisCerts = append(regisCerts, RegisCert{ + RegisPoolId: poolId, + RegisPoolVrf: vrfKeyHashHex, + TxIndex: txIndex, + }) + + case *PoolRetirementCertificate: + // pool_retirement + poolIdBytes := v.PoolKeyHash[:] + poolId, poolIdError := PoolIdToBech32(poolIdBytes) + if poolIdError != nil { + return nil, nil, nil, fmt.Errorf("GetBlockOutput: RetireSPO => PoolIdToBech32, tx index %v, cert index %v, error, %v", txIndex, certIndex, poolIdError.Error()) } + retireEpoch := v.Epoch + deRegisCerts = append(deRegisCerts, DeRegisCert{ + DeRegisPoolId: poolId, + DeRegisEpoch: strconv.FormatUint(retireEpoch, 10), + TxIndex: txIndex, + }) } } diff --git a/protocol/localstatequery/queries.go b/protocol/localstatequery/queries.go index 49d8c306..2db987bc 100644 --- a/protocol/localstatequery/queries.go +++ b/protocol/localstatequery/queries.go @@ -16,7 +16,6 @@ package localstatequery import ( "fmt" - "net" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/gouroboros/ledger" @@ -315,7 +314,7 @@ type StakePoolParamsResult struct { Margin *cbor.Rat RewardAccount ledger.Address PoolOwners []ledger.Blake2b224 - Relays []StakePoolParamsResultRelay + Relays []ledger.PoolRelay PoolMetadata *struct { cbor.StructAsArray Url string @@ -324,63 +323,6 @@ type StakePoolParamsResult struct { } } -type StakePoolParamsResultRelay struct { - Type int - Port *uint - Ipv4 *net.IP - Ipv6 *net.IP - Hostname *string -} - -func (s *StakePoolParamsResultRelay) UnmarshalCBOR(data []byte) error { - tmpId, err := cbor.DecodeIdFromList(data) - if err != nil { - return err - } - s.Type = tmpId - switch tmpId { - case 0: - var tmpData struct { - cbor.StructAsArray - Type uint - Port *uint - Ipv4 *net.IP - Ipv6 *net.IP - } - if _, err := cbor.Decode(data, &tmpData); err != nil { - return err - } - s.Port = tmpData.Port - s.Ipv4 = tmpData.Ipv4 - s.Ipv6 = tmpData.Ipv6 - case 1: - var tmpData struct { - cbor.StructAsArray - Type uint - Port *uint - Hostname *string - } - if _, err := cbor.Decode(data, &tmpData); err != nil { - return err - } - s.Port = tmpData.Port - s.Hostname = tmpData.Hostname - case 2: - var tmpData struct { - cbor.StructAsArray - Type uint - Hostname *string - } - if _, err := cbor.Decode(data, &tmpData); err != nil { - return err - } - s.Hostname = tmpData.Hostname - default: - return fmt.Errorf("invalid relay type: %d", tmpId) - } - return nil -} - // TODO type RewardInfoPoolsResult interface{}