Skip to content

Commit

Permalink
Now supporting new EOSIO 2.0 models
Browse files Browse the repository at this point in the history
Some fields change to adapt for both 1.x and 2.x models:
- `BlockSigningKey` in `BlockState` has been renamed to `BlockSigningKeyV1` and now a `*ecc.PublicKey`
- `ValidBlockSigningAuthorityV2` has been added to `BlockState` replacing old `BlockSigningKey` and is of type `BlockSigningAuthority`
- `ActiveSchedule` in `BlockState` is now of type `*ProducerScheduleOrAuthoritySchedule`
- `PendingSchedule.Schedule` is now of type `*ProducerScheduleOrAuthoritySchedule`
- `BlockHeader` `NewProducers` has been renamed `NewProducersV1`. The EOSIO 2.x does not
  have this field and has been replaced by a block header extension. An extension contains
  a payload, when the `producer_schedule_change_extension` block header extension is
  present, it means we are dealing with a new set of producers, as such, replacing the
  old `BlockHeader.NewProducers` field.

Some other changes for proper support:
- Added BlockHeaderExtension interface with all known EOS BlockHeaderExtension concrete type
  as well as necessary methods to work with BlockHeaderExtension
- Added support for `UnmarshalerBinary` interface in Decoder, this is to support `Variant`
  decoding as well as an initial start to unclutter the `decoder.go` file.
- Added Variant binary decoding support.
- Proper support of decoding `eos` `optional` struct field without relying on each type
  having an `Optional<Type>` field.
  • Loading branch information
Matthieu Vachon committed Jan 22, 2020
1 parent 259db1d commit 053b601
Show file tree
Hide file tree
Showing 14 changed files with 767 additions and 96 deletions.
112 changes: 69 additions & 43 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@ package eos

import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math"

"time"

"errors"
"reflect"

"strings"

"io/ioutil"
"time"

"github.com/eoscanada/eos-go/ecc"
"go.uber.org/zap"
)

// UnmarshalerBinary is the interface implemented by types
// that can unmarshal an EOSIO binary description of themselves.
//
// **Warning** This is experimental, exposed only for internal usage for now.
type UnmarshalerBinary interface {
UnmarshalBinary(decoder *Decoder) error
}

var TypeSize = struct {
Byte int
Int8 int
Expand Down Expand Up @@ -98,28 +102,72 @@ func (d *Decoder) DecodeActions(decode bool) {
d.decodeActions = decode
}

func (d *Decoder) Decode(v interface{}) (err error) {
type DecodeOption = interface{}

type optionalFieldType bool

const OptionalField optionalFieldType = true

func (d *Decoder) Decode(v interface{}, options ...DecodeOption) (err error) {
optionalField := false
for _, option := range options {
if _, isOptionalField := option.(optionalFieldType); isOptionalField {
optionalField = true
}
}

rv := reflect.Indirect(reflect.ValueOf(v))
if !rv.CanAddr() {
return errors.New("decode, can only Decode to pointer type")
}
t := rv.Type()

if loggingEnabled {
decoderLog.Debug("decode type", typeField("type", v))
decoderLog.Debug("decode type", typeField("type", v), zap.Bool("optional", optionalField))
}

if !rv.CanAddr() {
return errors.New("binary: can only Decode to pointer type")
}

if optionalField {
isPresent, e := d.ReadByte()
if e != nil {
err = fmt.Errorf("decode: %t isPresent, %s", v, e)
return
}

if isPresent == 0 {
if loggingEnabled {
decoderLog.Debug("skipping optional", typeField("type", v))
}

rv.Set(reflect.Zero(t))
return
}
}

if t.Kind() == reflect.Ptr {
t = t.Elem()
newRV := reflect.New(t)
rv.Set(newRV)

// At this point, `newRV` is a pointer to our target type, we need to check here because
// after that, when `reflect.Indirect` is used, we get a `**<Type>` instead of a `*<Type>`
// which breaks the interface checking.
//
// Ultimetaly, I think this could should be re-written, I don't think the `**<Type>` is necessary.
if u, ok := newRV.Interface().(UnmarshalerBinary); ok {
if loggingEnabled {
decoderLog.Debug("using UnmarshalBinary method to decode type", typeField("type", v))
}
return u.UnmarshalBinary(d)
}

rv = reflect.Indirect(newRV)
}

switch realV := v.(type) {
switch v.(type) {
case *string:
s, e := d.ReadString()
if e != nil {
Expand Down Expand Up @@ -277,7 +325,6 @@ func (d *Decoder) Decode(v interface{}) (err error) {
asset, err = d.ReadAsset()
rv.Set(reflect.ValueOf(asset))
return

case *TransactionWithID:

t, e := d.ReadByte()
Expand Down Expand Up @@ -311,21 +358,6 @@ func (d *Decoder) Decode(v interface{}) (err error) {
return nil
}

case **OptionalProducerSchedule:
isPresent, e := d.ReadByte()
if e != nil {
err = fmt.Errorf("decode: OptionalProducerSchedule isPresent, %s", e)
return
}

if isPresent == 0 {
if loggingEnabled {
decoderLog.Debug("skipping optional OptionalProducerSchedule")
}
*realV = nil
return
}

case **Action:
err = d.decodeStruct(v, t, rv)
if err != nil {
Expand Down Expand Up @@ -445,7 +477,13 @@ func (d *Decoder) decodeStruct(v interface{}, t reflect.Type, rv reflect.Value)
if loggingEnabled {
decoderLog.Debug("field", zap.String("name", typeField.Name))
}
if err = d.Decode(iface); err != nil {

var options []DecodeOption
if tag == "optional" {
options = append(options, OptionalField)
}

if err = d.Decode(iface, options...); err != nil {
return
}
}
Expand Down Expand Up @@ -792,17 +830,17 @@ func (d *Decoder) readWAPublicKeyMaterial() (out []byte, err error) {
}

d.pos += 34
reminderDataSize, err := d.ReadUvarint32()
remainderDataSize, err := d.ReadUvarint32()
if err != nil {
return out, fmt.Errorf("unable to read public key WA key material size: %s", err)
}

if d.remaining() < int(reminderDataSize) {
err = fmt.Errorf("publicKey WA reminder key material requires [%d] bytes, remaining [%d]", reminderDataSize, d.remaining())
if d.remaining() < int(remainderDataSize) {
err = fmt.Errorf("publicKey WA remainder key material requires [%d] bytes, remaining [%d]", remainderDataSize, d.remaining())
return
}

d.pos += int(reminderDataSize)
d.pos += int(remainderDataSize)
keyMaterialSize := d.pos - begin

out = make([]byte, keyMaterialSize)
Expand Down Expand Up @@ -848,18 +886,6 @@ func (d *Decoder) ReadSignature() (out ecc.Signature, err error) {
decoderLog.Debug("read signature", zap.Stringer("sig", out))
}

// sigContent := make([]byte, 66)
// copy(sigContent, d.data[d.pos:d.pos+TypeSize.Signature])

// out, err = ecc.NewSignatureFromData(sigContent)
// if err != nil {
// return out, fmt.Errorf("new signature: %s", err)
// }

// d.pos += TypeSize.Signature
// if loggingEnabled {
// decoderLog.Debug("read signature", zap.Stringer("sig", out))
// }
return
}

Expand Down
2 changes: 1 addition & 1 deletion decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ func TestDecoder_SignedBlock_Full(t *testing.T) {
assert.Equal(t, "0000000000000000000000000000000000000000000000000000000000000000", signedBlock.TransactionMRoot.String())
assert.Equal(t, "6a46611d7b15f71ff42de916e19f8ed1011096178f81d9b17987637a545b1521", signedBlock.ActionMRoot.String())
assert.Equal(t, uint32(0), signedBlock.ScheduleVersion)
assert.Equal(t, (*OptionalProducerSchedule)(nil), signedBlock.NewProducers)
assert.Equal(t, (*ProducerSchedule)(nil), signedBlock.NewProducersV1)
assert.Equal(t, []*Extension{&Extension{uint16(0), expectedHeaderExtension}}, signedBlock.HeaderExtensions)
assert.Equal(t, "SIG_K1_K7cBDNuka9kLUNAGaCm4FpNTdJwVKY3rP3v2esU8RGv1KXNNDEEdrWBAJSH3cPB8t1478e4RmhjkP48Sbuaqkf6Z5iDZKW", signedBlock.ProducerSignature.String())
assert.Equal(t, []TransactionReceipt{}, signedBlock.Transactions)
Expand Down
3 changes: 3 additions & 0 deletions ecc/curve.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ecc

import "runtime/debug"

type CurveID uint8

const (
Expand All @@ -17,6 +19,7 @@ func (c CurveID) String() string {
case CurveWA:
return "WA"
default:
debug.PrintStack()
return "UN" // unknown
}
}
Expand Down
8 changes: 8 additions & 0 deletions ecc/pubkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type PublicKey struct {
inner innerPublicKey
}

func (p PublicKey) IsEmpty() bool {
return p.Curve == 0 && p.Content == nil && p.inner == nil
}

func NewPublicKeyFromData(data []byte) (out PublicKey, err error) {
if len(data) <= 0 {
return out, errors.New("data must have at least one byte, got 0")
Expand Down Expand Up @@ -157,6 +161,10 @@ func (p PublicKey) Key() (*btcec.PublicKey, error) {
var emptyKeyMaterial = make([]byte, 33)

func (p PublicKey) String() string {
if p.IsEmpty() {
return ""
}

data := p.Content
if len(data) == 0 {
// Nothing really to do, just output some garbage
Expand Down
9 changes: 1 addition & 8 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import (
"errors"
"fmt"
"io"
"math"
"reflect"
"time"

"math"

"github.com/eoscanada/eos-go/ecc"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -139,12 +138,6 @@ func (e *Encoder) Encode(v interface{}) (err error) {
return e.writeUint64(uint64(cv))
case Asset:
return e.writeAsset(cv)
// case *OptionalProducerSchedule:
// isPresent := cv != nil
// e.writeBool(isPresent)
// if isPresent {

// }
case ActionData:
return e.writeActionData(cv)
case *ActionData:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.13

require (
github.com/davecgh/go-spew v1.1.1
github.com/ethereum/go-ethereum v1.9.9 // indirect
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.4.0
github.com/tidwall/gjson v1.3.2
Expand Down
Loading

0 comments on commit 053b601

Please sign in to comment.