Skip to content

Commit fa2249c

Browse files
Events (#28)
* Implement Events/Event Record Type * Add MetadataV8 * Add reflection to Event decoding * Refactor metadata functions as methods
1 parent b4c779a commit fa2249c

13 files changed

Lines changed: 979 additions & 122 deletions

types/event_record.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls
2+
//
3+
// Copyright 2019 Centrifuge GmbH
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package types
18+
19+
import (
20+
"bytes"
21+
"errors"
22+
"fmt"
23+
"reflect"
24+
25+
"github.com/centrifuge/go-substrate-rpc-client/scale"
26+
)
27+
28+
// EventRecordsRaw is a raw record for a set of events, represented as the raw bytes. It exists since
29+
// decoding of events can only be done with metadata, so events can't follow the static way of decoding
30+
// other types do. It exposes functions to decode events using metadata and targets.
31+
type EventRecordsRaw []byte
32+
33+
type EventSystemExtrinsicSuccess struct {
34+
Phase Phase
35+
Topics []Hash
36+
}
37+
type EventSystemExtrinsicFailed struct {
38+
Phase Phase
39+
DispatchError DispatchError
40+
Topics []Hash
41+
}
42+
43+
type EventBalancesTransfer struct {
44+
Phase Phase
45+
From AccountID
46+
To AccountID
47+
Value U128
48+
Fees U128
49+
Topics []Hash
50+
}
51+
type EventIndicesNewAccountIndex struct {
52+
Phase Phase
53+
AccountID AccountID
54+
AccountIndex AccountIndex
55+
Topics []Hash
56+
}
57+
type EventBalancesNewAccount struct {
58+
Phase Phase
59+
AccountID AccountID
60+
Balance U128
61+
Topics []Hash
62+
}
63+
type EventBalancesReapedAccount struct {
64+
Phase Phase
65+
AccountID AccountID
66+
Topics []Hash
67+
}
68+
type EventSessionNewSession struct {
69+
Phase Phase
70+
SessionIndex U32
71+
Topics []Hash
72+
}
73+
74+
// EventRecords is a default set of possible event records that can be used as a target for
75+
// `func (e EventRecordsRaw) Decode(...`
76+
type EventRecords struct {
77+
System_ExtrinsicSuccess []EventSystemExtrinsicSuccess //nolint:stylecheck,golint
78+
System_ExtrinsicFailed []EventSystemExtrinsicFailed //nolint:stylecheck,golint
79+
Indices_NewAccountIndex []EventIndicesNewAccountIndex //nolint:stylecheck,golint
80+
Balances_NewAccount []EventBalancesNewAccount //nolint:stylecheck,golint
81+
Balances_ReapedAccount []EventBalancesReapedAccount //nolint:stylecheck,golint
82+
Balances_Transfer []EventBalancesTransfer //nolint:stylecheck,golint
83+
Session_NewSession []EventSessionNewSession //nolint:stylecheck,golint
84+
}
85+
86+
// Decode decodes the events from an EventRecordRaw into a target t using the given Metadata m
87+
func (e EventRecordsRaw) Decode(m *Metadata, t interface{}) error {
88+
// ensure t is a pointer
89+
ttyp := reflect.TypeOf(t)
90+
if ttyp.Kind() != reflect.Ptr {
91+
return errors.New("target must be a pointer, but is " + fmt.Sprint(ttyp))
92+
}
93+
// ensure t is not a nil pointer
94+
tval := reflect.ValueOf(t)
95+
if tval.IsNil() {
96+
return errors.New("target is a nil pointer")
97+
}
98+
val := tval.Elem()
99+
typ := val.Type()
100+
// ensure val can be set
101+
if !val.CanSet() {
102+
return fmt.Errorf("unsettable value %v", typ)
103+
}
104+
// ensure val points to a struct
105+
if val.Kind() != reflect.Struct {
106+
return fmt.Errorf("target must point to a struct, but is " + fmt.Sprint(typ))
107+
}
108+
109+
decoder := scale.NewDecoder(bytes.NewReader(e))
110+
111+
// determine number of events
112+
n, err := decoder.DecodeUintCompact()
113+
if err != nil {
114+
return err
115+
}
116+
117+
// iterate over events
118+
for i := uint64(0); i < n; i++ {
119+
// decode EventID
120+
id := EventID{}
121+
err := decoder.Decode(&id)
122+
if err != nil {
123+
return fmt.Errorf("unable to decode EventID for event #%v: %v", i, err)
124+
}
125+
126+
// ask metadata for method & event name for event
127+
moduleName, eventName, err := m.FindEventNamesForEventID(id)
128+
// moduleName, eventName, err := "System", "ExtrinsicSuccess", nil
129+
if err != nil {
130+
return fmt.Errorf("unable to find event with EventID %v in metadata for event #%v", id, i)
131+
}
132+
133+
// check whether name for eventID exists in t
134+
field := val.FieldByName(fmt.Sprintf("%v_%v", moduleName, eventName))
135+
if !field.IsValid() {
136+
return fmt.Errorf("unable to find field %v_%v for event #%v with EventID %v", moduleName, eventName, i, id)
137+
}
138+
139+
// create a pointer to with the correct type that will hold the decoded event
140+
holder := reflect.New(field.Type().Elem())
141+
err = decoder.Decode(holder.Interface())
142+
if err != nil {
143+
return fmt.Errorf("unable to decode event #%v with EventID %v, field %v_%v: %v", i, id, moduleName,
144+
eventName, err)
145+
}
146+
147+
// add the decoded event to the slice
148+
field.Set(reflect.Append(field, holder.Elem()))
149+
150+
// fmt.Println("Slice type", field.Type().Elem())
151+
}
152+
return nil
153+
}
154+
155+
// Phase is an enum describing the current phase of the event (applying the extrinsic or finalized)
156+
type Phase struct {
157+
IsApplyExtrinsic bool
158+
AsApplyExtrinsic uint32
159+
IsFinalization bool
160+
}
161+
162+
func (p *Phase) Decode(decoder scale.Decoder) error {
163+
b, err := decoder.ReadOneByte()
164+
if err != nil {
165+
return err
166+
}
167+
168+
if b == 0 {
169+
p.IsApplyExtrinsic = true
170+
err = decoder.Decode(&p.AsApplyExtrinsic)
171+
} else if b == 1 {
172+
p.IsFinalization = true
173+
}
174+
175+
if err != nil {
176+
return err
177+
}
178+
179+
return nil
180+
}
181+
182+
func (p Phase) Encode(encoder scale.Encoder) error {
183+
var err1, err2 error
184+
if p.IsApplyExtrinsic {
185+
err1 = encoder.PushByte(0)
186+
err2 = encoder.Encode(p.AsApplyExtrinsic)
187+
} else if p.IsFinalization {
188+
err1 = encoder.PushByte(1)
189+
}
190+
191+
if err1 != nil {
192+
return err1
193+
}
194+
if err2 != nil {
195+
return err2
196+
}
197+
198+
return nil
199+
}
200+
201+
// DispatchError is an error occurring during extrinsic dispatch
202+
type DispatchError struct {
203+
HasModule bool
204+
Module uint8
205+
Error uint8
206+
}
207+
208+
func (d *DispatchError) Decode(decoder scale.Decoder) error {
209+
b, err := decoder.ReadOneByte()
210+
if err != nil {
211+
return err
212+
}
213+
214+
if b == 1 {
215+
d.HasModule = true
216+
err = decoder.Decode(&d.Module)
217+
}
218+
if err != nil {
219+
return err
220+
}
221+
222+
return decoder.Decode(&d.Error)
223+
}
224+
225+
func (d DispatchError) Encode(encoder scale.Encoder) error {
226+
var err error
227+
if d.HasModule {
228+
err = encoder.PushByte(1)
229+
if err != nil {
230+
return err
231+
}
232+
err = encoder.Encode(d.Module)
233+
} else {
234+
err = encoder.PushByte(0)
235+
}
236+
237+
if err != nil {
238+
return err
239+
}
240+
241+
return encoder.Encode(&d.Error)
242+
}
243+
244+
type EventID [2]byte

types/event_record_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Go Substrate RPC Client (GSRPC) provides APIs and types around Polkadot and any Substrate-based chain RPC calls
2+
// Copyright (C) 2019 Centrifuge GmbH
3+
//
4+
// This file is part of Go Substrate RPC Client (GSRPC).
5+
//
6+
// GSRPC is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU General Public License as published by
8+
// the Free Software Foundation, either version 3 of the License, or
9+
// (at your option) any later version.
10+
//
11+
// GSRPC is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
package types_test
20+
21+
import (
22+
"fmt"
23+
"testing"
24+
25+
. "github.com/centrifuge/go-substrate-rpc-client/types"
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
var examplePhaseApp = Phase{
30+
IsApplyExtrinsic: true,
31+
AsApplyExtrinsic: 42,
32+
}
33+
34+
var examplePhaseFin = Phase{
35+
IsFinalization: true,
36+
}
37+
38+
var exampleEventApp = EventSystemExtrinsicSuccess{
39+
Phase: examplePhaseApp,
40+
Topics: []Hash{{1, 2}},
41+
}
42+
43+
var exampleEventFin = EventSystemExtrinsicSuccess{
44+
Phase: examplePhaseFin,
45+
Topics: []Hash{{1, 2}},
46+
}
47+
48+
var exampleEventFinEnc = []byte{0x1, 0x4, 0x1, 0x2, 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} //nolint:lll
49+
50+
func TestEventSystemExtrinsicSuccess_EncodedLength(t *testing.T) {
51+
assertEncodedLength(t, []encodedLengthAssert{
52+
{exampleEventApp, 38},
53+
{exampleEventFin, 34},
54+
})
55+
}
56+
57+
func TestEventSystemExtrinsicSuccess_Encode(t *testing.T) {
58+
encoded, err := EncodeToBytes(exampleEventFin)
59+
assert.NoError(t, err)
60+
assert.Equal(t, exampleEventFinEnc, encoded)
61+
}
62+
63+
func TestEventSystemExtrinsicSuccess_Decode(t *testing.T) {
64+
decoded := EventSystemExtrinsicSuccess{}
65+
err := DecodeFromBytes(exampleEventFinEnc, &decoded)
66+
assert.NoError(t, err)
67+
assert.Equal(t, exampleEventFin, decoded)
68+
}
69+
70+
func TestEventSystemExtrinsicSuccess_Hash(t *testing.T) {
71+
assertHash(t, []hashAssert{
72+
{exampleEventFin, MustHexDecodeString(
73+
"0xfb1a0568e74c9e2ed9ec6a7cca8b680a24ca442e5cf391ca6d863e3b35a4c962")},
74+
})
75+
}
76+
77+
func ExampleEventRecordsRaw_Decode() {
78+
e := EventRecordsRaw(MustHexDecodeString("0x100000000000000000000100000000000000020000000302d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48266d00000000000000000000000000000010a5d4e80000000000000000000000000002000000000000")) //nolint:lll
79+
80+
events := EventRecords{}
81+
err := e.Decode(ExamplaryMetadataV8, &events)
82+
if err != nil {
83+
panic(err)
84+
}
85+
86+
fmt.Printf("Got %v System_ExtrinsicSuccess events\n", len(events.System_ExtrinsicSuccess))
87+
fmt.Printf("Got %v System_ExtrinsicFailed events\n", len(events.System_ExtrinsicFailed))
88+
fmt.Printf("Got %v Indices_NewAccountIndex events\n", len(events.Indices_NewAccountIndex))
89+
fmt.Printf("Got %v Balances_Transfer events\n", len(events.Balances_Transfer))
90+
t := events.Balances_Transfer[0]
91+
fmt.Printf("Transfer: %v tokens from %#x to\n%#x with a fee of %v", t.Value, t.From, t.To, t.Fees)
92+
93+
// Output: Got 1 System_ExtrinsicSuccess events
94+
// Got 1 System_ExtrinsicFailed events
95+
// Got 1 Indices_NewAccountIndex events
96+
// Got 1 Balances_Transfer events
97+
// Transfer: 109 tokens from 0x3593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8e to
98+
// 0xaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4826 with a fee of 3906250000
99+
}
100+
101+
func TestDispatchError(t *testing.T) {
102+
assertRoundtrip(t, DispatchError{HasModule: true, Module: 0xf1, Error: 0xa2})
103+
assertRoundtrip(t, DispatchError{HasModule: false, Error: 0xa2})
104+
}

0 commit comments

Comments
 (0)