Skip to content

Commit

Permalink
feat: catching up with JS version of ipfs-log
Browse files Browse the repository at this point in the history
* added entries v2
* added additional tests
  • Loading branch information
glouvigny committed Feb 7, 2020
1 parent 45d7ed6 commit d232dc6
Show file tree
Hide file tree
Showing 33 changed files with 2,834 additions and 1,047 deletions.
199 changes: 165 additions & 34 deletions entry/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@
package entry // import "berty.tech/go-ipfs-log/entry"

import (
"berty.tech/go-ipfs-log/identityprovider"
"berty.tech/go-ipfs-log/iface"
"berty.tech/go-ipfs-log/io"
"context"
"encoding/hex"
"encoding/json"
"math"
"sort"

"github.com/ipfs/go-cid"
cbornode "github.com/ipfs/go-ipld-cbor"
"github.com/multiformats/go-multibase"
"github.com/pkg/errors"
"github.com/polydawn/refmt/obj/atlas"
"math"
"sort"

"berty.tech/go-ipfs-log/identityprovider"
"berty.tech/go-ipfs-log/iface"
"berty.tech/go-ipfs-log/io"
)

type Entry struct {
Payload []byte `json:"payload,omitempty"`
LogID string `json:"id,omitempty"`
Next []cid.Cid `json:"next,omitempty"`
Refs []cid.Cid `json:"refs,omitempty"`
V uint64 `json:"v,omitempty"`
Key []byte `json:"key,omitempty"`
Sig []byte `json:"sig,omitempty"`
Expand All @@ -28,6 +32,14 @@ type Entry struct {
Clock *LamportClock `json:"clock,omitempty"`
}

func (e *Entry) GetRefs() []cid.Cid {
return e.Refs
}

func (e *Entry) SetRefs(refs []cid.Cid) {
e.Refs = refs
}

func (e *Entry) GetPayload() []byte {
return e.Payload
}
Expand Down Expand Up @@ -108,6 +120,7 @@ type Hashable struct {
ID string
Payload []byte
Next []string
Refs []string
V uint64
Clock iface.IPFSLogLamportClock
Key []byte
Expand All @@ -119,12 +132,27 @@ var _ = atlas.BuildEntry(Hashable{}).
AddField("ID", atlas.StructMapEntry{SerialName: "id"}).
AddField("Payload", atlas.StructMapEntry{SerialName: "payload"}).
AddField("Next", atlas.StructMapEntry{SerialName: "next"}).
AddField("Refs", atlas.StructMapEntry{SerialName: "refs"}).
AddField("V", atlas.StructMapEntry{SerialName: "v"}).
AddField("Clock", atlas.StructMapEntry{SerialName: "clock"}).
Complete()

// CborEntry CBOR representable version of Entry
type CborEntry struct {
V uint64
LogID string
Key string
Sig string
Hash interface{}
Next []cid.Cid
Refs []cid.Cid
Clock *CborLamportClock
Payload string
Identity *identityprovider.CborIdentity
}

// CborEntryV1 CBOR representable version of Entry v1
type CborEntryV1 struct {
V uint64
LogID string
Key string
Expand All @@ -136,6 +164,9 @@ type CborEntry struct {
Identity *identityprovider.CborIdentity
}

// CborEntryV2 CBOR representable version of Entry v2
type CborEntryV2 = CborEntry

// ToEntry returns a plain Entry from a CBOR serialized version
func (c *CborEntry) ToEntry(provider identityprovider.Interface) (*Entry, error) {
key, err := hex.DecodeString(c.Key)
Expand Down Expand Up @@ -164,6 +195,7 @@ func (c *CborEntry) ToEntry(provider identityprovider.Interface) (*Entry, error)
Key: key,
Sig: sig,
Next: c.Next,
Refs: c.Refs,
Clock: clock,
Payload: []byte(c.Payload),
Identity: identity,
Expand All @@ -172,13 +204,28 @@ func (c *CborEntry) ToEntry(provider identityprovider.Interface) (*Entry, error)

// ToCborEntry creates a CBOR serializable version of an entry
func (e *Entry) ToCborEntry() interface{} {
if e.V == 1 {
return &CborEntryV1{
V: e.V,
LogID: e.LogID,
Key: hex.EncodeToString(e.Key),
Sig: hex.EncodeToString(e.Sig),
Hash: nil,
Next: e.Next,
Clock: e.Clock.ToCborLamportClock(),
Payload: string(e.Payload),
Identity: e.Identity.ToCborIdentity(),
}
}

return &CborEntry{
V: e.V,
LogID: e.LogID,
Key: hex.EncodeToString(e.Key),
Sig: hex.EncodeToString(e.Sig),
Hash: nil,
Next: e.Next,
Refs: e.Refs,
Clock: e.Clock.ToCborLamportClock(),
Payload: string(e.Payload),
Identity: e.Identity.ToCborIdentity(),
Expand All @@ -194,16 +241,32 @@ func init() {
AddField("Sig", atlas.StructMapEntry{SerialName: "sig"}).
AddField("Hash", atlas.StructMapEntry{SerialName: "hash"}).
AddField("Next", atlas.StructMapEntry{SerialName: "next"}).
AddField("Refs", atlas.StructMapEntry{SerialName: "refs"}).
AddField("Clock", atlas.StructMapEntry{SerialName: "clock"}).
AddField("Payload", atlas.StructMapEntry{SerialName: "payload"}).
AddField("Identity", atlas.StructMapEntry{SerialName: "identity"}).
Complete()

cbornode.RegisterCborType(AtlasEntry)

AtlasEntryV1 := atlas.BuildEntry(CborEntryV1{}).
StructMap().
AddField("V", atlas.StructMapEntry{SerialName: "v"}).
AddField("LogID", atlas.StructMapEntry{SerialName: "id"}).
AddField("Key", atlas.StructMapEntry{SerialName: "key"}).
AddField("Sig", atlas.StructMapEntry{SerialName: "sig"}).
AddField("Hash", atlas.StructMapEntry{SerialName: "hash"}).
AddField("Next", atlas.StructMapEntry{SerialName: "next"}).
AddField("Clock", atlas.StructMapEntry{SerialName: "clock"}).
AddField("Payload", atlas.StructMapEntry{SerialName: "payload"}).
AddField("Identity", atlas.StructMapEntry{SerialName: "identity"}).
Complete()

cbornode.RegisterCborType(AtlasEntryV1)
}

// CreateEntry creates an Entry.
func CreateEntry(ctx context.Context, ipfsInstance io.IpfsServices, identity *identityprovider.Identity, data *Entry, clock iface.IPFSLogLamportClock) (*Entry, error) {
func CreateEntry(ctx context.Context, ipfsInstance io.IpfsServices, identity *identityprovider.Identity, data *Entry, opts *iface.CreateEntryOptions) (*Entry, error) {
if ipfsInstance == nil {
return nil, errors.New("ipfs instance not defined")
}
Expand All @@ -220,18 +283,21 @@ func CreateEntry(ctx context.Context, ipfsInstance io.IpfsServices, identity *id
return nil, errors.New("'LogID' is required")
}

if clock == nil {
clock = NewLamportClock(identity.PublicKey, 0)
data = data.copy()

if data.Clock != nil {
data.Clock = CopyLamportClock(data.GetClock())
} else {
data.Clock = NewLamportClock(identity.PublicKey, 0)
}
data.V = 2

data = data.copy()
data.Clock = &LamportClock{
ID: clock.GetID(),
Time: clock.GetTime(),
hashable, err := data.toHashable()
if err != nil {
return nil, err
}
data.V = 1

jsonBytes, err := toBuffer(data.toHashable())
jsonBytes, err := toBuffer(hashable)
if err != nil {
return nil, err
}
Expand All @@ -246,7 +312,7 @@ func CreateEntry(ctx context.Context, ipfsInstance io.IpfsServices, identity *id
data.Sig = signature

data.Identity = identity.Filtered()
data.Hash, err = data.ToMultihash(ctx, ipfsInstance)
data.Hash, err = data.ToMultihash(ctx, ipfsInstance, opts)
if err != nil {
return nil, err
}
Expand All @@ -270,6 +336,7 @@ func (e *Entry) copy() *Entry {
Payload: e.Payload,
LogID: e.LogID,
Next: uniqueCIDs(e.Next),
Refs: uniqueCIDs(e.Refs),
V: e.V,
Key: e.Key,
Sig: e.Sig,
Expand All @@ -296,6 +363,15 @@ func uniqueCIDs(cids []cid.Cid) []cid.Cid {
return out
}

func cidB58(c cid.Cid) (string, error) {
e, err := multibase.NewEncoder(multibase.Base58BTC)
if err != nil {
return "", err
}

return c.Encode(e), nil
}

// toBuffer converts a hashable entry to bytes.
func toBuffer(e *Hashable) ([]byte, error) {
if e == nil {
Expand All @@ -307,6 +383,7 @@ func toBuffer(e *Hashable) ([]byte, error) {
"id": e.ID,
"payload": string(e.Payload),
"next": e.Next,
"refs": e.Refs,
"v": e.V,
"clock": map[string]interface{}{
"id": hex.EncodeToString(e.Clock.GetID()),
Expand All @@ -321,27 +398,45 @@ func toBuffer(e *Hashable) ([]byte, error) {
}

// toHashable Converts an entry to hashable.
func (e *Entry) toHashable() *Hashable {
func (e *Entry) toHashable() (*Hashable, error) {
nexts := []string{}
refs := []string{}

for _, n := range e.Next {
nexts = append(nexts, n.String())
c, err := cidB58(n)
if err != nil {
return nil, err
}

nexts = append(nexts, c)
}

for _, r := range e.Refs {
c, err := cidB58(r)
if err != nil {
return nil, err
}

refs = append(refs, c)
}

return &Hashable{
Hash: nil,
ID: e.LogID,
Payload: e.Payload,
Next: nexts,
Refs: refs,
V: e.V,
Clock: e.Clock,
Key: e.Key,
}
}, nil
}

// isValid checks that an entry is valid.
func (e *Entry) IsValid() bool {
return e.LogID != "" && len(e.Payload) > 0 && e.V <= 1
ok := e.LogID != "" && len(e.Payload) > 0 && e.V <= 2

return ok
}

// Verify checks the entry's signature.
Expand All @@ -360,7 +455,12 @@ func (e *Entry) Verify(identity identityprovider.Interface) error {

// TODO: Check against trusted keys

jsonBytes, err := toBuffer(e.toHashable())
hashable, err := e.toHashable()
if err != nil {
return errors.Wrap(err, "unable to make a hashable entry")
}

jsonBytes, err := toBuffer(hashable)
if err != nil {
return errors.Wrap(err, "unable to build string buffer")
}
Expand All @@ -383,43 +483,72 @@ func (e *Entry) Verify(identity identityprovider.Interface) error {
}

// ToMultihash gets the multihash of an Entry.
func (e *Entry) ToMultihash(ctx context.Context, ipfsInstance io.IpfsServices) (cid.Cid, error) {
func (e *Entry) ToMultihash(ctx context.Context, ipfsInstance io.IpfsServices, opts *iface.CreateEntryOptions) (cid.Cid, error) {
if opts == nil {
opts = &iface.CreateEntryOptions{}
}

if e == nil {
return cid.Cid{}, errors.New("entry is not defined")
return cid.Undef, errors.New("entry is not defined")
}

if ipfsInstance == nil {
return cid.Cid{}, errors.New("ipfs instance not defined")
return cid.Undef, errors.New("ipfs instance not defined")
}

data := e.copyNormalizedEntry(&normalizeEntryOpts{
preSigned: opts.PreSigned,
})

entryCID, err := io.WriteCBOR(ctx, ipfsInstance, data.ToCborEntry(), &io.WriteOpts{
Pin: opts.Pin,
})

return entryCID, err
}

type normalizeEntryOpts struct {
preSigned bool
includeHash bool
}

func (e *Entry) copyNormalizedEntry(opts *normalizeEntryOpts) *Entry {
if opts == nil {
opts = &normalizeEntryOpts{}
}

data := &Entry{
Hash: cid.Cid{},
LogID: e.LogID,
Payload: e.Payload,
Next: e.Next,
V: e.V,
Clock: e.Clock,
Clock: CopyLamportClock(e.GetClock()),
}

if e.Key != nil {
data.Key = e.Key
if opts.includeHash {
data.Hash = e.Hash
}

if e.Identity != nil {
data.Identity = e.Identity
if e.V > 1 {
data.Refs = e.Refs
}

data.Key = e.Key
data.Identity = e.Identity

if opts.preSigned {
return data
}

if len(e.Sig) > 0 {
data.Sig = e.Sig
}

entryCID, err := io.WriteCBOR(ctx, ipfsInstance, data.ToCborEntry())

return entryCID, err
return data
}

// fromMultihash creates an Entry from a hash.
func fromMultihash(ctx context.Context, ipfs io.IpfsServices, hash cid.Cid, provider identityprovider.Interface) (*Entry, error) {
// FromMultihash creates an Entry from a hash.
func FromMultihash(ctx context.Context, ipfs io.IpfsServices, hash cid.Cid, provider identityprovider.Interface) (*Entry, error) {
if ipfs == nil {
return nil, errors.New("ipfs instance not defined")
}
Expand All @@ -442,6 +571,8 @@ func fromMultihash(ctx context.Context, ipfs io.IpfsServices, hash cid.Cid, prov
return nil, err
}

entry.Hash = hash

return entry, nil
}

Expand Down
Loading

0 comments on commit d232dc6

Please sign in to comment.