diff --git a/rpc/beefy/beefy.go b/rpc/beefy/beefy.go new file mode 100644 index 000000000..ba4981df6 --- /dev/null +++ b/rpc/beefy/beefy.go @@ -0,0 +1,31 @@ +// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls +// +// Copyright 2021 Snowfork +// +// 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 beefy + +import ( + "github.com/centrifuge/go-substrate-rpc-client/v4/client" +) + +// Beefy exposes methods for retrieval of chain data +type Beefy struct { + client client.Client +} + +// NewBeefy creates a new Chain struct +func NewBeefy(cl client.Client) Beefy { + return Beefy{cl} +} diff --git a/rpc/beefy/beefy_test.go b/rpc/beefy/beefy_test.go new file mode 100644 index 000000000..cd06dd630 --- /dev/null +++ b/rpc/beefy/beefy_test.go @@ -0,0 +1,20 @@ +package beefy + +import ( + "os" + "testing" + + "github.com/centrifuge/go-substrate-rpc-client/v4/client" + "github.com/centrifuge/go-substrate-rpc-client/v4/config" +) + +var beefy Beefy + +func TestMain(m *testing.M) { + cl, err := client.Connect(config.Default().RPCURL) + if err != nil { + panic(err) + } + beefy = NewBeefy(cl) + os.Exit(m.Run()) +} diff --git a/rpc/beefy/get_finalized_head.go b/rpc/beefy/get_finalized_head.go new file mode 100644 index 000000000..8ee045d9b --- /dev/null +++ b/rpc/beefy/get_finalized_head.go @@ -0,0 +1,33 @@ +// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls +// +// Copyright 2021 Snowfork +// +// 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 beefy + +import ( + "github.com/centrifuge/go-substrate-rpc-client/v4/types" +) + +// GetFinalizedHead returns the hash of the latest BEEFY block +func (b *Beefy) GetFinalizedHead() (types.Hash, error) { + var res string + + err := b.client.Call(&res, "beefy_getFinalizedHead") + if err != nil { + return types.Hash{}, err + } + + return types.NewHashFromHexString(res) +} diff --git a/rpc/beefy/get_finalized_head_test.go b/rpc/beefy/get_finalized_head_test.go new file mode 100644 index 000000000..f008e38b5 --- /dev/null +++ b/rpc/beefy/get_finalized_head_test.go @@ -0,0 +1,29 @@ +// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls +// +// Copyright 2019 Centrifuge GmbH +// +// 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 beefy + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBeefy_GetFinalizedHead(t *testing.T) { + t.Skip("API only available in Polkadot") + _, err := beefy.GetFinalizedHead() + assert.NoError(t, err) +} diff --git a/rpc/beefy/subscribe_justifications.go b/rpc/beefy/subscribe_justifications.go new file mode 100644 index 000000000..d05948eef --- /dev/null +++ b/rpc/beefy/subscribe_justifications.go @@ -0,0 +1,78 @@ +// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls +// +// Copyright 2021 Snowfork +// +// 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 beefy + +import ( + "context" + "sync" + + "github.com/centrifuge/go-substrate-rpc-client/v4/config" + gethrpc "github.com/centrifuge/go-substrate-rpc-client/v4/gethrpc" + "github.com/centrifuge/go-substrate-rpc-client/v4/types" +) + +// JustificationsSubscription is a subscription established through one of the Client's subscribe methods. +type JustificationsSubscription struct { + sub *gethrpc.ClientSubscription + channel chan types.SignedCommitment + quitOnce sync.Once // ensures quit is closed once +} + +// Chan returns the subscription channel. +// +// The channel is closed when Unsubscribe is called on the subscription. +func (s *JustificationsSubscription) Chan() <-chan types.SignedCommitment { + return s.channel +} + +// Err returns the subscription error channel. The intended use of Err is to schedule +// resubscription when the client connection is closed unexpectedly. +// +// The error channel receives a value when the subscription has ended due +// to an error. The received error is nil if Close has been called +// on the underlying client and no other error has occurred. +// +// The error channel is closed when Unsubscribe is called on the subscription. +func (s *JustificationsSubscription) Err() <-chan error { + return s.sub.Err() +} + +// Unsubscribe unsubscribes the notification and closes the error channel. +// It can safely be called more than once. +func (s *JustificationsSubscription) Unsubscribe() { + s.sub.Unsubscribe() + s.quitOnce.Do(func() { + close(s.channel) + }) +} + +// SubscribeJustifications subscribes beefy justifications, returning a subscription that will +// receive server notifications containing the Header. +func (b *Beefy) SubscribeJustifications() (*JustificationsSubscription, error) { + ctx, cancel := context.WithTimeout(context.Background(), config.Default().SubscribeTimeout) + defer cancel() + + ch := make(chan types.SignedCommitment) + + sub, err := b.client.Subscribe(ctx, "beefy", "subscribeJustifications", "unsubscribeJustifications", + "justifications", ch) + if err != nil { + return nil, err + } + + return &JustificationsSubscription{sub: sub, channel: ch}, nil +} diff --git a/rpc/main.go b/rpc/main.go index 1a7877650..525990ab7 100644 --- a/rpc/main.go +++ b/rpc/main.go @@ -19,7 +19,9 @@ package rpc import ( "github.com/centrifuge/go-substrate-rpc-client/v4/client" "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/author" + "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/beefy" "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/chain" + "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/mmr" "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/offchain" "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/state" "github.com/centrifuge/go-substrate-rpc-client/v4/rpc/system" @@ -28,7 +30,9 @@ import ( type RPC struct { Author author.Author + Beefy beefy.Beefy Chain chain.Chain + MMR mmr.MMR Offchain offchain.Offchain State state.State System system.System @@ -47,7 +51,9 @@ func NewRPC(cl client.Client) (*RPC, error) { return &RPC{ Author: author.NewAuthor(cl), + Beefy: beefy.NewBeefy(cl), Chain: chain.NewChain(cl), + MMR: mmr.NewMMR(cl), Offchain: offchain.NewOffchain(cl), State: st, System: system.NewSystem(cl), diff --git a/rpc/mmr/generate_proof.go b/rpc/mmr/generate_proof.go new file mode 100644 index 000000000..4faafe269 --- /dev/null +++ b/rpc/mmr/generate_proof.go @@ -0,0 +1,27 @@ +package mmr + +import ( + "github.com/centrifuge/go-substrate-rpc-client/v4/client" + "github.com/centrifuge/go-substrate-rpc-client/v4/types" +) + +// GenerateProof retrieves a MMR proof and leaf for the specified leave index, at the given blockHash (useful to query a +// proof at an earlier block, likely with antoher MMR root) +func (c *MMR) GenerateProof(leafIndex uint64, blockHash types.Hash) (types.GenerateMMRProofResponse, error) { + return c.generateProof(leafIndex, &blockHash) +} + +// GenerateProofLatest retrieves the latest MMR proof and leaf for the specified leave index +func (c *MMR) GenerateProofLatest(leafIndex uint64) (types.GenerateMMRProofResponse, error) { + return c.generateProof(leafIndex, nil) +} + +func (c *MMR) generateProof(leafIndex uint64, blockHash *types.Hash) (types.GenerateMMRProofResponse, error) { + var res types.GenerateMMRProofResponse + err := client.CallWithBlockHash(c.client, &res, "mmr_generateProof", blockHash, leafIndex) + if err != nil { + return types.GenerateMMRProofResponse{}, err + } + + return res, nil +} diff --git a/rpc/mmr/mmr.go b/rpc/mmr/mmr.go new file mode 100644 index 000000000..5734747d6 --- /dev/null +++ b/rpc/mmr/mmr.go @@ -0,0 +1,13 @@ +package mmr + +import "github.com/centrifuge/go-substrate-rpc-client/v4/client" + +// MMR exposes methods for retrieval of MMR data +type MMR struct { + client client.Client +} + +// NewMMR creates a new MMR struct +func NewMMR(c client.Client) MMR { + return MMR{client: c} +} diff --git a/teste2e/beefy_subscribe_justifications_test.go b/teste2e/beefy_subscribe_justifications_test.go new file mode 100644 index 000000000..693761302 --- /dev/null +++ b/teste2e/beefy_subscribe_justifications_test.go @@ -0,0 +1,59 @@ +// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls +// +// Copyright 2019 Centrifuge GmbH +// +// 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 teste2e + +import ( + "fmt" + "testing" + "time" + + gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4" + "github.com/centrifuge/go-substrate-rpc-client/v4/config" + "github.com/stretchr/testify/assert" +) + +func TestBeefy_SubscribeJustifications(t *testing.T) { + if testing.Short() { + t.Skip("skipping end-to-end test in short mode.") + } + + api, err := gsrpc.NewSubstrateAPI(config.Default().RPCURL) + assert.NoError(t, err) + + sub, err := api.RPC.Beefy.SubscribeJustifications() + assert.NoError(t, err) + defer sub.Unsubscribe() + + timeout := time.After(300 * time.Second) + received := 0 + + for { + select { + case commitment := <-sub.Chan(): + fmt.Printf("%#v\n", commitment) + + received++ + + if received >= 2 { + return + } + case <-timeout: + assert.FailNow(t, "timeout reached without getting 2 notifications from subscription") + return + } + } +} diff --git a/types/beefy.go b/types/beefy.go index 361941f58..2d63bc997 100644 --- a/types/beefy.go +++ b/types/beefy.go @@ -1,6 +1,7 @@ // Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls // // Copyright 2019 Centrifuge GmbH +// Copyright 2021 Snowfork // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,13 +17,21 @@ package types -import "github.com/centrifuge/go-substrate-rpc-client/v4/scale" +import ( + "github.com/centrifuge/go-substrate-rpc-client/v4/scale" +) + +// PayloadItem ... +type PayloadItem struct { + ID [2]byte + Data []byte +} // Commitment is a beefy commitment type Commitment struct { - Payload H256 - BlockNumber BlockNumber - ValidatorSetID U64 + Payload []PayloadItem + BlockNumber uint32 + ValidatorSetID uint64 } // SignedCommitment is a beefy commitment with optional signatures from the set of validators @@ -31,6 +40,18 @@ type SignedCommitment struct { Signatures []OptionBeefySignature } +type OptionalSignedCommitment struct { + option + value SignedCommitment +} + +type CompactSignedCommitment struct { + Commitment Commitment + SignaturesFrom []byte + ValidatorSetLen uint32 + SignaturesCompact []BeefySignature +} + // BeefySignature is a beefy signature type BeefySignature [65]byte @@ -74,3 +95,125 @@ func (o *OptionBeefySignature) SetNone() { func (o OptionBeefySignature) Unwrap() (ok bool, value BeefySignature) { return o.hasValue, o.value } + +// bits are packed into chunks of this size +const containerBitSize = 8 + +func (s *SignedCommitment) Decode(decoder scale.Decoder) error { + compact := CompactSignedCommitment{} + + err := decoder.Decode(&compact) + if err != nil { + return err + } + + var bits []byte + + for _, block := range compact.SignaturesFrom { + for bit := 0; bit < containerBitSize; bit++ { + bits = append(bits, (block>>(containerBitSize-bit-1))&1) + } + } + + bits = bits[0:compact.ValidatorSetLen] + + var signatures []OptionBeefySignature + sigIndex := 0 + + for _, bit := range bits { + if bit == 1 { + signatures = append(signatures, NewOptionBeefySignature(compact.SignaturesCompact[sigIndex])) + sigIndex++ + } else { + signatures = append(signatures, NewOptionBeefySignatureEmpty()) + } + } + + s.Commitment = compact.Commitment + s.Signatures = signatures + + return nil +} + +func (s SignedCommitment) Encode(encoder scale.Encoder) error { + var compact CompactSignedCommitment + var bits []byte + var signaturesFrom []byte + var signaturesCompact []BeefySignature + + validatorSetLen := len(s.Signatures) + + for _, optionSig := range s.Signatures { + if optionSig.IsSome() { + bits = append(bits, 1) + _, signature := optionSig.Unwrap() + signaturesCompact = append(signaturesCompact, signature) + } else { + bits = append(bits, 0) + } + } + + excessBitsLen := containerBitSize - (validatorSetLen % containerBitSize) + bits = append(bits, make([]byte, excessBitsLen)...) + + for _, chunk := range makeChunks(bits, containerBitSize) { + acc := chunk[0] + for i := 1; i < containerBitSize; i++ { + acc <<= 1 + acc |= chunk[i] + } + signaturesFrom = append(signaturesFrom, acc) + } + + compact.Commitment = s.Commitment + compact.SignaturesCompact = signaturesCompact + compact.SignaturesFrom = signaturesFrom + compact.ValidatorSetLen = uint32(validatorSetLen) + + return encoder.Encode(compact) +} + +func makeChunks(slice []byte, chunkSize int) [][]byte { + var chunks [][]byte + for i := 0; i < len(slice); i += chunkSize { + end := i + chunkSize + + // necessary check to avoid slicing beyond + // slice capacity + if end > len(slice) { + end = len(slice) + } + + chunks = append(chunks, slice[i:end]) + } + + return chunks +} + +// UnmarshalText deserializes hex string into a SignedCommitment. +// Used for decoding JSON-RPC subscription messages (beefy_subscribeJustifications) +func (s *SignedCommitment) UnmarshalText(text []byte) error { + return DecodeFromHex(string(text), s) +} + +func (o OptionalSignedCommitment) Encode(encoder scale.Encoder) error { + return encoder.EncodeOption(o.hasValue, o.value) +} + +func (o *OptionalSignedCommitment) Decode(decoder scale.Decoder) error { + return decoder.DecodeOption(&o.hasValue, &o.value) +} + +func (o OptionalSignedCommitment) Unwrap() (ok bool, value SignedCommitment) { + return o.hasValue, o.value +} + +func (o *OptionalSignedCommitment) SetSome(value SignedCommitment) { + o.hasValue = true + o.value = value +} + +func (o *OptionalSignedCommitment) SetNone() { + o.hasValue = false + o.value = SignedCommitment{} +} diff --git a/types/beefy_test.go b/types/beefy_test.go index a6cfd3a14..85a18166f 100644 --- a/types/beefy_test.go +++ b/types/beefy_test.go @@ -17,6 +17,7 @@ package types_test import ( + "fmt" "testing" fuzz "github.com/google/gofuzz" @@ -25,6 +26,9 @@ import ( "github.com/stretchr/testify/assert" ) +var sig1 = [65]byte{85, 132, 85, 173, 129, 39, 157, 240, 121, 92, 201, 133, 88, 14, 79, 183, 93, 114, 217, 72, 209, 16, 123, 42, 200, 10, 9, 171, 237, 77, 168, 72, 12, 116, 108, 195, 33, 242, 49, 154, 94, 153, 168, 48, 227, 20, 209, 13, 211, 205, 104, 206, 61, 192, 195, 60, 134, 233, 155, 203, 120, 22, 249, 186, 1} +var sig2 = [65]byte{45, 110, 31, 129, 5, 195, 55, 168, 108, 221, 154, 170, 205, 196, 150, 87, 127, 61, 184, 197, 94, 249, 230, 253, 72, 242, 197, 192, 90, 34, 116, 112, 116, 145, 99, 93, 139, 163, 223, 100, 243, 36, 87, 91, 123, 42, 52, 72, 123, 202, 35, 36, 182, 160, 4, 99, 149, 167, 22, 129, 190, 61, 12, 42, 0} + func TestBeefySignature(t *testing.T) { empty := NewOptionBeefySignatureEmpty() assert.True(t, empty.IsNone()) @@ -40,6 +44,122 @@ func TestBeefySignature(t *testing.T) { assertRoundtrip(t, sig) } +func makeCommitment() (*Commitment, error) { + data, err := Encode([]byte("Hello World!")) + if err != nil { + return nil, err + } + + payloadItem := PayloadItem{ + ID: [2]byte{'m', 'h'}, + Data: data, + } + + commitment := Commitment{ + Payload: []PayloadItem{payloadItem}, + BlockNumber: 5, + ValidatorSetID: 0, + } + + return &commitment, nil +} + +func makeLargeCommitment() (*Commitment, error) { + data := MustHexDecodeString("0xb5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c") + + payloadItem := PayloadItem{ + ID: [2]byte{'m', 'h'}, + Data: data, + } + + commitment := Commitment{ + Payload: []PayloadItem{payloadItem}, + BlockNumber: 5, + ValidatorSetID: 3, + } + + return &commitment, nil +} + +func TestCommitment_Encode(t *testing.T) { + c, err := makeCommitment() + assert.NoError(t, err) + assertEncode(t, []encodingAssert{ + {c, MustHexDecodeString("0x046d68343048656c6c6f20576f726c6421050000000000000000000000")}, + }) +} + +func TestLargeCommitment_Encode(t *testing.T) { + c, err := makeLargeCommitment() + assert.NoError(t, err) + fmt.Println(len(c.Payload[0].Data)) + fmt.Println(EncodeToHex(c)) +} + +func TestCommitment_Decode(t *testing.T) { + c, err := makeCommitment() + assert.NoError(t, err) + + assertDecode(t, []decodingAssert{ + { + input: MustHexDecodeString("0x046d68343048656c6c6f20576f726c6421050000000000000000000000"), + expected: *c, + }, + }) +} + +func TestCommitment_EncodeDecode(t *testing.T) { + c, err := makeCommitment() + assert.NoError(t, err) + + assertRoundtrip(t, *c) +} + +func TestSignedCommitment_Decode(t *testing.T) { + c, err := makeCommitment() + assert.NoError(t, err) + + s := SignedCommitment{ + Commitment: *c, + Signatures: []OptionBeefySignature{ + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignature(sig1), + NewOptionBeefySignature(sig2), + }, + } + + assertDecode(t, []decodingAssert{ + { + input: MustHexDecodeString("0x046d68343048656c6c6f20576f726c642105000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba012d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00"), + expected: s, + }, + }) +} + +func TestSignedCommitment_EncodeDecode(t *testing.T) { + c, err := makeCommitment() + assert.NoError(t, err) + + s := SignedCommitment{ + Commitment: *c, + Signatures: []OptionBeefySignature{ + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignature(sig1), + NewOptionBeefySignature(sig1), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignatureEmpty(), + NewOptionBeefySignature(sig1), + }, + } + + assertRoundtrip(t, s) +} + func TestBeefySignature_EncodeDecode(t *testing.T) { assertRoundTripFuzz[BeefySignature](t, 100) assertDecodeNilData[BeefySignature](t) diff --git a/types/mmr.go b/types/mmr.go new file mode 100644 index 000000000..6559368af --- /dev/null +++ b/types/mmr.go @@ -0,0 +1,77 @@ +package types + +import ( + "encoding/json" +) + +// GenerateMMRProofResponse contains the generate proof rpc response +type GenerateMMRProofResponse struct { + BlockHash H256 + Leaf MMRLeaf + Proof MMRProof +} + +// UnmarshalJSON fills u with the JSON encoded byte array given by b +func (d *GenerateMMRProofResponse) UnmarshalJSON(bz []byte) error { + var tmp struct { + BlockHash string `json:"blockHash"` + Leaf string `json:"leaf"` + Proof string `json:"proof"` + } + if err := json.Unmarshal(bz, &tmp); err != nil { + return err + } + err := DecodeFromHex(tmp.BlockHash, &d.BlockHash) + if err != nil { + return err + } + var encodedLeaf MMREncodableOpaqueLeaf + err = DecodeFromHex(tmp.Leaf, &encodedLeaf) + if err != nil { + return err + } + err = Decode(encodedLeaf, &d.Leaf) + if err != nil { + return err + } + err = DecodeFromHex(tmp.Proof, &d.Proof) + if err != nil { + return err + } + return nil +} + +type MMREncodableOpaqueLeaf Bytes + +// MMRProof is a MMR proof +type MMRProof struct { + // The index of the leaf the proof is for. + LeafIndex U64 + // Number of leaves in MMR, when the proof was generated. + LeafCount U64 + // Proof elements (hashes of siblings of inner nodes on the path to the leaf). + Items []H256 +} + +type MMRLeaf struct { + Version MMRLeafVersion + ParentNumberAndHash ParentNumberAndHash + BeefyNextAuthoritySet BeefyNextAuthoritySet + ParachainHeads H256 +} + +type MMRLeafVersion U8 + +type ParentNumberAndHash struct { + ParentNumber U32 + Hash Hash +} + +type BeefyNextAuthoritySet struct { + // ID + ID U64 + // Number of validators in the set. + Len U32 + // Merkle Root Hash build from BEEFY uncompressed AuthorityIds. + Root H256 +} diff --git a/types/mmr_test.go b/types/mmr_test.go new file mode 100644 index 000000000..cceabd5b6 --- /dev/null +++ b/types/mmr_test.go @@ -0,0 +1,40 @@ +// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls +// +// Copyright 2019 Centrifuge GmbH +// +// 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 types_test + +import ( + "encoding/json" + "testing" + + . "github.com/centrifuge/go-substrate-rpc-client/v4/types" +) + +func TestGenerateMMRProofResponse_Unmarshal(t *testing.T) { + jsonData := map[string]interface{}{"blockHash": "0x52d917b53796b671eb6f9f5497878cd7bacd1e8bafd41004687f67d2938b7b13", "leaf": "0xc50100d007000024724d3186265a4c1da058d532f7e806cb7b6d0c5be6eecf753f35addc53b54601000000000000000300000042b63941ec636f52303b3c33f53349830d8a466e9456d25d22b28f4bb0ad03650000000000000000000000000000000000000000000000000000000000000000", "proof": "0xd007000000000000d90800000000000030ef3c78f3febfafa319c77aadb0801099da03a775898f486392106a15688c0354c71aed1edc46767357f1f6dd8701f7974dd2ae39e6f178f9de1f6b8402683ef78f2048bf973a2f8b852a4b4997d0b8637111641d88a5ae8670260417a1f4ccfc2a5a56bc7517b04d9931c82a24660c8fa86fd33611b6964c3201669537d7851a17f814e37d71841d69412816692a8c3b6615572b799674c9b6879e547b1f6511af7be87ea122cb70f6da8718cb017acf56c0dc8fb0f093ed300dbbef98d632329b93aa08f39a2fd0cf2e51c12f8b0370b9f3e50d3395a20017e3f24991c9b603951ed76df7fad6c19cade303795345a7a96729fa337ae82d74b03f2b039ee5e3b0c34f63d8f8e43a0db754f4c0f42dc84d98a26c51edea931971206398a80f5ebee8c4ab19a7358adc89d2c4258fb61d979517d363ee33086cc5a9e63c7eed27674f1ca1d3585eb77c1c281ff21057dfdf89227dbe9f6a522753d26cfcf2f271e27f93422d112ef763d502970da640b416c1d3a04c9373b10beef676068ceeec"} + + marshalled, err := json.Marshal(jsonData) + if err != nil { + panic(err) + } + + expected := GenerateMMRProofResponse{BlockHash:H256{0x52, 0xd9, 0x17, 0xb5, 0x37, 0x96, 0xb6, 0x71, 0xeb, 0x6f, 0x9f, 0x54, 0x97, 0x87, 0x8c, 0xd7, 0xba, 0xcd, 0x1e, 0x8b, 0xaf, 0xd4, 0x10, 0x4, 0x68, 0x7f, 0x67, 0xd2, 0x93, 0x8b, 0x7b, 0x13}, Leaf:MMRLeaf{Version:0x0, ParentNumberAndHash:ParentNumberAndHash{ParentNumber:0x7d0, Hash:Hash{0x24, 0x72, 0x4d, 0x31, 0x86, 0x26, 0x5a, 0x4c, 0x1d, 0xa0, 0x58, 0xd5, 0x32, 0xf7, 0xe8, 0x6, 0xcb, 0x7b, 0x6d, 0xc, 0x5b, 0xe6, 0xee, 0xcf, 0x75, 0x3f, 0x35, 0xad, 0xdc, 0x53, 0xb5, 0x46}}, BeefyNextAuthoritySet:BeefyNextAuthoritySet{ID:0x1, Len:0x3, Root:H256{0x42, 0xb6, 0x39, 0x41, 0xec, 0x63, 0x6f, 0x52, 0x30, 0x3b, 0x3c, 0x33, 0xf5, 0x33, 0x49, 0x83, 0xd, 0x8a, 0x46, 0x6e, 0x94, 0x56, 0xd2, 0x5d, 0x22, 0xb2, 0x8f, 0x4b, 0xb0, 0xad, 0x3, 0x65}}, ParachainHeads:H256{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, Proof:MMRProof{LeafIndex:0x7d0, LeafCount:0x8d9, Items:[]H256{H256{0xef, 0x3c, 0x78, 0xf3, 0xfe, 0xbf, 0xaf, 0xa3, 0x19, 0xc7, 0x7a, 0xad, 0xb0, 0x80, 0x10, 0x99, 0xda, 0x3, 0xa7, 0x75, 0x89, 0x8f, 0x48, 0x63, 0x92, 0x10, 0x6a, 0x15, 0x68, 0x8c, 0x3, 0x54}, H256{0xc7, 0x1a, 0xed, 0x1e, 0xdc, 0x46, 0x76, 0x73, 0x57, 0xf1, 0xf6, 0xdd, 0x87, 0x1, 0xf7, 0x97, 0x4d, 0xd2, 0xae, 0x39, 0xe6, 0xf1, 0x78, 0xf9, 0xde, 0x1f, 0x6b, 0x84, 0x2, 0x68, 0x3e, 0xf7}, H256{0x8f, 0x20, 0x48, 0xbf, 0x97, 0x3a, 0x2f, 0x8b, 0x85, 0x2a, 0x4b, 0x49, 0x97, 0xd0, 0xb8, 0x63, 0x71, 0x11, 0x64, 0x1d, 0x88, 0xa5, 0xae, 0x86, 0x70, 0x26, 0x4, 0x17, 0xa1, 0xf4, 0xcc, 0xfc}, H256{0x2a, 0x5a, 0x56, 0xbc, 0x75, 0x17, 0xb0, 0x4d, 0x99, 0x31, 0xc8, 0x2a, 0x24, 0x66, 0xc, 0x8f, 0xa8, 0x6f, 0xd3, 0x36, 0x11, 0xb6, 0x96, 0x4c, 0x32, 0x1, 0x66, 0x95, 0x37, 0xd7, 0x85, 0x1a}, H256{0x17, 0xf8, 0x14, 0xe3, 0x7d, 0x71, 0x84, 0x1d, 0x69, 0x41, 0x28, 0x16, 0x69, 0x2a, 0x8c, 0x3b, 0x66, 0x15, 0x57, 0x2b, 0x79, 0x96, 0x74, 0xc9, 0xb6, 0x87, 0x9e, 0x54, 0x7b, 0x1f, 0x65, 0x11}, H256{0xaf, 0x7b, 0xe8, 0x7e, 0xa1, 0x22, 0xcb, 0x70, 0xf6, 0xda, 0x87, 0x18, 0xcb, 0x1, 0x7a, 0xcf, 0x56, 0xc0, 0xdc, 0x8f, 0xb0, 0xf0, 0x93, 0xed, 0x30, 0xd, 0xbb, 0xef, 0x98, 0xd6, 0x32, 0x32}, H256{0x9b, 0x93, 0xaa, 0x8, 0xf3, 0x9a, 0x2f, 0xd0, 0xcf, 0x2e, 0x51, 0xc1, 0x2f, 0x8b, 0x3, 0x70, 0xb9, 0xf3, 0xe5, 0xd, 0x33, 0x95, 0xa2, 0x0, 0x17, 0xe3, 0xf2, 0x49, 0x91, 0xc9, 0xb6, 0x3}, H256{0x95, 0x1e, 0xd7, 0x6d, 0xf7, 0xfa, 0xd6, 0xc1, 0x9c, 0xad, 0xe3, 0x3, 0x79, 0x53, 0x45, 0xa7, 0xa9, 0x67, 0x29, 0xfa, 0x33, 0x7a, 0xe8, 0x2d, 0x74, 0xb0, 0x3f, 0x2b, 0x3, 0x9e, 0xe5, 0xe3}, H256{0xb0, 0xc3, 0x4f, 0x63, 0xd8, 0xf8, 0xe4, 0x3a, 0xd, 0xb7, 0x54, 0xf4, 0xc0, 0xf4, 0x2d, 0xc8, 0x4d, 0x98, 0xa2, 0x6c, 0x51, 0xed, 0xea, 0x93, 0x19, 0x71, 0x20, 0x63, 0x98, 0xa8, 0xf, 0x5e}, H256{0xbe, 0xe8, 0xc4, 0xab, 0x19, 0xa7, 0x35, 0x8a, 0xdc, 0x89, 0xd2, 0xc4, 0x25, 0x8f, 0xb6, 0x1d, 0x97, 0x95, 0x17, 0xd3, 0x63, 0xee, 0x33, 0x8, 0x6c, 0xc5, 0xa9, 0xe6, 0x3c, 0x7e, 0xed, 0x27}, H256{0x67, 0x4f, 0x1c, 0xa1, 0xd3, 0x58, 0x5e, 0xb7, 0x7c, 0x1c, 0x28, 0x1f, 0xf2, 0x10, 0x57, 0xdf, 0xdf, 0x89, 0x22, 0x7d, 0xbe, 0x9f, 0x6a, 0x52, 0x27, 0x53, 0xd2, 0x6c, 0xfc, 0xf2, 0xf2, 0x71}, H256{0xe2, 0x7f, 0x93, 0x42, 0x2d, 0x11, 0x2e, 0xf7, 0x63, 0xd5, 0x2, 0x97, 0xd, 0xa6, 0x40, 0xb4, 0x16, 0xc1, 0xd3, 0xa0, 0x4c, 0x93, 0x73, 0xb1, 0xb, 0xee, 0xf6, 0x76, 0x6, 0x8c, 0xee, 0xec}}}} + + var unmarshalled GenerateMMRProofResponse + json.Unmarshal(marshalled, &unmarshalled) + + assertEqual(t, unmarshalled, expected) +}