From 21b4803fa256e478556bd948656d92e1b81d4168 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Thu, 30 Apr 2020 13:54:14 +0200 Subject: [PATCH 1/9] Byzcoin: refactor transactions --- go.sum | 5 + internal/testing/fake/mod.go | 5 + ledger/arc/darc/action.go | 113 +++++ ledger/arc/darc/contract/mod.go | 101 ---- ledger/arc/darc/contract/mod_test.go | 171 ------- ledger/arc/darc/messages.pb.go | 76 ++- ledger/arc/darc/messages.proto | 5 + ledger/byzcoin/actions/contract/action.go | 214 ++++++++ .../byzcoin/actions/contract/action_test.go | 189 +++++++ ledger/byzcoin/actions/contract/context.go | 64 +++ .../byzcoin/actions/contract/context_test.go | 30 ++ .../byzcoin/actions/contract/messages.pb.go | 257 ++++++++++ .../actions/contract}/messages.proto | 23 +- ledger/byzcoin/actions/contract/mod.go | 29 ++ ledger/byzcoin/actions/contract/mod_test.go | 21 + ledger/byzcoin/actions/mod.go | 58 +++ ledger/byzcoin/mod.go | 51 +- ledger/byzcoin/mod_test.go | 59 +-- ledger/byzcoin/txbag.go | 12 +- ledger/byzcoin/txbag_test.go | 4 +- ledger/byzcoin/txproc.go | 30 +- ledger/byzcoin/txproc_test.go | 81 +-- ledger/consumer/mod.go | 67 --- ledger/consumer/smartcontract/context.go | 73 --- ledger/consumer/smartcontract/context_test.go | 45 -- ledger/consumer/smartcontract/messages.pb.go | 384 --------------- ledger/consumer/smartcontract/mod.go | 181 ------- ledger/consumer/smartcontract/mod_test.go | 221 --------- ledger/consumer/smartcontract/tx.go | 463 ------------------ ledger/consumer/smartcontract/tx_test.go | 408 --------------- ledger/mod.go | 11 +- ledger/transactions/basic/messages.pb.go | 108 ++++ ledger/transactions/basic/messages.proto | 12 + ledger/transactions/basic/mod.go | 270 ++++++++++ ledger/transactions/basic/mod_test.go | 168 +++++++ ledger/transactions/mod.go | 31 ++ 36 files changed, 1700 insertions(+), 2340 deletions(-) create mode 100644 ledger/arc/darc/action.go delete mode 100644 ledger/arc/darc/contract/mod.go delete mode 100644 ledger/arc/darc/contract/mod_test.go create mode 100644 ledger/byzcoin/actions/contract/action.go create mode 100644 ledger/byzcoin/actions/contract/action_test.go create mode 100644 ledger/byzcoin/actions/contract/context.go create mode 100644 ledger/byzcoin/actions/contract/context_test.go create mode 100644 ledger/byzcoin/actions/contract/messages.pb.go rename ledger/{consumer/smartcontract => byzcoin/actions/contract}/messages.proto (50%) create mode 100644 ledger/byzcoin/actions/contract/mod.go create mode 100644 ledger/byzcoin/actions/contract/mod_test.go create mode 100644 ledger/byzcoin/actions/mod.go delete mode 100644 ledger/consumer/mod.go delete mode 100644 ledger/consumer/smartcontract/context.go delete mode 100644 ledger/consumer/smartcontract/context_test.go delete mode 100644 ledger/consumer/smartcontract/messages.pb.go delete mode 100644 ledger/consumer/smartcontract/mod.go delete mode 100644 ledger/consumer/smartcontract/mod_test.go delete mode 100644 ledger/consumer/smartcontract/tx.go delete mode 100644 ledger/consumer/smartcontract/tx_test.go create mode 100644 ledger/transactions/basic/messages.pb.go create mode 100644 ledger/transactions/basic/messages.proto create mode 100644 ledger/transactions/basic/mod.go create mode 100644 ledger/transactions/basic/mod_test.go create mode 100644 ledger/transactions/mod.go diff --git a/go.sum b/go.sum index ad45df79a..255c180bd 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -18,6 +20,8 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -75,6 +79,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/internal/testing/fake/mod.go b/internal/testing/fake/mod.go index 590b02813..eea73191e 100644 --- a/internal/testing/fake/mod.go +++ b/internal/testing/fake/mod.go @@ -379,6 +379,11 @@ func (pk PublicKey) Pack(encoding.ProtoMarshaler) (proto.Message, error) { return &empty.Empty{}, pk.err } +// String implements fmt.Stringer. +func (pk PublicKey) String() string { + return "fake.PublicKey" +} + // Signer is a fake implementation of the crypto.AggregateSigner interface. type Signer struct { crypto.AggregateSigner diff --git a/ledger/arc/darc/action.go b/ledger/arc/darc/action.go new file mode 100644 index 000000000..f0939549f --- /dev/null +++ b/ledger/arc/darc/action.go @@ -0,0 +1,113 @@ +package darc + +import ( + "io" + + proto "github.com/golang/protobuf/proto" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions/basic" + "golang.org/x/xerrors" +) + +// darcAction is a transaction action to create or update an access right +// control. +type darcAction struct { + key []byte + access Access +} + +// NewCreate returns a new action to create a DARC. +func NewCreate() basic.ClientAction { + return darcAction{} +} + +// NewUpdate returns a new action to update a DARC. +func NewUpdate(key []byte) basic.ClientAction { + return darcAction{key: key} +} + +// Pack implements encoding.Packable. +func (act darcAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { + pb := &ActionProto{ + Key: act.key, + } + + access, err := enc.Pack(act.access) + if err != nil { + return nil, err + } + + pb.Access = access.(*AccessControlProto) + + return pb, nil +} + +// WriteTo implements io.WriterTo. +func (act darcAction) WriteTo(w io.Writer) (int64, error) { + // TODO: write to + sum := int64(0) + + n, err := w.Write(act.key) + sum += int64(n) + if err != nil { + return sum, err + } + + return 0, nil +} + +type serverArcAction struct { + encoder encoding.ProtoMarshaler + darcAction +} + +func (act serverArcAction) Consume(ctx basic.Context, page inventory.WritablePage) error { + accesspb, err := act.encoder.Pack(act.access) + if err != nil { + return err + } + + err = page.Write(ctx.GetID(), accesspb) + if err != nil { + return err + } + + return nil +} + +type actionFactory struct { + darcFactory Factory +} + +// NewActionFactory returns a new instance of the action factory. +func NewActionFactory() basic.ActionFactory { + return actionFactory{ + darcFactory: NewFactory(), + } +} + +func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { + var pb *ActionProto + switch msg := in.(type) { + case *ActionProto: + pb = msg + default: + return nil, xerrors.Errorf("invalid message type '%T'", in) + } + + access, err := f.darcFactory.FromProto(pb.GetAccess()) + if err != nil { + return nil, err + } + + servAccess := serverArcAction{ + darcAction: darcAction{ + key: pb.GetKey(), + access: access.(Access), + }, + encoder: f.darcFactory.encoder, + } + + return servAccess, nil +} diff --git a/ledger/arc/darc/contract/mod.go b/ledger/arc/darc/contract/mod.go deleted file mode 100644 index c0b1694f1..000000000 --- a/ledger/arc/darc/contract/mod.go +++ /dev/null @@ -1,101 +0,0 @@ -package contract - -import ( - proto "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/empty" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/ledger/arc" - "go.dedis.ch/fabric/ledger/arc/darc" - sc "go.dedis.ch/fabric/ledger/consumer/smartcontract" - "golang.org/x/xerrors" -) - -// ContractName is the unique name used to differentiate the contract among -// others. -const ContractName = "darc" - -// Contract is the smart contract implementation for DARCs. -type Contract struct { - encoder encoding.ProtoMarshaler - factory arc.AccessControlFactory -} - -// Spawn implements smartcontract.Contract. It returns an access control if the -// transaction is correct. -func (c Contract) Spawn(ctx sc.SpawnContext) (proto.Message, []byte, error) { - gnrc, err := c.factory.FromProto(ctx.GetAction().Argument) - if err != nil { - return nil, nil, xerrors.Errorf("couldn't decode argument: %v", err) - } - - access, ok := gnrc.(darc.EvolvableAccessControl) - if !ok { - return nil, nil, - xerrors.Errorf("'%T' does not implement 'darc.EvolvableAccessControl'", gnrc) - } - - // Set a rule to allow the creator of the DARC to update it. - rule := arc.Compile(ContractName, "invoke") - access, err = access.Evolve(rule, ctx.GetTransaction().GetIdentity()) - if err != nil { - return nil, nil, xerrors.Errorf("couldn't evolve darc: %v", err) - } - - darcpb, err := c.encoder.Pack(access) - if err != nil { - return nil, nil, xerrors.Errorf("couldn't pack darc: %v", err) - } - - return darcpb, ctx.GetTransaction().GetID(), nil -} - -// Invoke implements smartcontract.Contract. -func (c Contract) Invoke(ctx sc.InvokeContext) (proto.Message, error) { - instance, err := ctx.Read(ctx.GetAction().Key) - if err != nil { - return nil, xerrors.Errorf("couldn't read instance: %v", err) - } - - generic, err := c.factory.FromProto(instance.GetValue()) - if err != nil { - return nil, xerrors.Errorf("couldn't decode darc: %v", err) - } - - access, ok := generic.(darc.EvolvableAccessControl) - if !ok { - return nil, xerrors.Errorf("'%T' does not implement 'darc.EvolvableAccessControl'", generic) - } - - // TODO: use argument to update the darc.. - - darcpb, err := c.encoder.Pack(access) - if err != nil { - return nil, xerrors.Errorf("couldn't pack darc: %v", err) - } - - return darcpb, nil -} - -// NewGenesisAction returns a transaction to spawn a new empty DARC. -func NewGenesisAction() sc.SpawnAction { - return sc.SpawnAction{ - ContractID: ContractName, - Argument: &darc.AccessControlProto{}, - } -} - -// NewUpdateAction returns a transaction to update an existing DARC. -func NewUpdateAction(key []byte) sc.InvokeAction { - return sc.InvokeAction{ - Key: key, - Argument: &empty.Empty{}, - } -} - -// RegisterContract can be used to enable DARC for a smart contract consumer. -func RegisterContract(c sc.Consumer) { - c.Register(ContractName, Contract{ - encoder: encoding.NewProtoEncoder(), - factory: darc.NewFactory(), - }) -} diff --git a/ledger/arc/darc/contract/mod_test.go b/ledger/arc/darc/contract/mod_test.go deleted file mode 100644 index e488b8b69..000000000 --- a/ledger/arc/darc/contract/mod_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package contract - -import ( - "testing" - - "github.com/golang/protobuf/proto" - "github.com/stretchr/testify/require" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/internal/testing/fake" - "go.dedis.ch/fabric/ledger/arc" - "go.dedis.ch/fabric/ledger/arc/darc" - "go.dedis.ch/fabric/ledger/consumer" - "go.dedis.ch/fabric/ledger/consumer/smartcontract" - "golang.org/x/xerrors" -) - -func TestContract_Spawn(t *testing.T) { - contract := Contract{ - encoder: encoding.NewProtoEncoder(), - factory: darc.NewFactory(), - } - - ctx := smartcontract.SpawnContext{ - Context: fakeContext{}, - Action: smartcontract.SpawnAction{ - Argument: &darc.AccessControlProto{}, - }, - } - - arg := &darc.AccessControlProto{Rules: map[string]*darc.Expression{ - "darc:invoke": {Matches: []string{"\252"}}, - }} - - pb, arcid, err := contract.Spawn(ctx) - require.NoError(t, err) - require.Equal(t, []byte{0xff}, arcid) - require.True(t, proto.Equal(arg, pb), "%+v != %+v", pb, arg) - - contract.factory = badArcFactory{err: xerrors.New("oops")} - _, _, err = contract.Spawn(ctx) - require.EqualError(t, err, "couldn't decode argument: oops") - - contract.factory = badArcFactory{arc: fakeArc{}} - _, _, err = contract.Spawn(ctx) - require.EqualError(t, err, - "'contract.fakeArc' does not implement 'darc.EvolvableAccessControl'") - - contract.factory = badArcFactory{arc: badArc{}} - _, _, err = contract.Spawn(ctx) - require.EqualError(t, err, "couldn't evolve darc: oops") - - contract.factory = darc.NewFactory() - contract.encoder = fake.BadPackEncoder{} - _, _, err = contract.Spawn(ctx) - require.EqualError(t, err, "couldn't pack darc: fake error") -} - -func TestContract_Invoke(t *testing.T) { - contract := Contract{ - encoder: encoding.NewProtoEncoder(), - factory: darc.Factory{}, - } - - ctx := smartcontract.InvokeContext{ - Context: fakeContext{}, - Action: smartcontract.InvokeAction{}, - } - - pb, err := contract.Invoke(ctx) - require.NoError(t, err) - require.NotNil(t, pb) - - ctx.Context = fakeContext{err: xerrors.New("oops")} - _, err = contract.Invoke(ctx) - require.EqualError(t, err, "couldn't read instance: oops") - - ctx.Context = fakeContext{} - contract.factory = badArcFactory{err: xerrors.New("oops")} - _, err = contract.Invoke(ctx) - require.EqualError(t, err, "couldn't decode darc: oops") - - contract.factory = badArcFactory{arc: fakeArc{}} - _, err = contract.Invoke(ctx) - require.EqualError(t, err, - "'contract.fakeArc' does not implement 'darc.EvolvableAccessControl'") - - contract.factory = darc.NewFactory() - contract.encoder = fake.BadPackEncoder{} - _, err = contract.Invoke(ctx) - require.EqualError(t, err, "couldn't pack darc: fake error") -} - -func Test_NewGenesisTransaction(t *testing.T) { - require.NotNil(t, NewGenesisAction()) -} - -func Test_NewUpdateTransaction(t *testing.T) { - action := NewUpdateAction([]byte{0xff}) - require.Equal(t, []byte{0xff}, action.Key) -} - -func Test_RegisterContract(t *testing.T) { - c := smartcontract.NewConsumer() - RegisterContract(c) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeIdentity struct { - arc.Identity -} - -func (ident fakeIdentity) MarshalText() ([]byte, error) { - return []byte{0xaa}, nil -} - -type fakeTransaction struct { - consumer.Transaction -} - -func (t fakeTransaction) GetID() []byte { - return []byte{0xff} -} - -func (t fakeTransaction) GetIdentity() arc.Identity { - return fakeIdentity{} -} - -type fakeInstance struct { - consumer.Instance -} - -func (i fakeInstance) GetValue() proto.Message { - return &darc.AccessControlProto{} -} - -type fakeContext struct { - consumer.Context - err error -} - -func (ctx fakeContext) GetTransaction() consumer.Transaction { - return fakeTransaction{} -} - -func (ctx fakeContext) Read([]byte) (consumer.Instance, error) { - return fakeInstance{}, ctx.err -} - -type fakeArc struct { - arc.AccessControl -} - -type badArc struct { - arc.AccessControl -} - -func (arc badArc) Evolve(string, ...arc.Identity) (darc.Access, error) { - return darc.Access{}, xerrors.New("oops") -} - -type badArcFactory struct { - arc.AccessControlFactory - err error - arc arc.AccessControl -} - -func (f badArcFactory) FromProto(proto.Message) (arc.AccessControl, error) { - return f.arc, f.err -} diff --git a/ledger/arc/darc/messages.pb.go b/ledger/arc/darc/messages.pb.go index ef839a4b8..7faa35480 100644 --- a/ledger/arc/darc/messages.pb.go +++ b/ledger/arc/darc/messages.pb.go @@ -98,10 +98,58 @@ func (m *AccessControlProto) GetRules() map[string]*Expression { return nil } +type ActionProto struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Access *AccessControlProto `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ActionProto) Reset() { *m = ActionProto{} } +func (m *ActionProto) String() string { return proto.CompactTextString(m) } +func (*ActionProto) ProtoMessage() {} +func (*ActionProto) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{2} +} + +func (m *ActionProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ActionProto.Unmarshal(m, b) +} +func (m *ActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ActionProto.Marshal(b, m, deterministic) +} +func (m *ActionProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_ActionProto.Merge(m, src) +} +func (m *ActionProto) XXX_Size() int { + return xxx_messageInfo_ActionProto.Size(m) +} +func (m *ActionProto) XXX_DiscardUnknown() { + xxx_messageInfo_ActionProto.DiscardUnknown(m) +} + +var xxx_messageInfo_ActionProto proto.InternalMessageInfo + +func (m *ActionProto) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *ActionProto) GetAccess() *AccessControlProto { + if m != nil { + return m.Access + } + return nil +} + func init() { proto.RegisterType((*Expression)(nil), "darc.Expression") proto.RegisterType((*AccessControlProto)(nil), "darc.AccessControlProto") proto.RegisterMapType((map[string]*Expression)(nil), "darc.AccessControlProto.RulesEntry") + proto.RegisterType((*ActionProto)(nil), "darc.ActionProto") } func init() { @@ -109,17 +157,19 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 185 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, - 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0x49, 0x2c, 0x4a, - 0x56, 0x52, 0xe3, 0xe2, 0x72, 0xad, 0x28, 0x28, 0x4a, 0x2d, 0x2e, 0xce, 0xcc, 0xcf, 0x13, 0x92, - 0xe0, 0x62, 0xcf, 0x4d, 0x2c, 0x49, 0xce, 0x48, 0x2d, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, - 0x82, 0x71, 0x95, 0x66, 0x33, 0x72, 0x09, 0x39, 0x26, 0x27, 0xa7, 0x16, 0x17, 0x3b, 0xe7, 0xe7, - 0x95, 0x14, 0xe5, 0xe7, 0x04, 0x80, 0x0d, 0xb1, 0xe4, 0x62, 0x2d, 0x2a, 0xcd, 0x81, 0x2a, 0xe7, - 0x36, 0x52, 0xd6, 0x03, 0x19, 0xaa, 0x87, 0xa9, 0x50, 0x2f, 0x08, 0xa4, 0xca, 0x35, 0xaf, 0xa4, - 0xa8, 0x32, 0x08, 0xa2, 0x43, 0xca, 0x8b, 0x8b, 0x0b, 0x21, 0x28, 0x24, 0xc0, 0xc5, 0x9c, 0x9d, - 0x5a, 0x29, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0x62, 0x0a, 0xa9, 0x71, 0xb1, 0x96, 0x25, - 0xe6, 0x94, 0xa6, 0x4a, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0x40, 0x8c, 0x46, 0x38, 0x36, - 0x08, 0x22, 0x6d, 0xc5, 0x64, 0xc1, 0x98, 0xc4, 0x06, 0xf6, 0x92, 0x31, 0x20, 0x00, 0x00, 0xff, - 0xff, 0x5b, 0x87, 0x53, 0x5c, 0xe4, 0x00, 0x00, 0x00, + // 217 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0x4f, 0x4b, 0x87, 0x30, + 0x18, 0x80, 0xd9, 0xef, 0x97, 0x86, 0xaf, 0x11, 0xb2, 0xd3, 0xe8, 0x24, 0x06, 0xe2, 0x69, 0x84, + 0x5d, 0xaa, 0x9b, 0x84, 0x97, 0x4e, 0xb5, 0x6f, 0xb0, 0xd6, 0x28, 0x49, 0x37, 0xd9, 0x3b, 0x23, + 0x3f, 0x4b, 0x5f, 0x36, 0x74, 0x86, 0x41, 0x74, 0xdb, 0x9f, 0xe7, 0x7d, 0x78, 0x78, 0xe1, 0x7c, + 0xd0, 0x88, 0xf2, 0x55, 0x23, 0x1f, 0x9d, 0xf5, 0x96, 0x9e, 0xbc, 0x48, 0xa7, 0x8a, 0x12, 0xa0, + 0xfd, 0x1c, 0x9d, 0x46, 0xec, 0xac, 0xa1, 0x0c, 0x4e, 0x07, 0xe9, 0xd5, 0x9b, 0x46, 0x46, 0xf2, + 0x63, 0x95, 0x88, 0x9f, 0x6b, 0xf1, 0x45, 0x80, 0x36, 0x4a, 0x69, 0xc4, 0x7b, 0x6b, 0xbc, 0xb3, + 0xfd, 0xe3, 0x2a, 0xb9, 0x85, 0xc8, 0x4d, 0xfd, 0x86, 0xa7, 0xf5, 0x25, 0x5f, 0xa4, 0xfc, 0x2f, + 0xc8, 0xc5, 0x42, 0xb5, 0xc6, 0xbb, 0x59, 0x84, 0x89, 0x8b, 0x07, 0x80, 0xfd, 0x91, 0x66, 0x70, + 0x7c, 0xd7, 0x33, 0x23, 0x39, 0xa9, 0x12, 0xb1, 0x1c, 0x69, 0x09, 0xd1, 0x87, 0xec, 0x27, 0xcd, + 0x0e, 0x39, 0xa9, 0xd2, 0x3a, 0x0b, 0xea, 0x3d, 0x56, 0x84, 0xef, 0xbb, 0xc3, 0x0d, 0x29, 0x9e, + 0x20, 0x6d, 0x94, 0xef, 0xac, 0x09, 0x55, 0xbf, 0x64, 0x67, 0x41, 0x76, 0x05, 0xb1, 0x5c, 0xa3, + 0x36, 0x1b, 0xfb, 0x2f, 0x54, 0x6c, 0xdc, 0x73, 0xbc, 0x6e, 0xe9, 0xfa, 0x3b, 0x00, 0x00, 0xff, + 0xff, 0xdb, 0x76, 0x85, 0xae, 0x37, 0x01, 0x00, 0x00, } diff --git a/ledger/arc/darc/messages.proto b/ledger/arc/darc/messages.proto index 18d1f2e24..bdba3ba9f 100644 --- a/ledger/arc/darc/messages.proto +++ b/ledger/arc/darc/messages.proto @@ -9,3 +9,8 @@ message Expression { message AccessControlProto { map rules = 1; } + +message ActionProto { + bytes key = 1; + AccessControlProto access = 2; +} diff --git a/ledger/byzcoin/actions/contract/action.go b/ledger/byzcoin/actions/contract/action.go new file mode 100644 index 000000000..f29c6a005 --- /dev/null +++ b/ledger/byzcoin/actions/contract/action.go @@ -0,0 +1,214 @@ +package contract + +import ( + "io" + + "github.com/golang/protobuf/proto" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions/basic" + "golang.org/x/xerrors" +) + +// SpawnAction is a contract transaction action to create a new instance. +type SpawnAction struct { + ContractID string + Argument proto.Message +} + +// Pack implements encoding.Packable. +func (act SpawnAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &SpawnActionProto{}, nil +} + +// WriteTo implements io.WriterTo. +func (act SpawnAction) WriteTo(io.Writer) (int64, error) { + return 0, nil +} + +// InvokeAction is a contract transaction action to update an existing instance +// of the access control allows it. +type InvokeAction struct { + Key []byte + Argument proto.Message +} + +// Pack implements encoding.Packable. +func (act InvokeAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &SpawnActionProto{}, nil +} + +// WriteTo implements io.WriterTo. +func (act InvokeAction) WriteTo(io.Writer) (int64, error) { + return 0, nil +} + +// DeleteAction is a contract transaction action to mark an instance as deleted +// so that it cannot be updated anymore. +type DeleteAction struct { + Key []byte +} + +// Pack implements encoding.Packable. +func (a DeleteAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &DeleteActionProto{Key: a.Key}, nil +} + +// WriteTo implements io.WriterTo. +func (a DeleteAction) WriteTo(io.Writer) (int64, error) { + return 0, nil +} + +type serverAction struct { + basic.ClientAction + contracts map[string]Contract + arcFactory arc.AccessControlFactory + encoder encoding.ProtoMarshaler +} + +func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) error { + txCtx := transactionContext{ + Context: ctx, + arcFactory: act.arcFactory, + page: page, + } + + var instance *Instance + var err error + switch action := act.ClientAction.(type) { + case SpawnAction: + instance, err = act.consumeSpawn(SpawnContext{ + Context: txCtx, + SpawnAction: action, + }) + case InvokeAction: + instance, err = act.consumeInvoke(InvokeContext{ + Context: txCtx, + InvokeAction: action, + }) + case DeleteAction: + instance, err = act.consumeDelete(DeleteContext{ + Context: txCtx, + DeleteAction: action, + }) + default: + return xerrors.New("missing action") + } + + if err != nil { + return err + } + + err = page.Write(instance.Key, instance) + if err != nil { + return err + } + + return nil +} + +func (act serverAction) consumeSpawn(ctx SpawnContext) (*Instance, error) { + _, err := ctx.Read(ctx.GetID()) + if err == nil { + return nil, xerrors.New("instance already exists") + } + + exec := act.contracts[ctx.ContractID] + if exec == nil { + return nil, xerrors.Errorf("contract '%s' not found", ctx.ContractID) + } + + value, arcid, err := exec.Spawn(ctx) + if err != nil { + return nil, xerrors.Errorf("couldn't execute spawn: %v", err) + } + + rule := arc.Compile(ctx.ContractID, "spawn") + + err = act.hasAccess(ctx, arcid, rule) + if err != nil { + return nil, xerrors.Errorf("no access: %v", err) + } + + valueAny, err := act.encoder.MarshalAny(value) + if err != nil { + return nil, err + } + + instance := &Instance{ + Key: ctx.GetID(), + AccessControl: arcid, + ContractID: ctx.ContractID, + Deleted: false, + Value: valueAny, + } + + return instance, nil +} + +func (act serverAction) consumeInvoke(ctx InvokeContext) (*Instance, error) { + instance, err := ctx.Read(ctx.Key) + if err != nil { + return nil, xerrors.Errorf("couldn't read the instance: %v", err) + } + + rule := arc.Compile(instance.GetContractID(), "invoke") + + err = act.hasAccess(ctx, instance.GetAccessControl(), rule) + if err != nil { + return nil, xerrors.Errorf("no access: %v", err) + } + + exec := act.contracts[instance.GetContractID()] + if exec == nil { + return nil, xerrors.Errorf("contract '%s' not found", instance.GetContractID()) + } + + value, err := exec.Invoke(ctx) + if err != nil { + return nil, xerrors.Errorf("couldn't invoke: %v", err) + } + + valueAny, err := act.encoder.MarshalAny(value) + if err != nil { + return nil, err + } + + instance.Value = valueAny + + return instance, nil +} + +func (act serverAction) consumeDelete(ctx DeleteContext) (*Instance, error) { + instance, err := ctx.Read(ctx.Key) + if err != nil { + return nil, xerrors.Errorf("couldn't read the instance: %v", err) + } + + instance.Deleted = true + + return instance, nil +} + +func (act serverAction) hasAccess(ctx Context, key []byte, rule string) error { + access, err := ctx.GetArc(key) + if err != nil { + return xerrors.Errorf("couldn't decode access: %v", err) + } + + err = access.Match(rule, ctx.GetIdentity()) + if err != nil { + return xerrors.Errorf("%v is refused to '%s' by %v: %v", + ctx.GetIdentity(), rule, access, err) + } + + return nil +} + +type actionFactory struct{} + +func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { + + return serverAction{}, nil +} diff --git a/ledger/byzcoin/actions/contract/action_test.go b/ledger/byzcoin/actions/contract/action_test.go new file mode 100644 index 000000000..df2c7ded8 --- /dev/null +++ b/ledger/byzcoin/actions/contract/action_test.go @@ -0,0 +1,189 @@ +package contract + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/empty" + "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/internal/testing/fake" + "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/inventory" + "golang.org/x/xerrors" +) + +func TestServerAction_Consume(t *testing.T) { + factory := &fakeAccessFactory{access: &fakeAccess{match: true}} + contracts := map[string]Contract{ + "fake": fakeContract{}, + "bad": fakeContract{err: xerrors.New("oops")}, + } + + action := serverAction{ + ClientAction: SpawnAction{ContractID: "fake"}, + contracts: contracts, + arcFactory: factory, + encoder: encoding.NewProtoEncoder(), + } + + page := fakePage{ + store: map[string]proto.Message{ + "a": makeInstance(t), + "y": &Instance{ContractID: "bad", AccessControl: []byte("arc")}, + "z": &Instance{ContractID: "unknown", AccessControl: []byte("arc")}, + "arc": &empty.Empty{}, + }, + } + + // 1. Consume a spawn action. + err := action.Consume(fakeContext{id: []byte("b")}, page) + require.NoError(t, err) + + action.ClientAction = SpawnAction{ContractID: "bad"} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't execute spawn: oops") + + // 2. Consume an invoke transaction. + action.encoder = encoding.NewProtoEncoder() + action.ClientAction = InvokeAction{Key: []byte("b")} + + factory.access.calls = make([][]interface{}, 0) + err = action.Consume(fakeContext{}, page) + require.NoError(t, err) + require.Len(t, factory.access.calls, 1) + require.Equal(t, []arc.Identity{fake.PublicKey{}}, factory.access.calls[0][0]) + require.Equal(t, arc.Compile("fake", "invoke"), factory.access.calls[0][1]) + + action.ClientAction = InvokeAction{Key: []byte("c")} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, + "couldn't read the instance: couldn't read the entry: not found") + + action.ClientAction = InvokeAction{Key: []byte("z")} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "contract 'unknown' not found") + + action.ClientAction = InvokeAction{Key: []byte("b")} + factory.err = xerrors.New("oops") + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "no access: couldn't decode access: oops") + + factory.err = nil + factory.access.match = false + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, + "no access: fake.PublicKey is refused to 'fake:invoke' by fakeAccessControl: not authorized") + + factory.access.match = true + action.ClientAction = InvokeAction{Key: []byte("y")} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't invoke: oops") + + // 3. Consume a delete transaction. + action.ClientAction = DeleteAction{Key: []byte("a")} + + err = action.Consume(fakeContext{}, page) + require.NoError(t, err) + + action.ClientAction = DeleteAction{Key: []byte("c")} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, + "couldn't read the instance: couldn't read the entry: not found") + + // 4. Consume an invalid transaction. + action.ClientAction = nil + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "missing action") +} + +// ----------------------------------------------------------------------------- +// Utility functions + +func makeInstance(t *testing.T) *Instance { + value, err := ptypes.MarshalAny(&empty.Empty{}) + require.NoError(t, err) + + return &Instance{ + ContractID: "fake", + AccessControl: []byte("arc"), + Deleted: false, + Value: value, + } +} + +type fakeContract struct { + Contract + err error +} + +func (c fakeContract) Spawn(ctx SpawnContext) (proto.Message, []byte, error) { + ctx.Read([]byte{0xab}) + return &empty.Empty{}, []byte("arc"), c.err +} + +func (c fakeContract) Invoke(ctx InvokeContext) (proto.Message, error) { + ctx.Read([]byte{0xab}) + return &empty.Empty{}, c.err +} + +type fakePage struct { + inventory.WritablePage + store map[string]proto.Message + err error +} + +func (page fakePage) Read(key []byte) (proto.Message, error) { + instance := page.store[string(key)] + if instance == nil { + return nil, xerrors.New("not found") + } + + return instance, page.err +} + +func (page fakePage) Write(key []byte, value proto.Message) error { + page.store[string(key)] = value + return page.err +} + +type fakeContext struct { + id []byte +} + +func (ctx fakeContext) GetID() []byte { + return ctx.id +} + +func (ctx fakeContext) GetIdentity() arc.Identity { + return fake.PublicKey{} +} + +type fakeAccess struct { + arc.AccessControl + match bool + calls [][]interface{} +} + +func (ac *fakeAccess) Match(rule string, idents ...arc.Identity) error { + ac.calls = append(ac.calls, []interface{}{idents, rule}) + if ac.match { + return nil + } + return xerrors.New("not authorized") +} + +func (ac *fakeAccess) String() string { + return "fakeAccessControl" +} + +type fakeAccessFactory struct { + arc.AccessControlFactory + access *fakeAccess + err error +} + +func (f *fakeAccessFactory) FromProto(proto.Message) (arc.AccessControl, error) { + return f.access, f.err +} diff --git a/ledger/byzcoin/actions/contract/context.go b/ledger/byzcoin/actions/contract/context.go new file mode 100644 index 000000000..17a1eab58 --- /dev/null +++ b/ledger/byzcoin/actions/contract/context.go @@ -0,0 +1,64 @@ +package contract + +import ( + "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions/basic" + "golang.org/x/xerrors" +) + +type transactionContext struct { + basic.Context + arcFactory arc.AccessControlFactory + page inventory.Page +} + +func (ctx transactionContext) GetArc(key []byte) (arc.AccessControl, error) { + value, err := ctx.page.Read(key) + if err != nil { + return nil, err + } + + access, err := ctx.arcFactory.FromProto(value) + if err != nil { + return nil, err + } + + return access, nil +} + +// Read implements consumer.Context. It returns the instance stored at the given +// key, or an error if it does not find it. +func (ctx transactionContext) Read(key []byte) (*Instance, error) { + entry, err := ctx.page.Read(key) + if err != nil { + return nil, xerrors.Errorf("couldn't read the entry: %v", err) + } + + instance, ok := entry.(*Instance) + if !ok { + return nil, xerrors.Errorf("...") + } + + return instance, nil +} + +// SpawnContext is the context provided to a smart contract execution of a spawn +// transaction. +type SpawnContext struct { + Context + SpawnAction +} + +// InvokeContext is the context provided to a smart contract execution of an +// invoke transaction. +type InvokeContext struct { + Context + InvokeAction +} + +// DeleteContext is the context to delete an instance. +type DeleteContext struct { + Context + DeleteAction +} diff --git a/ledger/byzcoin/actions/contract/context_test.go b/ledger/byzcoin/actions/contract/context_test.go new file mode 100644 index 000000000..03c185693 --- /dev/null +++ b/ledger/byzcoin/actions/contract/context_test.go @@ -0,0 +1,30 @@ +package contract + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "github.com/stretchr/testify/require" +) + +func TestTransactionContext_Read(t *testing.T) { + ctx := transactionContext{ + page: fakePage{ + store: map[string]proto.Message{ + "a": &Instance{ + ContractID: "abc", + Value: &any.Any{}, + }, + }, + }, + } + + instance, err := ctx.Read([]byte("a")) + require.NoError(t, err) + require.Equal(t, "abc", instance.GetContractID()) + require.NotNil(t, instance.Value) + + _, err = ctx.Read(nil) + require.EqualError(t, err, "couldn't read the entry: not found") +} diff --git a/ledger/byzcoin/actions/contract/messages.pb.go b/ledger/byzcoin/actions/contract/messages.pb.go new file mode 100644 index 000000000..9702c7e30 --- /dev/null +++ b/ledger/byzcoin/actions/contract/messages.pb.go @@ -0,0 +1,257 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: messages.proto + +package contract + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Instance struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value *any.Any `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + ContractID string `protobuf:"bytes,3,opt,name=contractID,proto3" json:"contractID,omitempty"` + Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"` + AccessControl []byte `protobuf:"bytes,5,opt,name=accessControl,proto3" json:"accessControl,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Instance) Reset() { *m = Instance{} } +func (m *Instance) String() string { return proto.CompactTextString(m) } +func (*Instance) ProtoMessage() {} +func (*Instance) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{0} +} + +func (m *Instance) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Instance.Unmarshal(m, b) +} +func (m *Instance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Instance.Marshal(b, m, deterministic) +} +func (m *Instance) XXX_Merge(src proto.Message) { + xxx_messageInfo_Instance.Merge(m, src) +} +func (m *Instance) XXX_Size() int { + return xxx_messageInfo_Instance.Size(m) +} +func (m *Instance) XXX_DiscardUnknown() { + xxx_messageInfo_Instance.DiscardUnknown(m) +} + +var xxx_messageInfo_Instance proto.InternalMessageInfo + +func (m *Instance) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *Instance) GetValue() *any.Any { + if m != nil { + return m.Value + } + return nil +} + +func (m *Instance) GetContractID() string { + if m != nil { + return m.ContractID + } + return "" +} + +func (m *Instance) GetDeleted() bool { + if m != nil { + return m.Deleted + } + return false +} + +func (m *Instance) GetAccessControl() []byte { + if m != nil { + return m.AccessControl + } + return nil +} + +type SpawnActionProto struct { + ContractID string `protobuf:"bytes,1,opt,name=contractID,proto3" json:"contractID,omitempty"` + Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SpawnActionProto) Reset() { *m = SpawnActionProto{} } +func (m *SpawnActionProto) String() string { return proto.CompactTextString(m) } +func (*SpawnActionProto) ProtoMessage() {} +func (*SpawnActionProto) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{1} +} + +func (m *SpawnActionProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SpawnActionProto.Unmarshal(m, b) +} +func (m *SpawnActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SpawnActionProto.Marshal(b, m, deterministic) +} +func (m *SpawnActionProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_SpawnActionProto.Merge(m, src) +} +func (m *SpawnActionProto) XXX_Size() int { + return xxx_messageInfo_SpawnActionProto.Size(m) +} +func (m *SpawnActionProto) XXX_DiscardUnknown() { + xxx_messageInfo_SpawnActionProto.DiscardUnknown(m) +} + +var xxx_messageInfo_SpawnActionProto proto.InternalMessageInfo + +func (m *SpawnActionProto) GetContractID() string { + if m != nil { + return m.ContractID + } + return "" +} + +func (m *SpawnActionProto) GetArgument() *any.Any { + if m != nil { + return m.Argument + } + return nil +} + +type InvokeActionProto struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InvokeActionProto) Reset() { *m = InvokeActionProto{} } +func (m *InvokeActionProto) String() string { return proto.CompactTextString(m) } +func (*InvokeActionProto) ProtoMessage() {} +func (*InvokeActionProto) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{2} +} + +func (m *InvokeActionProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InvokeActionProto.Unmarshal(m, b) +} +func (m *InvokeActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InvokeActionProto.Marshal(b, m, deterministic) +} +func (m *InvokeActionProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_InvokeActionProto.Merge(m, src) +} +func (m *InvokeActionProto) XXX_Size() int { + return xxx_messageInfo_InvokeActionProto.Size(m) +} +func (m *InvokeActionProto) XXX_DiscardUnknown() { + xxx_messageInfo_InvokeActionProto.DiscardUnknown(m) +} + +var xxx_messageInfo_InvokeActionProto proto.InternalMessageInfo + +func (m *InvokeActionProto) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *InvokeActionProto) GetArgument() *any.Any { + if m != nil { + return m.Argument + } + return nil +} + +type DeleteActionProto struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeleteActionProto) Reset() { *m = DeleteActionProto{} } +func (m *DeleteActionProto) String() string { return proto.CompactTextString(m) } +func (*DeleteActionProto) ProtoMessage() {} +func (*DeleteActionProto) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{3} +} + +func (m *DeleteActionProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DeleteActionProto.Unmarshal(m, b) +} +func (m *DeleteActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DeleteActionProto.Marshal(b, m, deterministic) +} +func (m *DeleteActionProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeleteActionProto.Merge(m, src) +} +func (m *DeleteActionProto) XXX_Size() int { + return xxx_messageInfo_DeleteActionProto.Size(m) +} +func (m *DeleteActionProto) XXX_DiscardUnknown() { + xxx_messageInfo_DeleteActionProto.DiscardUnknown(m) +} + +var xxx_messageInfo_DeleteActionProto proto.InternalMessageInfo + +func (m *DeleteActionProto) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func init() { + proto.RegisterType((*Instance)(nil), "contract.Instance") + proto.RegisterType((*SpawnActionProto)(nil), "contract.SpawnActionProto") + proto.RegisterType((*InvokeActionProto)(nil), "contract.InvokeActionProto") + proto.RegisterType((*DeleteActionProto)(nil), "contract.DeleteActionProto") +} + +func init() { + proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) +} + +var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ + // 255 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x4f, 0x4b, 0xc3, 0x40, + 0x10, 0xc5, 0x59, 0x6b, 0x35, 0x8e, 0x7f, 0x68, 0x17, 0x0f, 0xab, 0x07, 0x09, 0x41, 0x21, 0x78, + 0x48, 0x45, 0x3f, 0x41, 0xb1, 0x97, 0xdc, 0x24, 0x1e, 0x3c, 0x6f, 0x37, 0x63, 0x90, 0x6e, 0x67, + 0x4a, 0x76, 0x53, 0xc9, 0x37, 0xf2, 0x63, 0x4a, 0x37, 0x44, 0x5a, 0x0a, 0x05, 0x6f, 0xbb, 0x8f, + 0x37, 0xef, 0x37, 0x6f, 0xe0, 0x6a, 0x89, 0xce, 0xe9, 0x0a, 0x5d, 0xb6, 0xaa, 0xd9, 0xb3, 0x8c, + 0x0c, 0x93, 0xaf, 0xb5, 0xf1, 0xb7, 0x37, 0x15, 0x73, 0x65, 0x71, 0x12, 0xf4, 0x79, 0xf3, 0x39, + 0xd1, 0xd4, 0x76, 0xa6, 0xe4, 0x47, 0x40, 0x94, 0x93, 0xf3, 0x9a, 0x0c, 0xca, 0x11, 0x0c, 0x16, + 0xd8, 0x2a, 0x11, 0x8b, 0xf4, 0xa2, 0xd8, 0x3c, 0xe5, 0x23, 0x0c, 0xd7, 0xda, 0x36, 0xa8, 0x8e, + 0x62, 0x91, 0x9e, 0x3f, 0x5f, 0x67, 0x5d, 0x52, 0xd6, 0x27, 0x65, 0x53, 0x6a, 0x8b, 0xce, 0x22, + 0xef, 0x00, 0x7a, 0x62, 0x3e, 0x53, 0x83, 0x58, 0xa4, 0x67, 0xc5, 0x96, 0x22, 0x15, 0x9c, 0x96, + 0x68, 0xd1, 0x63, 0xa9, 0x8e, 0x63, 0x91, 0x46, 0x45, 0xff, 0x95, 0xf7, 0x70, 0xa9, 0x8d, 0x41, + 0xe7, 0x5e, 0x37, 0x6e, 0xb6, 0x6a, 0x18, 0x36, 0xd8, 0x15, 0x93, 0x12, 0x46, 0xef, 0x2b, 0xfd, + 0x4d, 0x53, 0xe3, 0xbf, 0x98, 0xde, 0x42, 0xc7, 0x5d, 0xa6, 0xd8, 0x63, 0x3e, 0x41, 0xa4, 0xeb, + 0xaa, 0x59, 0x22, 0xf9, 0x83, 0x15, 0xfe, 0x5c, 0xc9, 0x07, 0x8c, 0x73, 0x5a, 0xf3, 0x02, 0xb7, + 0x31, 0xfb, 0x87, 0xf9, 0x7f, 0xf0, 0x03, 0x8c, 0x67, 0xa1, 0xef, 0xc1, 0xe0, 0xf9, 0x49, 0x18, + 0x7f, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x55, 0x4b, 0xd8, 0xdd, 0xce, 0x01, 0x00, 0x00, +} diff --git a/ledger/consumer/smartcontract/messages.proto b/ledger/byzcoin/actions/contract/messages.proto similarity index 50% rename from ledger/consumer/smartcontract/messages.proto rename to ledger/byzcoin/actions/contract/messages.proto index fc8ce202f..eb2103a51 100644 --- a/ledger/consumer/smartcontract/messages.proto +++ b/ledger/byzcoin/actions/contract/messages.proto @@ -1,10 +1,10 @@ syntax = "proto3"; -package smartcontract; +package contract; import "google/protobuf/any.proto"; -message InstanceProto { +message Instance { bytes key = 1; google.protobuf.Any value = 2; string contractID = 3; @@ -12,29 +12,16 @@ message InstanceProto { bytes accessControl = 5; } -message Spawn { +message SpawnActionProto { string contractID = 1; google.protobuf.Any argument = 2; } -message Invoke { +message InvokeActionProto { bytes key = 1; google.protobuf.Any argument = 2; } -message Delete { +message DeleteActionProto { bytes key = 1; } - -message TransactionProto { - uint64 nonce = 1; - - oneof action { - Spawn spawn = 2; - Invoke invoke = 3; - Delete delete = 4; - } - - google.protobuf.Any identity = 5; - google.protobuf.Any signature = 6; -} diff --git a/ledger/byzcoin/actions/contract/mod.go b/ledger/byzcoin/actions/contract/mod.go new file mode 100644 index 000000000..0939255b7 --- /dev/null +++ b/ledger/byzcoin/actions/contract/mod.go @@ -0,0 +1,29 @@ +package contract + +import ( + proto "github.com/golang/protobuf/proto" + "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/transactions/basic" +) + +//go:generate protoc -I ./ --go_out=./ ./messages.proto + +// Context is provided during a transaction execution. +type Context interface { + basic.Context + + GetArc([]byte) (arc.AccessControl, error) + + Read([]byte) (*Instance, error) +} + +// Contract is an interface that provides the primitives to execute a smart +// contract transaction and produce the resulting instance. +type Contract interface { + // Spawn is called to create a new instance. It returns the initial value of + // the new instance and its access rights control (arc) ID. + Spawn(ctx SpawnContext) (proto.Message, []byte, error) + + // Invoke is called to update an existing instance. + Invoke(ctx InvokeContext) (proto.Message, error) +} diff --git a/ledger/byzcoin/actions/contract/mod_test.go b/ledger/byzcoin/actions/contract/mod_test.go new file mode 100644 index 000000000..119c3dfcd --- /dev/null +++ b/ledger/byzcoin/actions/contract/mod_test.go @@ -0,0 +1,21 @@ +package contract + +import ( + "testing" + + "github.com/golang/protobuf/proto" + internal "go.dedis.ch/fabric/internal/testing" +) + +func TestMessages(t *testing.T) { + messages := []proto.Message{ + &Instance{}, + &SpawnActionProto{}, + &InvokeActionProto{}, + &DeleteActionProto{}, + } + + for _, m := range messages { + internal.CoverProtoMessage(t, m) + } +} diff --git a/ledger/byzcoin/actions/mod.go b/ledger/byzcoin/actions/mod.go new file mode 100644 index 000000000..7f4e56b73 --- /dev/null +++ b/ledger/byzcoin/actions/mod.go @@ -0,0 +1,58 @@ +package actions + +import ( + "reflect" + + "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/arc/darc" + "go.dedis.ch/fabric/ledger/transactions/basic" + "golang.org/x/xerrors" +) + +type ActionFactory struct { + encoder encoding.ProtoMarshaler + registry map[reflect.Type]basic.ActionFactory +} + +// NewActionFactory creates a new action factory. +func NewActionFactory() basic.ActionFactory { + f := ActionFactory{ + encoder: encoding.NewProtoEncoder(), + registry: make(map[reflect.Type]basic.ActionFactory), + } + + f.Register((*darc.ActionProto)(nil), darc.NewActionFactory()) + + return f +} + +func (f ActionFactory) Register(pb proto.Message, factory basic.ActionFactory) { + key := reflect.TypeOf(pb) + f.registry[key] = factory +} + +func (f ActionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { + inAny, ok := in.(*any.Any) + if ok { + var err error + in, err = f.encoder.UnmarshalDynamicAny(inAny) + if err != nil { + return nil, err + } + } + + key := reflect.TypeOf(in) + factory := f.registry[key] + if factory == nil { + return nil, xerrors.Errorf("unknown action type '%T'", in) + } + + action, err := factory.FromProto(in) + if err != nil { + return nil, err + } + + return action, nil +} diff --git a/ledger/byzcoin/mod.go b/ledger/byzcoin/mod.go index a840f7baa..0344fa9f0 100644 --- a/ledger/byzcoin/mod.go +++ b/ledger/byzcoin/mod.go @@ -15,8 +15,10 @@ import ( "go.dedis.ch/fabric/crypto" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger" - "go.dedis.ch/fabric/ledger/consumer" + "go.dedis.ch/fabric/ledger/byzcoin/actions" "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions" + "go.dedis.ch/fabric/ledger/transactions/basic" "go.dedis.ch/fabric/mino" "go.dedis.ch/fabric/mino/gossip" "golang.org/x/xerrors" @@ -49,24 +51,25 @@ type Ledger struct { bag *txBag proc *txProcessor governance governance - consumer consumer.Consumer encoder encoding.ProtoMarshaler + txFactory transactions.TransactionFactory closing chan struct{} initiated chan error } // NewLedger creates a new Byzcoin ledger. -func NewLedger(mino mino.Mino, signer crypto.AggregateSigner, consumer consumer.Consumer) *Ledger { - cosi := flatcosi.NewFlat(mino, signer) +func NewLedger(mino mino.Mino, signer crypto.AggregateSigner) *Ledger { + factory := basic.NewTransactionFactory(signer, actions.NewActionFactory()) decoder := func(pb proto.Message) (gossip.Rumor, error) { - return consumer.GetTransactionFactory().FromProto(pb) + return factory.FromProto(pb) } - proc := newTxProcessor(consumer) + proc := newTxProcessor(factory) gov := governance{ inventory: proc.inventory, rosterFactory: newRosterFactory(mino.GetAddressFactory(), signer.GetPublicKeyFactory()), } + cosi := flatcosi.NewFlat(mino, signer) consensus := cosipbft.NewCoSiPBFT(mino, cosi, gov) return &Ledger{ @@ -77,37 +80,31 @@ func NewLedger(mino mino.Mino, signer crypto.AggregateSigner, consumer consumer. bag: newTxBag(), proc: proc, governance: gov, - consumer: consumer, encoder: encoding.NewProtoEncoder(), + txFactory: factory, closing: make(chan struct{}), initiated: make(chan error, 1), } } -// GetInstance implements ledger.Ledger. It returns the instance with the given -// key as of the latest block. -func (ldgr *Ledger) GetInstance(key []byte) (consumer.Instance, error) { +// GetValue implements ledger.Ledger. +func (ldgr *Ledger) GetValue(key []byte) (proto.Message, error) { latest, err := ldgr.bc.GetBlock() if err != nil { - return nil, xerrors.Errorf("couldn't read latest block: %v", err) + return nil, err } page, err := ldgr.proc.inventory.GetPage(latest.GetIndex()) if err != nil { - return nil, xerrors.Errorf("couldn't read the page: %v", err) + return nil, err } - instancepb, err := page.Read(key) + value, err := page.Read(key) if err != nil { - return nil, xerrors.Errorf("couldn't read the instance: %v", err) + return nil, err } - instance, err := ldgr.consumer.GetInstanceFactory().FromProto(instancepb) - if err != nil { - return nil, xerrors.Errorf("couldn't decode instance: %v", err) - } - - return instance, nil + return value, nil } // Listen implements ledger.Ledger. It starts to participate in the blockchain @@ -173,7 +170,7 @@ func (ldgr *Ledger) gossipTxs() { return case rumor := <-ldgr.gossiper.Rumors(): - tx, ok := rumor.(consumer.Transaction) + tx, ok := rumor.(transactions.ClientTransaction) if ok { ldgr.bag.Add(tx) } @@ -214,11 +211,9 @@ func (ldgr *Ledger) proposeBlocks(actor blockchain.Actor, players mino.Players) break } - factory := ldgr.consumer.GetTransactionFactory() - txRes := make([]TransactionResult, len(payload.GetTransactions())) for i, txProto := range payload.GetTransactions() { - tx, err := factory.FromProto(txProto) + tx, err := ldgr.txFactory.FromProto(txProto) if err != nil { fabric.Logger.Warn().Err(err).Msg("couldn't decode transaction") return @@ -263,7 +258,7 @@ func (ldgr *Ledger) proposeBlock(actor blockchain.Actor, players mino.Players) e // stagePayload creates a payload with the list of transactions by staging a new // snapshot to the inventory. -func (ldgr *Ledger) stagePayload(txs []consumer.Transaction) (*BlockPayload, error) { +func (ldgr *Ledger) stagePayload(txs []transactions.ClientTransaction) (*BlockPayload, error) { fabric.Logger.Trace(). Str("addr", ldgr.addr.String()). Msgf("staging payload with %d transactions", len(txs)) @@ -308,10 +303,8 @@ func (ldgr *Ledger) Watch(ctx context.Context) <-chan ledger.TransactionResult { payload, ok := block.GetPayload().(*BlockPayload) if ok { - factory := ldgr.consumer.GetTransactionFactory() - for _, txProto := range payload.GetTransactions() { - tx, err := factory.FromProto(txProto) + tx, err := ldgr.txFactory.FromProto(txProto) if err != nil { fabric.Logger.Warn().Err(err).Msg("couldn't decode transaction") return @@ -375,7 +368,7 @@ func (a actorLedger) Setup(players mino.Players) error { // AddTransaction implements ledger.Actor. It sends the transaction towards the // consensus layer. -func (a actorLedger) AddTransaction(tx consumer.Transaction) error { +func (a actorLedger) AddTransaction(tx transactions.ClientTransaction) error { // The gossiper will propagate the transaction to other players but also to // the transaction buffer of this player. // TODO: gossiper should accept tx before it has started. diff --git a/ledger/byzcoin/mod_test.go b/ledger/byzcoin/mod_test.go index 6818698e8..a14918c29 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -16,9 +16,7 @@ import ( "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/ledger" "go.dedis.ch/fabric/ledger/arc/darc" - "go.dedis.ch/fabric/ledger/arc/darc/contract" - "go.dedis.ch/fabric/ledger/consumer" - "go.dedis.ch/fabric/ledger/consumer/smartcontract" + "go.dedis.ch/fabric/ledger/transactions/basic" "go.dedis.ch/fabric/mino" "go.dedis.ch/fabric/mino/minoch" "golang.org/x/xerrors" @@ -60,10 +58,10 @@ func TestLedger_Basic(t *testing.T) { defer cancel() txs := ledgers[2].Watch(ctx) - txFactory := smartcontract.NewTransactionFactory(bls.NewSigner()) + txFactory := basic.NewTransactionFactory(bls.NewSigner(), nil) // Try to create a DARC. - tx, err := txFactory.New(contract.NewGenesisAction()) + tx, err := txFactory.New(darc.NewCreate()) require.NoError(t, err) err = actors[1].AddTransaction(tx) @@ -77,14 +75,12 @@ func TestLedger_Basic(t *testing.T) { t.Fatal("timeout 1") } - instance, err := ledgers[2].GetInstance(tx.GetID()) + value, err := ledgers[2].GetValue(tx.GetID()) require.NoError(t, err) - require.Equal(t, tx.GetID(), instance.GetKey()) - require.Equal(t, tx.GetID(), instance.GetArcID()) - require.IsType(t, (*darc.AccessControlProto)(nil), instance.GetValue()) + require.IsType(t, (*darc.AccessControlProto)(nil), value) // Then update it. - tx, err = txFactory.New(contract.NewUpdateAction(tx.GetID())) + tx, err = txFactory.New(darc.NewUpdate(tx.GetID())) require.NoError(t, err) err = actors[0].AddTransaction(tx) @@ -99,40 +95,6 @@ func TestLedger_Basic(t *testing.T) { } } -func TestLedger_GetInstance(t *testing.T) { - ledger := &Ledger{ - bc: fakeBlockchain{}, - proc: &txProcessor{ - inventory: fakeInventory{ - page: &fakePage{}, - }, - }, - consumer: fakeConsumer{}, - } - - instance, err := ledger.GetInstance([]byte{0xab}) - require.NoError(t, err) - require.NotNil(t, instance) - - ledger.bc = fakeBlockchain{err: xerrors.New("oops")} - _, err = ledger.GetInstance(nil) - require.EqualError(t, err, "couldn't read latest block: oops") - - ledger.bc = fakeBlockchain{} - ledger.proc.inventory = fakeInventory{err: xerrors.New("oops")} - _, err = ledger.GetInstance(nil) - require.EqualError(t, err, "couldn't read the page: oops") - - ledger.proc.inventory = fakeInventory{page: &fakePage{err: xerrors.New("oops")}} - _, err = ledger.GetInstance(nil) - require.EqualError(t, err, "couldn't read the instance: oops") - - ledger.proc.inventory = fakeInventory{page: &fakePage{}} - ledger.consumer = fakeConsumer{errFactory: xerrors.New("oops")} - _, err = ledger.GetInstance(nil) - require.EqualError(t, err, "couldn't decode instance: oops") -} - func TestGovernance_GetAuthority(t *testing.T) { factory := rosterFactory{ addressFactory: fake.AddressFactory{}, @@ -183,7 +145,7 @@ func makeLedger(t *testing.T, n int) ([]ledger.Ledger, []ledger.Actor, crypto.Co ledgers := make([]ledger.Ledger, n) actors := make([]ledger.Actor, n) for i, m := range minos { - ledger := NewLedger(m, ca.GetSigner(i), makeConsumer()) + ledger := NewLedger(m, ca.GetSigner(i)) ledgers[i] = ledger actor, err := ledger.Listen() @@ -195,13 +157,6 @@ func makeLedger(t *testing.T, n int) ([]ledger.Ledger, []ledger.Actor, crypto.Co return ledgers, actors, ca } -func makeConsumer() consumer.Consumer { - c := smartcontract.NewConsumer() - contract.RegisterContract(c) - - return c -} - type fakeBlock struct { blockchain.Block } diff --git a/ledger/byzcoin/txbag.go b/ledger/byzcoin/txbag.go index acd997ba7..c5aa6ccfb 100644 --- a/ledger/byzcoin/txbag.go +++ b/ledger/byzcoin/txbag.go @@ -3,7 +3,7 @@ package byzcoin import ( "sync" - "go.dedis.ch/fabric/ledger/consumer" + "go.dedis.ch/fabric/ledger/transactions" ) // Key is type used to differentiate the transactions in the bag. @@ -13,21 +13,21 @@ type Key [32]byte // waiting to be included in a block. type txBag struct { sync.Mutex - buffer map[Key]consumer.Transaction + buffer map[Key]transactions.ClientTransaction } func newTxBag() *txBag { return &txBag{ - buffer: make(map[Key]consumer.Transaction), + buffer: make(map[Key]transactions.ClientTransaction), } } // GetAll returns a list of the transactions currently queued. -func (q *txBag) GetAll() []consumer.Transaction { +func (q *txBag) GetAll() []transactions.ClientTransaction { q.Lock() defer q.Unlock() - txs := make([]consumer.Transaction, 0, len(q.buffer)) + txs := make([]transactions.ClientTransaction, 0, len(q.buffer)) for _, tx := range q.buffer { txs = append(txs, tx) } @@ -36,7 +36,7 @@ func (q *txBag) GetAll() []consumer.Transaction { } // Add adds the transaction to the queue. -func (q *txBag) Add(tx consumer.Transaction) { +func (q *txBag) Add(tx transactions.ClientTransaction) { key := Key{} copy(key[:], tx.GetID()) diff --git a/ledger/byzcoin/txbag_test.go b/ledger/byzcoin/txbag_test.go index 986422e15..c9ef37eb6 100644 --- a/ledger/byzcoin/txbag_test.go +++ b/ledger/byzcoin/txbag_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" - "go.dedis.ch/fabric/ledger/consumer" + "go.dedis.ch/fabric/ledger/transactions" ) func TestTxQueue_GetAll(t *testing.T) { @@ -52,7 +52,7 @@ func TestTxQueue_Remove(t *testing.T) { // Utility functions type fakeTx struct { - consumer.Transaction + transactions.ClientTransaction id []byte } diff --git a/ledger/byzcoin/txproc.go b/ledger/byzcoin/txproc.go index efadfe8d1..644b50584 100644 --- a/ledger/byzcoin/txproc.go +++ b/ledger/byzcoin/txproc.go @@ -5,11 +5,9 @@ import ( proto "github.com/golang/protobuf/proto" "go.dedis.ch/fabric" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/ledger/consumer" - "go.dedis.ch/fabric/ledger/consumer/smartcontract" "go.dedis.ch/fabric/ledger/inventory" "go.dedis.ch/fabric/ledger/inventory/mem" + "go.dedis.ch/fabric/ledger/transactions" "golang.org/x/xerrors" ) @@ -18,16 +16,14 @@ import ( // // - implements blockchain.PayloadProcessor type txProcessor struct { - encoder encoding.ProtoMarshaler inventory inventory.Inventory - consumer consumer.Consumer + txFactory transactions.TransactionFactory } -func newTxProcessor(c consumer.Consumer) *txProcessor { +func newTxProcessor(f transactions.TransactionFactory) *txProcessor { return &txProcessor{ - encoder: encoding.NewProtoEncoder(), inventory: mem.NewInventory(), - consumer: c, + txFactory: f, } } @@ -94,32 +90,18 @@ func (proc *txProcessor) process(payload *BlockPayload) (inventory.Page, error) } page, err := proc.inventory.Stage(func(page inventory.WritablePage) error { - factory := proc.consumer.GetTransactionFactory() - for _, txpb := range payload.GetTransactions() { - tx, err := factory.FromProto(txpb) + tx, err := proc.txFactory.FromProto(txpb) if err != nil { return xerrors.Errorf("couldn't decode tx: %v", err) } fabric.Logger.Trace().Msgf("processing %v", tx) - ctx := smartcontract.NewContext(tx, page) - - instance, err := proc.consumer.Consume(ctx) + err = tx.Consume(page) if err != nil { return xerrors.Errorf("couldn't consume tx: %v", err) } - - instancepb, err := proc.encoder.Pack(instance) - if err != nil { - return xerrors.Errorf("couldn't pack instance: %v", err) - } - - err = page.Write(instance.GetKey(), instancepb) - if err != nil { - return xerrors.Errorf("couldn't write instances: %v", err) - } } return nil diff --git a/ledger/byzcoin/txproc_test.go b/ledger/byzcoin/txproc_test.go index 04ad3e7ba..83522a146 100644 --- a/ledger/byzcoin/txproc_test.go +++ b/ledger/byzcoin/txproc_test.go @@ -5,17 +5,14 @@ import ( proto "github.com/golang/protobuf/proto" any "github.com/golang/protobuf/ptypes/any" - "github.com/golang/protobuf/ptypes/empty" "github.com/stretchr/testify/require" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/internal/testing/fake" - "go.dedis.ch/fabric/ledger/consumer" "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions" "golang.org/x/xerrors" ) func TestTxProcessor_Validate(t *testing.T) { - proc := newTxProcessor(fakeConsumer{}) + proc := newTxProcessor(nil) proc.inventory = fakeInventory{} err := proc.Validate(0, &BlockPayload{}) @@ -52,7 +49,6 @@ func TestTxProcessor_Validate(t *testing.T) { func TestTxProcessor_Process(t *testing.T) { proc := newTxProcessor(nil) proc.inventory = fakeInventory{page: &fakePage{index: 999}} - proc.consumer = fakeConsumer{key: []byte{0xab}} page, err := proc.process(&BlockPayload{}) require.NoError(t, err) @@ -60,33 +56,9 @@ func TestTxProcessor_Process(t *testing.T) { payload := &BlockPayload{Transactions: []*any.Any{{}}} - proc.inventory = fakeInventory{} + proc.inventory = fakeInventory{page: &fakePage{}} page, err = proc.process(payload) require.NoError(t, err) - require.Len(t, page.(*fakePage).calls, 1) - require.Equal(t, []byte{0xab}, page.(*fakePage).calls[0][0]) - - proc.consumer = fakeConsumer{errFactory: xerrors.New("oops")} - _, err = proc.process(payload) - require.EqualError(t, err, - "couldn't stage new page: couldn't decode tx: oops") - - proc.consumer = fakeConsumer{err: xerrors.New("oops")} - _, err = proc.process(payload) - require.EqualError(t, err, - "couldn't stage new page: couldn't consume tx: oops") - - proc.consumer = fakeConsumer{} - proc.encoder = fake.BadPackEncoder{} - _, err = proc.process(payload) - require.EqualError(t, err, - "couldn't stage new page: couldn't pack instance: fake error") - - proc.encoder = encoding.NewProtoEncoder() - proc.inventory = fakeInventory{errPage: xerrors.New("oops")} - _, err = proc.process(payload) - require.EqualError(t, err, - "couldn't stage new page: couldn't write instances: oops") } func TestTxProcessor_Commit(t *testing.T) { @@ -176,53 +148,10 @@ func (inv fakeInventory) Commit([]byte) error { } type fakeTxFactory struct { - consumer.TransactionFactory + transactions.TransactionFactory err error } -func (f fakeTxFactory) FromProto(proto.Message) (consumer.Transaction, error) { +func (f fakeTxFactory) FromProto(proto.Message) (transactions.ServerTransaction, error) { return nil, f.err } - -type fakeInstance struct { - consumer.Instance - key []byte - err error -} - -func (i fakeInstance) GetKey() []byte { - return i.key -} - -func (i fakeInstance) Pack(encoding.ProtoMarshaler) (proto.Message, error) { - return &empty.Empty{}, i.err -} - -type fakeInstanceFactory struct { - consumer.InstanceFactory - err error -} - -func (f fakeInstanceFactory) FromProto(proto.Message) (consumer.Instance, error) { - return fakeInstance{}, f.err -} - -type fakeConsumer struct { - consumer.Consumer - key []byte - err error - errFactory error - errInstance error -} - -func (c fakeConsumer) GetTransactionFactory() consumer.TransactionFactory { - return fakeTxFactory{err: c.errFactory} -} - -func (c fakeConsumer) GetInstanceFactory() consumer.InstanceFactory { - return fakeInstanceFactory{err: c.errFactory} -} - -func (c fakeConsumer) Consume(consumer.Context) (consumer.Instance, error) { - return fakeInstance{key: c.key, err: c.errInstance}, c.err -} diff --git a/ledger/consumer/mod.go b/ledger/consumer/mod.go deleted file mode 100644 index 88efd272c..000000000 --- a/ledger/consumer/mod.go +++ /dev/null @@ -1,67 +0,0 @@ -package consumer - -import ( - "github.com/golang/protobuf/proto" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/ledger/arc" -) - -// Transaction is an atomic execution of one or several instructions. -type Transaction interface { - encoding.Packable - - // GetID returns a unique identifier for the transaction. - GetID() []byte - - // GetIdentity returns the identity of the signer of the transaction. - GetIdentity() arc.Identity -} - -// TransactionFactory is a factory to create new transactions or decode from -// network messages. -type TransactionFactory interface { - // FromProto returns the transaction from the protobuf message. - FromProto(pb proto.Message) (Transaction, error) -} - -// Instance is the result of a transaction execution. -type Instance interface { - encoding.Packable - - // GetKey returns the identifier of the instance. - GetKey() []byte - - // GetArcID returns the access rights control identifier. - GetArcID() []byte - - // GetValue returns the value stored in the instance. - GetValue() proto.Message -} - -// InstanceFactory is an abstraction to decode protobuf messages into instances. -type InstanceFactory interface { - // FromProto returns the instance from the protobuf message. - FromProto(pb proto.Message) (Instance, error) -} - -// Context is provided during a transaction execution. -type Context interface { - GetTransaction() Transaction - - Read([]byte) (Instance, error) -} - -// Consumer is an abstraction for a ledger to consume the incoming transactions. -// It is responsible for processing the transactions and producing the instances -// that will later be stored in the inventory. -type Consumer interface { - // GetTransactionFactory returns the transaction factory. - GetTransactionFactory() TransactionFactory - - // GetInstanceFactory returns the instance factory. - GetInstanceFactory() InstanceFactory - - // Consume returns the resulting instance of the transaction execution. The - // current page of the inventory is provided. - Consume(ctx Context) (Instance, error) -} diff --git a/ledger/consumer/smartcontract/context.go b/ledger/consumer/smartcontract/context.go deleted file mode 100644 index 2752ad2df..000000000 --- a/ledger/consumer/smartcontract/context.go +++ /dev/null @@ -1,73 +0,0 @@ -package smartcontract - -import ( - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/ledger/consumer" - "go.dedis.ch/fabric/ledger/inventory" - "golang.org/x/xerrors" -) - -type transactionContext struct { - encoder encoding.ProtoMarshaler - tx consumer.Transaction - page inventory.Page -} - -// NewContext returns a new instance of a smart contract transaction context. -// -// - implements consumer.Context -func NewContext(tx consumer.Transaction, page inventory.Page) consumer.Context { - return transactionContext{ - encoder: encoding.NewProtoEncoder(), - tx: tx, - page: page, - } -} - -// GetTransaction implements consumer.Context. It returns the transaction of the -// context. -func (ctx transactionContext) GetTransaction() consumer.Transaction { - return ctx.tx -} - -// Read implements consumer.Context. It returns the instance stored at the given -// key, or an error if it does not find it. -func (ctx transactionContext) Read(key []byte) (consumer.Instance, error) { - entry, err := ctx.page.Read(key) - if err != nil { - return nil, xerrors.Errorf("couldn't read the entry: %v", err) - } - - factory := instanceFactory{encoder: ctx.encoder} - - instance, err := factory.FromProto(entry) - if err != nil { - return nil, xerrors.Errorf("couldn't decode instance: %v", err) - } - - return instance, nil -} - -// SpawnContext is the context provided to a smart contract execution of a spawn -// transaction. -type SpawnContext struct { - consumer.Context - Action SpawnAction -} - -// GetAction returns the transaction casted as a spawn transaction. -func (ctx SpawnContext) GetAction() SpawnAction { - return ctx.Action -} - -// InvokeContext is the context provided to a smart contract execution of an -// invoke transaction. -type InvokeContext struct { - consumer.Context - Action InvokeAction -} - -// GetAction returns the transaction casted as an invoke transaction. -func (ctx InvokeContext) GetAction() InvokeAction { - return ctx.Action -} diff --git a/ledger/consumer/smartcontract/context_test.go b/ledger/consumer/smartcontract/context_test.go deleted file mode 100644 index 1c1d43a44..000000000 --- a/ledger/consumer/smartcontract/context_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package smartcontract - -import ( - "testing" - - "github.com/golang/protobuf/ptypes" - "github.com/golang/protobuf/ptypes/empty" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" -) - -func TestTransactionContext_GetTransaction(t *testing.T) { - tx := transaction{} - ctx := NewContext(tx, nil) - - require.Equal(t, tx, ctx.GetTransaction()) -} - -func TestTransactionContext_Read(t *testing.T) { - valueAny, err := ptypes.MarshalAny(&empty.Empty{}) - require.NoError(t, err) - - ctx := NewContext( - nil, - fakePage{ - instance: &InstanceProto{ - ContractID: "abc", - Value: valueAny, - }, - }, - ) - - instance, err := ctx.Read([]byte{0xab}) - require.NoError(t, err) - require.Equal(t, "abc", instance.(ContractInstance).GetContractID()) - require.IsType(t, (*empty.Empty)(nil), instance.GetValue()) - - ctx = NewContext(nil, fakePage{err: xerrors.New("oops")}) - _, err = ctx.Read(nil) - require.EqualError(t, err, "couldn't read the entry: oops") - - ctx = NewContext(nil, fakePage{instance: &empty.Empty{}}) - _, err = ctx.Read(nil) - require.EqualError(t, err, "couldn't decode instance: invalid instance type '*empty.Empty'") -} diff --git a/ledger/consumer/smartcontract/messages.pb.go b/ledger/consumer/smartcontract/messages.pb.go deleted file mode 100644 index 7b4e6ce87..000000000 --- a/ledger/consumer/smartcontract/messages.pb.go +++ /dev/null @@ -1,384 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: messages.proto - -package smartcontract - -import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - any "github.com/golang/protobuf/ptypes/any" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type InstanceProto struct { - Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value *any.Any `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - ContractID string `protobuf:"bytes,3,opt,name=contractID,proto3" json:"contractID,omitempty"` - Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"` - AccessControl []byte `protobuf:"bytes,5,opt,name=accessControl,proto3" json:"accessControl,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *InstanceProto) Reset() { *m = InstanceProto{} } -func (m *InstanceProto) String() string { return proto.CompactTextString(m) } -func (*InstanceProto) ProtoMessage() {} -func (*InstanceProto) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{0} -} - -func (m *InstanceProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_InstanceProto.Unmarshal(m, b) -} -func (m *InstanceProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_InstanceProto.Marshal(b, m, deterministic) -} -func (m *InstanceProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_InstanceProto.Merge(m, src) -} -func (m *InstanceProto) XXX_Size() int { - return xxx_messageInfo_InstanceProto.Size(m) -} -func (m *InstanceProto) XXX_DiscardUnknown() { - xxx_messageInfo_InstanceProto.DiscardUnknown(m) -} - -var xxx_messageInfo_InstanceProto proto.InternalMessageInfo - -func (m *InstanceProto) GetKey() []byte { - if m != nil { - return m.Key - } - return nil -} - -func (m *InstanceProto) GetValue() *any.Any { - if m != nil { - return m.Value - } - return nil -} - -func (m *InstanceProto) GetContractID() string { - if m != nil { - return m.ContractID - } - return "" -} - -func (m *InstanceProto) GetDeleted() bool { - if m != nil { - return m.Deleted - } - return false -} - -func (m *InstanceProto) GetAccessControl() []byte { - if m != nil { - return m.AccessControl - } - return nil -} - -type Spawn struct { - ContractID string `protobuf:"bytes,1,opt,name=contractID,proto3" json:"contractID,omitempty"` - Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Spawn) Reset() { *m = Spawn{} } -func (m *Spawn) String() string { return proto.CompactTextString(m) } -func (*Spawn) ProtoMessage() {} -func (*Spawn) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{1} -} - -func (m *Spawn) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Spawn.Unmarshal(m, b) -} -func (m *Spawn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Spawn.Marshal(b, m, deterministic) -} -func (m *Spawn) XXX_Merge(src proto.Message) { - xxx_messageInfo_Spawn.Merge(m, src) -} -func (m *Spawn) XXX_Size() int { - return xxx_messageInfo_Spawn.Size(m) -} -func (m *Spawn) XXX_DiscardUnknown() { - xxx_messageInfo_Spawn.DiscardUnknown(m) -} - -var xxx_messageInfo_Spawn proto.InternalMessageInfo - -func (m *Spawn) GetContractID() string { - if m != nil { - return m.ContractID - } - return "" -} - -func (m *Spawn) GetArgument() *any.Any { - if m != nil { - return m.Argument - } - return nil -} - -type Invoke struct { - Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Invoke) Reset() { *m = Invoke{} } -func (m *Invoke) String() string { return proto.CompactTextString(m) } -func (*Invoke) ProtoMessage() {} -func (*Invoke) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{2} -} - -func (m *Invoke) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Invoke.Unmarshal(m, b) -} -func (m *Invoke) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Invoke.Marshal(b, m, deterministic) -} -func (m *Invoke) XXX_Merge(src proto.Message) { - xxx_messageInfo_Invoke.Merge(m, src) -} -func (m *Invoke) XXX_Size() int { - return xxx_messageInfo_Invoke.Size(m) -} -func (m *Invoke) XXX_DiscardUnknown() { - xxx_messageInfo_Invoke.DiscardUnknown(m) -} - -var xxx_messageInfo_Invoke proto.InternalMessageInfo - -func (m *Invoke) GetKey() []byte { - if m != nil { - return m.Key - } - return nil -} - -func (m *Invoke) GetArgument() *any.Any { - if m != nil { - return m.Argument - } - return nil -} - -type Delete struct { - Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Delete) Reset() { *m = Delete{} } -func (m *Delete) String() string { return proto.CompactTextString(m) } -func (*Delete) ProtoMessage() {} -func (*Delete) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{3} -} - -func (m *Delete) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Delete.Unmarshal(m, b) -} -func (m *Delete) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Delete.Marshal(b, m, deterministic) -} -func (m *Delete) XXX_Merge(src proto.Message) { - xxx_messageInfo_Delete.Merge(m, src) -} -func (m *Delete) XXX_Size() int { - return xxx_messageInfo_Delete.Size(m) -} -func (m *Delete) XXX_DiscardUnknown() { - xxx_messageInfo_Delete.DiscardUnknown(m) -} - -var xxx_messageInfo_Delete proto.InternalMessageInfo - -func (m *Delete) GetKey() []byte { - if m != nil { - return m.Key - } - return nil -} - -type TransactionProto struct { - Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"` - // Types that are valid to be assigned to Action: - // *TransactionProto_Spawn - // *TransactionProto_Invoke - // *TransactionProto_Delete - Action isTransactionProto_Action `protobuf_oneof:"action"` - Identity *any.Any `protobuf:"bytes,5,opt,name=identity,proto3" json:"identity,omitempty"` - Signature *any.Any `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *TransactionProto) Reset() { *m = TransactionProto{} } -func (m *TransactionProto) String() string { return proto.CompactTextString(m) } -func (*TransactionProto) ProtoMessage() {} -func (*TransactionProto) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{4} -} - -func (m *TransactionProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TransactionProto.Unmarshal(m, b) -} -func (m *TransactionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TransactionProto.Marshal(b, m, deterministic) -} -func (m *TransactionProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_TransactionProto.Merge(m, src) -} -func (m *TransactionProto) XXX_Size() int { - return xxx_messageInfo_TransactionProto.Size(m) -} -func (m *TransactionProto) XXX_DiscardUnknown() { - xxx_messageInfo_TransactionProto.DiscardUnknown(m) -} - -var xxx_messageInfo_TransactionProto proto.InternalMessageInfo - -func (m *TransactionProto) GetNonce() uint64 { - if m != nil { - return m.Nonce - } - return 0 -} - -type isTransactionProto_Action interface { - isTransactionProto_Action() -} - -type TransactionProto_Spawn struct { - Spawn *Spawn `protobuf:"bytes,2,opt,name=spawn,proto3,oneof"` -} - -type TransactionProto_Invoke struct { - Invoke *Invoke `protobuf:"bytes,3,opt,name=invoke,proto3,oneof"` -} - -type TransactionProto_Delete struct { - Delete *Delete `protobuf:"bytes,4,opt,name=delete,proto3,oneof"` -} - -func (*TransactionProto_Spawn) isTransactionProto_Action() {} - -func (*TransactionProto_Invoke) isTransactionProto_Action() {} - -func (*TransactionProto_Delete) isTransactionProto_Action() {} - -func (m *TransactionProto) GetAction() isTransactionProto_Action { - if m != nil { - return m.Action - } - return nil -} - -func (m *TransactionProto) GetSpawn() *Spawn { - if x, ok := m.GetAction().(*TransactionProto_Spawn); ok { - return x.Spawn - } - return nil -} - -func (m *TransactionProto) GetInvoke() *Invoke { - if x, ok := m.GetAction().(*TransactionProto_Invoke); ok { - return x.Invoke - } - return nil -} - -func (m *TransactionProto) GetDelete() *Delete { - if x, ok := m.GetAction().(*TransactionProto_Delete); ok { - return x.Delete - } - return nil -} - -func (m *TransactionProto) GetIdentity() *any.Any { - if m != nil { - return m.Identity - } - return nil -} - -func (m *TransactionProto) GetSignature() *any.Any { - if m != nil { - return m.Signature - } - return nil -} - -// XXX_OneofWrappers is for the internal use of the proto package. -func (*TransactionProto) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*TransactionProto_Spawn)(nil), - (*TransactionProto_Invoke)(nil), - (*TransactionProto_Delete)(nil), - } -} - -func init() { - proto.RegisterType((*InstanceProto)(nil), "smartcontract.InstanceProto") - proto.RegisterType((*Spawn)(nil), "smartcontract.Spawn") - proto.RegisterType((*Invoke)(nil), "smartcontract.Invoke") - proto.RegisterType((*Delete)(nil), "smartcontract.Delete") - proto.RegisterType((*TransactionProto)(nil), "smartcontract.TransactionProto") -} - -func init() { - proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) -} - -var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 360 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xc1, 0x4f, 0xc2, 0x30, - 0x14, 0xc6, 0x19, 0xb0, 0x09, 0x0f, 0x31, 0xa4, 0xc1, 0x64, 0x72, 0x30, 0xcb, 0xe2, 0x61, 0x31, - 0x66, 0x18, 0xfc, 0x0b, 0x54, 0x0e, 0x90, 0x78, 0x30, 0xd3, 0x8b, 0xc7, 0x52, 0x9e, 0xcb, 0xc2, - 0x68, 0xc9, 0xda, 0x61, 0xf6, 0x1f, 0x79, 0xf1, 0x7f, 0x34, 0x6b, 0x1d, 0x0a, 0x28, 0x89, 0xb7, - 0xb5, 0xfb, 0x7d, 0xef, 0xeb, 0xf7, 0x3d, 0x38, 0x59, 0xa2, 0x94, 0x34, 0x46, 0x19, 0xae, 0x32, - 0xa1, 0x04, 0xe9, 0xca, 0x25, 0xcd, 0x14, 0x13, 0x5c, 0x65, 0x94, 0xa9, 0xc1, 0x59, 0x2c, 0x44, - 0x9c, 0xe2, 0x50, 0xff, 0x9c, 0xe5, 0xaf, 0x43, 0xca, 0x0b, 0x43, 0xfa, 0x1f, 0x16, 0x74, 0xa7, - 0x5c, 0x2a, 0xca, 0x19, 0x3e, 0x6a, 0x6d, 0x0f, 0x1a, 0x0b, 0x2c, 0x5c, 0xcb, 0xb3, 0x82, 0xe3, - 0xa8, 0xfc, 0x24, 0x97, 0x60, 0xaf, 0x69, 0x9a, 0xa3, 0x5b, 0xf7, 0xac, 0xa0, 0x33, 0xea, 0x87, - 0x66, 0x5c, 0x58, 0x8d, 0x0b, 0x6f, 0x79, 0x11, 0x19, 0x84, 0x9c, 0x03, 0x54, 0xb6, 0xd3, 0xb1, - 0xdb, 0xf0, 0xac, 0xa0, 0x1d, 0xfd, 0xb8, 0x21, 0x2e, 0x1c, 0xcd, 0x31, 0x45, 0x85, 0x73, 0xb7, - 0xe9, 0x59, 0x41, 0x2b, 0xaa, 0x8e, 0xe4, 0x02, 0xba, 0x94, 0x31, 0x94, 0xf2, 0xbe, 0xa4, 0x45, - 0xea, 0xda, 0xfa, 0x05, 0xdb, 0x97, 0xfe, 0x0b, 0xd8, 0x4f, 0x2b, 0xfa, 0xc6, 0x77, 0x8c, 0xac, - 0x3d, 0xa3, 0x6b, 0x68, 0xd1, 0x2c, 0xce, 0x97, 0xc8, 0xd5, 0xc1, 0x77, 0x6f, 0x28, 0xff, 0x01, - 0x9c, 0x29, 0x5f, 0x8b, 0x05, 0xfe, 0x52, 0xc1, 0xff, 0xa7, 0x0d, 0xc0, 0x19, 0xeb, 0x64, 0xfb, - 0xd3, 0xfc, 0xf7, 0x3a, 0xf4, 0x9e, 0x33, 0xca, 0x25, 0x65, 0x2a, 0x11, 0xdc, 0xf4, 0xde, 0x07, - 0x9b, 0x0b, 0xce, 0x50, 0x83, 0xcd, 0xc8, 0x1c, 0xc8, 0x15, 0xd8, 0xb2, 0xcc, 0xbb, 0x71, 0xdd, - 0xda, 0x6c, 0xa8, 0xbb, 0x98, 0xd4, 0x22, 0x03, 0x91, 0x21, 0x38, 0x89, 0x8e, 0xa0, 0x9b, 0xef, - 0x8c, 0x4e, 0x77, 0x70, 0x93, 0x6f, 0x52, 0x8b, 0xbe, 0xb0, 0x52, 0x60, 0xfa, 0xd7, 0xdb, 0xd8, - 0x17, 0x98, 0x08, 0xa5, 0xc0, 0x60, 0x65, 0x11, 0xc9, 0x1c, 0xb9, 0x4a, 0x54, 0xa1, 0x17, 0xf4, - 0x67, 0x11, 0x15, 0x45, 0x46, 0xd0, 0x96, 0x49, 0xcc, 0xa9, 0xca, 0x33, 0x74, 0x9d, 0x03, 0x92, - 0x6f, 0xec, 0xae, 0x05, 0x8e, 0xa9, 0x66, 0xe6, 0x68, 0xe4, 0xe6, 0x33, 0x00, 0x00, 0xff, 0xff, - 0xfe, 0x1a, 0xef, 0x84, 0xe2, 0x02, 0x00, 0x00, -} diff --git a/ledger/consumer/smartcontract/mod.go b/ledger/consumer/smartcontract/mod.go deleted file mode 100644 index 085ea1418..000000000 --- a/ledger/consumer/smartcontract/mod.go +++ /dev/null @@ -1,181 +0,0 @@ -package smartcontract - -import ( - "bytes" - - proto "github.com/golang/protobuf/proto" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/ledger/arc" - "go.dedis.ch/fabric/ledger/arc/common" - "go.dedis.ch/fabric/ledger/consumer" - "golang.org/x/xerrors" -) - -//go:generate protoc -I ./ --go_out=./ ./messages.proto - -// Contract is an interface that provides the primitives to execute a smart -// contract transaction and produce the resulting instance. -type Contract interface { - // Spawn is called to create a new instance. It returns the initial value of - // the new instance and its access rights control (arc) ID. - Spawn(ctx SpawnContext) (proto.Message, []byte, error) - - // Invoke is called to update an existing instance. - Invoke(ctx InvokeContext) (proto.Message, error) -} - -// Consumer is a consumer of smart contract transactions. -// -// - implements consumer.Consumer -type Consumer struct { - encoder encoding.ProtoMarshaler - contracts map[string]Contract - - AccessFactory arc.AccessControlFactory -} - -// NewConsumer returns a new instance of the smart contract consumer. -func NewConsumer() Consumer { - return Consumer{ - encoder: encoding.NewProtoEncoder(), - contracts: make(map[string]Contract), - AccessFactory: common.NewAccessControlFactory(), - } -} - -// Register registers an executor that can be triggered by a transaction with -// the contract ID sets to the provided name. -func (c Consumer) Register(name string, exec Contract) { - c.contracts[name] = exec -} - -// GetTransactionFactory implements consumer.Consumer. It returns the factory -// for smart contract transactions. -func (c Consumer) GetTransactionFactory() consumer.TransactionFactory { - return NewTransactionFactory(nil) -} - -// GetInstanceFactory implements consumer.Consumer. It returns the factory for -// smart contract instances. -func (c Consumer) GetInstanceFactory() consumer.InstanceFactory { - return instanceFactory{encoder: c.encoder} -} - -// Consume implements consumer.Consumer. It returns the instance produced from -// the execution of the transaction. -func (c Consumer) Consume(ctx consumer.Context) (consumer.Instance, error) { - tx, ok := ctx.GetTransaction().(transaction) - if !ok { - return nil, xerrors.Errorf("invalid tx type '%T'", ctx.GetTransaction()) - } - - switch action := tx.action.(type) { - case SpawnAction: - ctx := SpawnContext{Context: ctx, Action: action} - - return c.consumeSpawn(ctx) - case InvokeAction: - ctx := InvokeContext{Context: ctx, Action: action} - - return c.consumeInvoke(ctx) - case DeleteAction: - instance, err := ctx.Read(action.Key) - if err != nil { - return nil, xerrors.Errorf("couldn't read the instance: %v", err) - } - - ci := instance.(contractInstance) - ci.deleted = true - - return ci, nil - default: - return nil, xerrors.Errorf("invalid action type '%T'", action) - } -} - -func (c Consumer) consumeSpawn(ctx SpawnContext) (consumer.Instance, error) { - contractID := ctx.GetAction().ContractID - - exec := c.contracts[contractID] - if exec == nil { - return nil, xerrors.Errorf("unknown contract with id '%s'", contractID) - } - - value, arcid, err := exec.Spawn(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't execute spawn: %v", err) - } - - if !bytes.Equal(arcid, ctx.GetTransaction().GetID()) { - // If the instance is a new access control, it is left to the contract - // to insure the transaction is correct. - - rule := arc.Compile(ctx.GetAction().ContractID, "spawn") - - err = c.hasAccess(ctx, arcid, rule) - if err != nil { - return nil, xerrors.Errorf("no access: %v", err) - } - } - - instance := contractInstance{ - key: ctx.GetTransaction().GetID(), - accessControl: arcid, - contractID: contractID, - deleted: false, - value: value, - } - - return instance, nil -} - -func (c Consumer) consumeInvoke(ctx InvokeContext) (consumer.Instance, error) { - inst, err := ctx.Read(ctx.GetAction().Key) - if err != nil { - return nil, xerrors.Errorf("couldn't read the instance: %v", err) - } - - ci, ok := inst.(contractInstance) - if !ok { - return nil, xerrors.Errorf("invalid instance type '%T' != '%T'", inst, ci) - } - - exec := c.contracts[ci.GetContractID()] - if exec == nil { - return nil, xerrors.Errorf("unknown contract with id '%s'", ci.GetContractID()) - } - - rule := arc.Compile(ci.GetContractID(), "invoke") - - err = c.hasAccess(ctx, inst.GetArcID(), rule) - if err != nil { - return nil, xerrors.Errorf("no access: %v", err) - } - - ci.value, err = exec.Invoke(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't invoke: %v", err) - } - - return ci, nil -} - -func (c Consumer) hasAccess(ctx consumer.Context, key []byte, rule string) error { - instance, err := ctx.Read(key) - if err != nil { - return xerrors.Errorf("couldn't read instance: %v", err) - } - - access, err := c.AccessFactory.FromProto(instance.GetValue()) - if err != nil { - return xerrors.Errorf("couldn't decode access: %v", err) - } - - err = access.Match(rule, ctx.GetTransaction().GetIdentity()) - if err != nil { - return xerrors.Errorf("%v is refused to '%s' by %v: %v", - ctx.GetTransaction().GetIdentity(), rule, access, err) - } - - return nil -} diff --git a/ledger/consumer/smartcontract/mod_test.go b/ledger/consumer/smartcontract/mod_test.go deleted file mode 100644 index fbb5696bf..000000000 --- a/ledger/consumer/smartcontract/mod_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package smartcontract - -import ( - "testing" - - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/empty" - "github.com/stretchr/testify/require" - "go.dedis.ch/fabric/encoding" - internal "go.dedis.ch/fabric/internal/testing" - "go.dedis.ch/fabric/ledger/arc" - "go.dedis.ch/fabric/ledger/consumer" - "golang.org/x/xerrors" -) - -func TestMessages(t *testing.T) { - messages := []proto.Message{ - &InstanceProto{}, - &TransactionProto{}, - &Spawn{}, - &Invoke{}, - &Delete{}, - } - - for _, m := range messages { - internal.CoverProtoMessage(t, m) - } -} - -func TestConsumer_Register(t *testing.T) { - c := NewConsumer() - - c.Register("contract", fakeContract{}) - require.Len(t, c.contracts, 1) - - c.Register("another contract", fakeContract{}) - require.Len(t, c.contracts, 2) - - c.Register("contract", fakeContract{}) - require.Len(t, c.contracts, 2) -} - -func TestConsumer_GetTransactionFactory(t *testing.T) { - c := NewConsumer() - require.NotNil(t, c.GetTransactionFactory()) -} - -func TestConsumer_GetInstanceFactory(t *testing.T) { - c := NewConsumer() - require.NotNil(t, c.GetInstanceFactory()) -} - -func TestConsumer_Consume(t *testing.T) { - factory := &fakeAccessFactory{access: &fakeAccessControl{match: true}} - - c := NewConsumer() - c.AccessFactory = factory - c.Register("fake", fakeContract{}) - c.Register("bad", fakeContract{err: xerrors.New("oops")}) - - // 1. Consume a spawn transaction. - tx := transaction{ - hash: []byte{0xab}, - identity: fakeIdentity{}, - action: SpawnAction{ - ContractID: "fake", - }, - } - - out, err := c.Consume(newContext(tx, makeInstance())) - require.NoError(t, err) - require.Equal(t, tx.hash, out.GetKey()) - - tx.action = SpawnAction{ContractID: "abc"} - _, err = c.Consume(newContext(tx, nil)) - require.EqualError(t, err, "unknown contract with id 'abc'") - - tx.action = SpawnAction{ContractID: "bad"} - _, err = c.Consume(newContext(tx, nil)) - require.EqualError(t, err, "couldn't execute spawn: oops") - - // 2. Consume an invoke transaction. - c.encoder = encoding.NewProtoEncoder() - tx.action = InvokeAction{ - Key: []byte{0xab}, - Argument: &empty.Empty{}, - } - - instance := makeInstance() - instance.key = []byte{0xab} - ctx := newContext(tx, instance) - factory.access.calls = make([][]interface{}, 0) - out, err = c.Consume(ctx) - require.NoError(t, err) - require.Equal(t, []byte{0xab}, out.GetKey()) - require.Len(t, factory.access.calls, 1) - require.Equal(t, []arc.Identity{fakeIdentity{}}, factory.access.calls[0][0]) - require.Equal(t, arc.Compile("fake", "invoke"), factory.access.calls[0][1]) - - _, err = c.Consume(testContext{tx: tx, errRead: xerrors.New("oops")}) - require.EqualError(t, err, "couldn't read the instance: oops") - - instance.contractID = "unknown" - _, err = c.Consume(newContext(tx, instance)) - require.EqualError(t, err, "unknown contract with id 'unknown'") - - instance.contractID = "fake" - factory.err = xerrors.New("oops") - _, err = c.Consume(ctx) - require.EqualError(t, err, "no access: couldn't decode access: oops") - - factory.err = nil - factory.access.match = false - _, err = c.Consume(ctx) - require.EqualError(t, err, - "no access: fakePublicKey is refused to 'fake:invoke' by fakeAccessControl: not authorized") - - factory.access.match = true - instance.contractID = "bad" - _, err = c.Consume(newContext(tx, instance)) - require.EqualError(t, err, "couldn't invoke: oops") - - // 3. Consume a delete transaction. - tx.action = DeleteAction{ - Key: []byte{0xab}, - } - - out, err = c.Consume(newContext(tx, makeInstance())) - require.NoError(t, err) - require.True(t, out.(contractInstance).deleted) - - _, err = c.Consume(testContext{tx: tx, errRead: xerrors.New("oops")}) - require.EqualError(t, err, "couldn't read the instance: oops") - - // 4. Consume an invalid transaction. - _, err = c.Consume(newContext(fakeTx{}, nil)) - require.EqualError(t, err, "invalid tx type 'smartcontract.fakeTx'") - - tx.action = nil - _, err = c.Consume(newContext(tx, nil)) - require.EqualError(t, err, "invalid action type ''") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeInstance() contractInstance { - return contractInstance{ - value: &empty.Empty{}, - contractID: "fake", - deleted: false, - } -} - -type fakeContract struct { - Contract - err error -} - -func (c fakeContract) Spawn(ctx SpawnContext) (proto.Message, []byte, error) { - ctx.Read([]byte{0xab}) - return &empty.Empty{}, []byte{0xff}, c.err -} - -func (c fakeContract) Invoke(ctx InvokeContext) (proto.Message, error) { - ctx.Read([]byte{0xab}) - return &empty.Empty{}, c.err -} - -type fakeTx struct { - consumer.Transaction -} - -type fakeAccessControl struct { - arc.AccessControl - match bool - calls [][]interface{} -} - -func (ac *fakeAccessControl) Match(rule string, idents ...arc.Identity) error { - ac.calls = append(ac.calls, []interface{}{idents, rule}) - if ac.match { - return nil - } - return xerrors.New("not authorized") -} - -func (ac *fakeAccessControl) String() string { - return "fakeAccessControl" -} - -type testContext struct { - tx consumer.Transaction - instance ContractInstance - errRead error -} - -func newContext(tx consumer.Transaction, inst ContractInstance) testContext { - return testContext{ - tx: tx, - instance: inst, - } -} - -func (c testContext) GetTransaction() consumer.Transaction { - return c.tx -} - -func (c testContext) Read([]byte) (consumer.Instance, error) { - return c.instance, c.errRead -} - -type fakeAccessFactory struct { - arc.AccessControlFactory - access *fakeAccessControl - err error -} - -func (f *fakeAccessFactory) FromProto(proto.Message) (arc.AccessControl, error) { - return f.access, f.err -} diff --git a/ledger/consumer/smartcontract/tx.go b/ledger/consumer/smartcontract/tx.go deleted file mode 100644 index 9e57e78b5..000000000 --- a/ledger/consumer/smartcontract/tx.go +++ /dev/null @@ -1,463 +0,0 @@ -package smartcontract - -import ( - "encoding/binary" - fmt "fmt" - "hash" - - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/any" - "go.dedis.ch/fabric/crypto" - "go.dedis.ch/fabric/crypto/common" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/ledger/arc" - "go.dedis.ch/fabric/ledger/consumer" - "golang.org/x/xerrors" -) - -type action interface { - encoding.Packable - - hashTo(hash.Hash, encoding.ProtoMarshaler) error -} - -// transaction is an atomic execution. -// -// - implements ledger.transaction -type transaction struct { - hash []byte - nonce uint64 - action action - identity crypto.PublicKey - signature crypto.Signature -} - -// GetID implements ledger.Transaction. It returns the unique identifier of the -// transaction. -func (t transaction) GetID() []byte { - return t.hash[:] -} - -func (t transaction) GetIdentity() arc.Identity { - return t.identity -} - -func (t transaction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &TransactionProto{ - Nonce: t.nonce, - } - - var err error - pb.Identity, err = enc.PackAny(t.identity) - if err != nil { - return nil, xerrors.Errorf("couldn't pack identity: %v", err) - } - - pb.Signature, err = enc.PackAny(t.signature) - if err != nil { - return nil, xerrors.Errorf("couldn't pack signature: %v", err) - } - - actionpb, err := enc.Pack(t.action) - if err != nil { - return nil, xerrors.Errorf("couldn't pack action: %v", err) - } - - switch action := actionpb.(type) { - case *Spawn: - pb.Action = &TransactionProto_Spawn{Spawn: action} - case *Invoke: - pb.Action = &TransactionProto_Invoke{Invoke: action} - case *Delete: - pb.Action = &TransactionProto_Delete{Delete: action} - } - - return pb, nil -} - -func (t transaction) String() string { - return fmt.Sprintf("Transaction[%v]", t.identity) -} - -func (t transaction) computeHash(h hash.Hash, enc encoding.ProtoMarshaler) ([]byte, error) { - buffer := make([]byte, 8) - binary.LittleEndian.PutUint64(buffer[:], t.nonce) - - _, err := h.Write(buffer) - if err != nil { - return nil, xerrors.Errorf("couldn't write nonce: %v", err) - } - - buffer, err = t.identity.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal identity: %v", err) - } - - _, err = h.Write(buffer) - if err != nil { - return nil, xerrors.Errorf("couldn't write identity: %v", err) - } - - err = t.action.hashTo(h, enc) - if err != nil { - return nil, xerrors.Errorf("couldn't write action: %v", err) - } - - return h.Sum(nil), nil -} - -// SpawnAction is a transaction action that will create a new instance. -// -// - implements encoding.Packable -type SpawnAction struct { - ContractID string - Argument proto.Message -} - -// Pack implements encoding.Packable. It returns the protobuf message for a -// spawn transaction. -func (t SpawnAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &Spawn{ - ContractID: t.ContractID, - } - - if t.Argument != nil { - var err error - pb.Argument, err = enc.MarshalAny(t.Argument) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal the argument: %v", err) - } - } - - return pb, nil -} - -func (t SpawnAction) hashTo(h hash.Hash, enc encoding.ProtoMarshaler) error { - _, err := h.Write([]byte(t.ContractID)) - if err != nil { - return xerrors.Errorf("couldn't write contract ID: %v", err) - } - - if t.Argument != nil { - err = enc.MarshalStable(h, t.Argument) - if err != nil { - return xerrors.Errorf("couldn't write argument: %v", err) - } - } - - return nil -} - -// InvokeAction is a transaction action that will update an instance. -// -// - implements encoding.Packable -type InvokeAction struct { - Key []byte - Argument proto.Message -} - -// Pack implements encoding.Packable. It returns the protobuf message of the -// invoke transaction. -func (t InvokeAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - argany, err := enc.MarshalAny(t.Argument) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal the argument: %v", err) - } - - pb := &Invoke{ - Key: t.Key, - Argument: argany, - } - - return pb, nil -} - -func (t InvokeAction) hashTo(h hash.Hash, enc encoding.ProtoMarshaler) error { - _, err := h.Write(t.Key) - if err != nil { - return xerrors.Errorf("couldn't write key: %v", err) - } - - if t.Argument != nil { - err = enc.MarshalStable(h, t.Argument) - if err != nil { - return xerrors.Errorf("couldn't write argument: %v", err) - } - } - - return nil -} - -// DeleteAction is a transaction action that will tag an instance as deleted so -// that it will become immutable. -// -// implements encoding.Packable -type DeleteAction struct { - Key []byte -} - -// Pack implements encoding.Packable. It returns the protobuf message for the -// delete transaction. -func (t DeleteAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &Delete{ - Key: t.Key, - } - - return pb, nil -} - -func (t DeleteAction) hashTo(h hash.Hash, enc encoding.ProtoMarshaler) error { - _, err := h.Write(t.Key) - if err != nil { - return xerrors.Errorf("couldn't write key: %v", err) - } - - return nil -} - -// TransactionFactory is an implementation of a Byzcoin transaction factory. -// -// - implements ledger.TransactionFactory -type TransactionFactory struct { - signer crypto.Signer - hashFactory crypto.HashFactory - publicKeyFactory crypto.PublicKeyFactory - signatureFactory crypto.SignatureFactory - encoder encoding.ProtoMarshaler -} - -// NewTransactionFactory returns a new instance of the transaction factory. -// -// - implements ledger.TransactionFactory -func NewTransactionFactory(signer crypto.Signer) TransactionFactory { - return TransactionFactory{ - signer: signer, - hashFactory: crypto.NewSha256Factory(), - publicKeyFactory: common.NewPublicKeyFactory(), - signatureFactory: common.NewSignatureFactory(), - encoder: encoding.NewProtoEncoder(), - } -} - -// New returns a new transaction. -func (f TransactionFactory) New(action action) (consumer.Transaction, error) { - tx := transaction{ - nonce: 0, // TODO: - identity: f.signer.GetPublicKey(), - action: action, - } - - var err error - tx.hash, err = tx.computeHash(f.hashFactory.New(), f.encoder) - if err != nil { - return tx, xerrors.Errorf("couldn't compute hash: %v", err) - } - - tx.signature, err = f.signer.Sign(tx.hash) - if err != nil { - return tx, xerrors.Errorf("couldn't sign tx: %v", err) - } - - return tx, nil -} - -// FromProto implements ledger.TransactionFactory. It returns a new transaction -// built from the protobuf message. -func (f TransactionFactory) FromProto(pb proto.Message) (consumer.Transaction, error) { - var txProto *TransactionProto - - switch in := pb.(type) { - case *any.Any: - txProto = &TransactionProto{} - err := f.encoder.UnmarshalAny(in, txProto) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal input: %v", err) - } - case *TransactionProto: - txProto = in - default: - return nil, xerrors.Errorf("invalid transaction type '%T'", pb) - } - - tx := transaction{ - nonce: txProto.GetNonce(), - } - - spawn := txProto.GetSpawn() - if spawn != nil { - arg, err := f.encoder.UnmarshalDynamicAny(spawn.GetArgument()) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal argument: %v", err) - } - - tx.action = SpawnAction{ - ContractID: spawn.GetContractID(), - Argument: arg, - } - } - - invoke := txProto.GetInvoke() - if invoke != nil { - arg, err := f.encoder.UnmarshalDynamicAny(invoke.GetArgument()) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal argument: %v", err) - } - - tx.action = InvokeAction{ - Key: invoke.GetKey(), - Argument: arg, - } - } - - delete := txProto.GetDelete() - if delete != nil { - tx.action = DeleteAction{ - Key: delete.GetKey(), - } - } - - err := f.fillIdentity(&tx, txProto) - if err != nil { - return nil, err - } - - return tx, nil -} - -func (f TransactionFactory) fillIdentity(tx *transaction, pb *TransactionProto) error { - var err error - tx.identity, err = f.publicKeyFactory.FromProto(pb.GetIdentity()) - if err != nil { - return xerrors.Errorf("couldn't decode public key: %v", err) - } - - tx.signature, err = f.signatureFactory.FromProto(pb.GetSignature()) - if err != nil { - return xerrors.Errorf("couldn't decode signature: %v", err) - } - - tx.hash, err = tx.computeHash(f.hashFactory.New(), f.encoder) - if err != nil { - return xerrors.Errorf("couldn't compute hash: %v", err) - } - - err = tx.identity.Verify(tx.hash, tx.signature) - if err != nil { - return xerrors.Errorf("signature does not match tx: %v", err) - } - - return nil -} - -// ContractInstance is a specialization of the consumer instance to include -// smart contract details. -type ContractInstance interface { - consumer.Instance - - GetContractID() string - Deleted() bool -} - -// contractInstance is the result of a smart contract transaction execution. The -// key is defined by the hash of the spawn transaction that created the -// instance. It is immutable exactly like the the contract identifier. -// -// - implements consumer.Instance -// - implements smartcontract.ContractInstance -// - implements encoding.Packable -type contractInstance struct { - key []byte - accessControl []byte - contractID string - deleted bool - value proto.Message -} - -// GetKey implements consumer.Instance. It returns the key of the instance. -func (i contractInstance) GetKey() []byte { - return i.key -} - -// GetArcID implements consumer.Instance. It returns the access control -// identifier for this instance. -func (i contractInstance) GetArcID() []byte { - return i.accessControl -} - -// GetContractID implements smartcontract.ContractInstance. It returns the -// contract identifier. -func (i contractInstance) GetContractID() string { - return i.contractID -} - -// GetValue implements consumer.Instance. It returns the value produced by the -// transaction execution. -func (i contractInstance) GetValue() proto.Message { - return i.value -} - -// Deleted implements smartcontract.ContractInstance. It returns true if the -// instance has been permanently deleted. -func (i contractInstance) Deleted() bool { - return i.deleted -} - -// Pack implements encoding.Packable. It returns the protobuf message for this -// instance. -func (i contractInstance) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &InstanceProto{ - Key: i.key, - ContractID: i.contractID, - Deleted: i.deleted, - AccessControl: i.accessControl, - } - - var err error - pb.Value, err = enc.MarshalAny(i.value) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal the value: %v", err) - } - - return pb, nil -} - -// instanceFactory is the implementation of the consumer.InstanceFactory for -// smart contract instances. -// -// - implements consumer.InstanceFactory -type instanceFactory struct { - encoder encoding.ProtoMarshaler -} - -// FromProto implements consumer.InstanceFactory. It returns the instance from -// the protobuf message if it applies, otherwise an error. -func (f instanceFactory) FromProto(pb proto.Message) (consumer.Instance, error) { - var instancepb *InstanceProto - switch i := pb.(type) { - case *any.Any: - instancepb = &InstanceProto{} - err := f.encoder.UnmarshalAny(i, instancepb) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal: %v", err) - } - case *InstanceProto: - instancepb = i - default: - return nil, xerrors.Errorf("invalid instance type '%T'", pb) - } - - value, err := f.encoder.UnmarshalDynamicAny(instancepb.GetValue()) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal the value: %v", err) - } - - instance := contractInstance{ - key: instancepb.GetKey(), - accessControl: instancepb.GetAccessControl(), - contractID: instancepb.GetContractID(), - deleted: instancepb.GetDeleted(), - value: value, - } - - return instance, nil -} diff --git a/ledger/consumer/smartcontract/tx_test.go b/ledger/consumer/smartcontract/tx_test.go deleted file mode 100644 index 7c7672b0b..000000000 --- a/ledger/consumer/smartcontract/tx_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package smartcontract - -import ( - "bytes" - "hash" - "testing" - "testing/quick" - - proto "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes" - "github.com/golang/protobuf/ptypes/empty" - "github.com/golang/protobuf/ptypes/wrappers" - "github.com/stretchr/testify/require" - "go.dedis.ch/fabric/crypto" - "go.dedis.ch/fabric/crypto/bls" - "go.dedis.ch/fabric/encoding" - "go.dedis.ch/fabric/internal/testing/fake" - "go.dedis.ch/fabric/ledger/inventory" - "golang.org/x/xerrors" -) - -func TestTransaction_GetID(t *testing.T) { - f := func(buffer []byte) bool { - tx := transaction{hash: buffer} - - return bytes.Equal(buffer[:], tx.GetID()) - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestTransaction_Pack(t *testing.T) { - tx := transaction{ - identity: fakeIdentity{}, - signature: fake.Signature{}, - action: SpawnAction{}, - } - - txpb, err := tx.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - require.NotNil(t, txpb.(*TransactionProto).GetSpawn()) - - _, err = tx.Pack(fake.BadPackAnyEncoder{}) - require.EqualError(t, err, "couldn't pack identity: fake error") - - _, err = tx.Pack(fake.BadPackEncoder{}) - require.EqualError(t, err, "couldn't pack action: fake error") -} - -func TestTransaction_ComputeHash(t *testing.T) { - tx := transaction{ - nonce: 1, - identity: fakeIdentity{}, - action: SpawnAction{}, - } - - h := crypto.NewSha256Factory().New() - - hash, err := tx.computeHash(h, encoding.NewProtoEncoder()) - require.NoError(t, err) - require.Len(t, hash, 32) - - _, err = tx.computeHash(fake.NewBadHash(), nil) - require.EqualError(t, err, "couldn't write nonce: fake error") - - tx.identity = fakeIdentity{err: xerrors.New("oops")} - _, err = tx.computeHash(&fake.Hash{}, nil) - require.EqualError(t, err, "couldn't marshal identity: oops") - - tx.identity = fakeIdentity{} - tx.action = badAction{} - _, err = tx.computeHash(&fake.Hash{}, nil) - require.EqualError(t, err, "couldn't write action: oops") -} - -func TestSpawnAction_Pack(t *testing.T) { - spawn := SpawnAction{ - ContractID: "abc", - Argument: &wrappers.StringValue{Value: "abc"}, - } - - spawnpb, err := spawn.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - require.IsType(t, (*Spawn)(nil), spawnpb) - - _, err = spawn.Pack(fake.BadMarshalAnyEncoder{}) - require.EqualError(t, err, "couldn't marshal the argument: fake error") -} - -func TestSpawnAction_HashTo(t *testing.T) { - spawn := SpawnAction{} - - err := spawn.hashTo(&fake.Hash{}, encoding.NewProtoEncoder()) - require.NoError(t, err) - - spawn.Argument = &wrappers.BoolValue{Value: true} - err = spawn.hashTo(&fake.Hash{}, encoding.NewProtoEncoder()) - require.NoError(t, err) - - err = spawn.hashTo(fake.NewBadHash(), nil) - require.EqualError(t, err, "couldn't write contract ID: fake error") - - err = spawn.hashTo(&fake.Hash{}, fake.BadMarshalStableEncoder{}) - require.EqualError(t, err, "couldn't write argument: fake error") -} - -func TestInvokeAction_Pack(t *testing.T) { - invoke := InvokeAction{ - Key: []byte{0xab}, - Argument: &wrappers.StringValue{Value: "abc"}, - } - - invokepb, err := invoke.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - require.IsType(t, (*Invoke)(nil), invokepb) - - _, err = invoke.Pack(fake.BadMarshalAnyEncoder{}) - require.EqualError(t, err, "couldn't marshal the argument: fake error") -} - -func TestInvokeAction_HashTo(t *testing.T) { - invoke := InvokeAction{} - - err := invoke.hashTo(&fake.Hash{}, encoding.NewProtoEncoder()) - require.NoError(t, err) - - invoke.Argument = &wrappers.BoolValue{Value: true} - err = invoke.hashTo(&fake.Hash{}, encoding.NewProtoEncoder()) - require.NoError(t, err) - - err = invoke.hashTo(fake.NewBadHash(), nil) - require.EqualError(t, err, "couldn't write key: fake error") - - err = invoke.hashTo(&fake.Hash{}, fake.BadMarshalStableEncoder{}) - require.EqualError(t, err, "couldn't write argument: fake error") -} - -func TestDeleteAction_Pack(t *testing.T) { - delete := DeleteAction{ - Key: []byte{0xab}, - } - - deletepb, err := delete.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - require.IsType(t, (*Delete)(nil), deletepb) -} - -func TestDeleteAction_HashTo(t *testing.T) { - delete := DeleteAction{} - err := delete.hashTo(&fake.Hash{}, nil) - require.NoError(t, err) - - err = delete.hashTo(fake.NewBadHash(), nil) - require.EqualError(t, err, "couldn't write key: fake error") -} - -func TestTransactionFactory_New(t *testing.T) { - factory := NewTransactionFactory(bls.NewSigner()) - - spawn, err := factory.New(SpawnAction{}) - require.NoError(t, err) - require.IsType(t, SpawnAction{}, spawn.(transaction).action) - - invoke, err := factory.New(InvokeAction{}) - require.NoError(t, err) - require.IsType(t, InvokeAction{}, invoke.(transaction).action) - - delete, err := factory.New(DeleteAction{}) - require.NoError(t, err) - require.IsType(t, DeleteAction{}, delete.(transaction).action) - - factory.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - _, err = factory.New(SpawnAction{}) - require.EqualError(t, err, "couldn't compute hash: couldn't write nonce: fake error") - - factory.hashFactory = fake.NewHashFactory(&fake.Hash{}) - factory.signer = fake.NewBadSigner() - _, err = factory.New(SpawnAction{}) - require.EqualError(t, err, "couldn't sign tx: fake error") -} - -func TestTransactionFactory_FromProto(t *testing.T) { - factory := NewTransactionFactory(nil) - factory.publicKeyFactory = fake.PublicKeyFactory{} - factory.signatureFactory = fake.SignatureFactory{} - - tx := transaction{ - identity: fakeIdentity{}, - signature: fake.Signature{}, - } - - // 1. Spawn transaction - tx.action = SpawnAction{ - ContractID: "abc", - Argument: &wrappers.BoolValue{Value: true}, - } - txpb, err := tx.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - - _, err = factory.FromProto(txpb) - require.NoError(t, err) - - factory.encoder = fake.BadUnmarshalDynEncoder{} - _, err = factory.FromProto(txpb) - require.EqualError(t, err, "couldn't unmarshal argument: fake error") - - factory.encoder = encoding.NewProtoEncoder() - - // 2. Invoke transaction - tx.action = InvokeAction{ - Key: []byte{0xab}, - Argument: &wrappers.BoolValue{Value: true}, - } - txpb, err = tx.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - - _, err = factory.FromProto(txpb) - require.NoError(t, err) - - factory.encoder = fake.BadUnmarshalDynEncoder{} - _, err = factory.FromProto(txpb) - require.EqualError(t, err, "couldn't unmarshal argument: fake error") - - factory.encoder = encoding.NewProtoEncoder() - - // 3. Delete transaction - tx.action = DeleteAction{Key: []byte{0xab}} - txpb, err = tx.Pack(encoding.NewProtoEncoder()) - require.NoError(t, err) - deleteany, err := ptypes.MarshalAny(txpb) - require.NoError(t, err) - - _, err = factory.FromProto(txpb) - require.NoError(t, err) - - _, err = factory.FromProto(deleteany) - require.NoError(t, err) - - factory.encoder = fake.BadUnmarshalAnyEncoder{} - _, err = factory.FromProto(deleteany) - require.EqualError(t, err, "couldn't unmarshal input: fake error") - - // 4. Common - _, err = factory.FromProto(nil) - require.EqualError(t, err, "invalid transaction type ''") - - factory.publicKeyFactory = fake.NewBadPublicKeyFactory() - _, err = factory.FromProto(txpb) - require.EqualError(t, err, "couldn't decode public key: fake error") - - factory.publicKeyFactory = fake.NewPublicKeyFactory(fake.NewInvalidPublicKey()) - _, err = factory.FromProto(txpb) - require.EqualError(t, err, "signature does not match tx: fake error") - - factory.publicKeyFactory = fake.PublicKeyFactory{} - factory.signatureFactory = fake.NewBadSignatureFactory() - _, err = factory.FromProto(txpb) - require.EqualError(t, err, "couldn't decode signature: fake error") - - factory.signatureFactory = fake.SignatureFactory{} - factory.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - _, err = factory.FromProto(txpb) - require.EqualError(t, err, "couldn't compute hash: couldn't write nonce: fake error") -} - -func TestContractInstance_GetKey(t *testing.T) { - f := func(key []byte) bool { - ci := contractInstance{key: key} - return bytes.Equal(key, ci.GetKey()) - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestContractInstance_GetContractID(t *testing.T) { - f := func(id string) bool { - ci := contractInstance{contractID: id} - return ci.GetContractID() == id - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestContractInstance_GetValue(t *testing.T) { - f := func(value string) bool { - ci := contractInstance{value: &wrappers.StringValue{Value: value}} - return proto.Equal(ci.GetValue(), &wrappers.StringValue{Value: value}) - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestContractInstance_Deleted(t *testing.T) { - ci := contractInstance{deleted: true} - require.True(t, ci.Deleted()) - - ci.deleted = false - require.False(t, ci.Deleted()) -} - -func TestContractInstance_Pack(t *testing.T) { - f := func(key []byte, id, value string, deleted bool) bool { - ci := contractInstance{ - key: key, - contractID: id, - value: &wrappers.StringValue{Value: value}, - deleted: deleted, - } - - enc := encoding.NewProtoEncoder() - - cipb, err := ci.Pack(enc) - require.NoError(t, err) - instancepb := cipb.(*InstanceProto) - require.Equal(t, ci.key, instancepb.GetKey()) - require.Equal(t, ci.contractID, instancepb.GetContractID()) - require.Equal(t, ci.deleted, instancepb.GetDeleted()) - - msg, err := enc.UnmarshalDynamicAny(instancepb.GetValue()) - require.NoError(t, err) - require.True(t, proto.Equal(ci.value, msg)) - - _, err = ci.Pack(fake.BadMarshalAnyEncoder{}) - require.EqualError(t, err, "couldn't marshal the value: fake error") - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestInstanceFactory_FromProto(t *testing.T) { - factory := instanceFactory{encoder: encoding.NewProtoEncoder()} - - pb, err := makeInstance().Pack(factory.encoder) - require.NoError(t, err) - instancepb := pb.(*InstanceProto) - instanceany, err := ptypes.MarshalAny(instancepb) - require.NoError(t, err) - - instance, err := factory.FromProto(instancepb) - require.NoError(t, err) - require.IsType(t, contractInstance{}, instance) - ci := instance.(contractInstance) - require.Equal(t, instancepb.GetKey(), ci.key) - require.Equal(t, instancepb.GetContractID(), ci.contractID) - require.Equal(t, instancepb.GetDeleted(), ci.deleted) - - instance, err = factory.FromProto(instanceany) - require.NoError(t, err) - require.Equal(t, instancepb.GetKey(), instance.GetKey()) - - factory.encoder = fake.BadUnmarshalAnyEncoder{} - _, err = factory.FromProto(instanceany) - require.EqualError(t, err, "couldn't unmarshal: fake error") - - factory.encoder = fake.BadUnmarshalDynEncoder{} - _, err = factory.FromProto(instancepb) - require.EqualError(t, err, "couldn't unmarshal the value: fake error") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeIdentity struct { - crypto.PublicKey - err error - errVerify error -} - -func (ident fakeIdentity) Verify([]byte, crypto.Signature) error { - return ident.errVerify -} - -func (ident fakeIdentity) MarshalBinary() ([]byte, error) { - return []byte{0xff}, ident.err -} - -func (ident fakeIdentity) Pack(encoding.ProtoMarshaler) (proto.Message, error) { - return &empty.Empty{}, nil -} - -func (ident fakeIdentity) String() string { - return "fakePublicKey" -} - -type fakePage struct { - inventory.Page - instance proto.Message - err error -} - -func (p fakePage) Read(key []byte) (proto.Message, error) { - return p.instance, p.err -} - -type badAction struct { - action -} - -func (a badAction) hashTo(hash.Hash, encoding.ProtoMarshaler) error { - return xerrors.New("oops") -} diff --git a/ledger/mod.go b/ledger/mod.go index c99e966a7..bd3a8e995 100644 --- a/ledger/mod.go +++ b/ledger/mod.go @@ -3,7 +3,8 @@ package ledger import ( "context" - "go.dedis.ch/fabric/ledger/consumer" + "github.com/golang/protobuf/proto" + "go.dedis.ch/fabric/ledger/transactions" "go.dedis.ch/fabric/mino" ) @@ -21,7 +22,7 @@ type Actor interface { // AddTransaction spreads the transaction so that it will be included in the // next blocks. - AddTransaction(tx consumer.Transaction) error + AddTransaction(tx transactions.ClientTransaction) error // Close stops the ledger and cleans the states. Close() error @@ -37,10 +38,8 @@ type TransactionResult interface { type Ledger interface { Listen() (Actor, error) - // GetInstance returns the instance of the key if it exists, otherwise an - // error. - // TODO: verifiable instance. - GetInstance(key []byte) (consumer.Instance, error) + // TODO: value + proof it exists in the inventory + GetValue(key []byte) (proto.Message, error) // Watch populates the channel with new incoming transaction results. Watch(ctx context.Context) <-chan TransactionResult diff --git a/ledger/transactions/basic/messages.pb.go b/ledger/transactions/basic/messages.pb.go new file mode 100644 index 000000000..768e77d39 --- /dev/null +++ b/ledger/transactions/basic/messages.pb.go @@ -0,0 +1,108 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: messages.proto + +package basic + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type TransactionProto struct { + Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"` + Identity *any.Any `protobuf:"bytes,2,opt,name=identity,proto3" json:"identity,omitempty"` + Signature *any.Any `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + Action *any.Any `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransactionProto) Reset() { *m = TransactionProto{} } +func (m *TransactionProto) String() string { return proto.CompactTextString(m) } +func (*TransactionProto) ProtoMessage() {} +func (*TransactionProto) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{0} +} + +func (m *TransactionProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransactionProto.Unmarshal(m, b) +} +func (m *TransactionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransactionProto.Marshal(b, m, deterministic) +} +func (m *TransactionProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransactionProto.Merge(m, src) +} +func (m *TransactionProto) XXX_Size() int { + return xxx_messageInfo_TransactionProto.Size(m) +} +func (m *TransactionProto) XXX_DiscardUnknown() { + xxx_messageInfo_TransactionProto.DiscardUnknown(m) +} + +var xxx_messageInfo_TransactionProto proto.InternalMessageInfo + +func (m *TransactionProto) GetNonce() uint64 { + if m != nil { + return m.Nonce + } + return 0 +} + +func (m *TransactionProto) GetIdentity() *any.Any { + if m != nil { + return m.Identity + } + return nil +} + +func (m *TransactionProto) GetSignature() *any.Any { + if m != nil { + return m.Signature + } + return nil +} + +func (m *TransactionProto) GetAction() *any.Any { + if m != nil { + return m.Action + } + return nil +} + +func init() { + proto.RegisterType((*TransactionProto)(nil), "basic.TransactionProto") +} + +func init() { + proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) +} + +var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ + // 173 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, + 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4d, 0x4a, 0x2c, 0xce, + 0x4c, 0x96, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x0b, 0x26, 0x95, 0xa6, 0xe9, + 0x27, 0xe6, 0x55, 0x42, 0x54, 0x28, 0xed, 0x61, 0xe4, 0x12, 0x08, 0x29, 0x4a, 0xcc, 0x2b, 0x4e, + 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0x0b, 0x00, 0x6b, 0x13, 0xe1, 0x62, 0xcd, 0xcb, 0xcf, 0x4b, 0x4e, + 0x95, 0x60, 0x54, 0x60, 0xd4, 0x60, 0x09, 0x82, 0x70, 0x84, 0x0c, 0xb8, 0x38, 0x32, 0x53, 0x52, + 0xf3, 0x4a, 0x32, 0x4b, 0x2a, 0x25, 0x98, 0x14, 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0x06, + 0xeb, 0xc1, 0x0c, 0xd6, 0x73, 0xcc, 0xab, 0x0c, 0x82, 0xab, 0x12, 0x32, 0xe2, 0xe2, 0x2c, 0xce, + 0x4c, 0xcf, 0x4b, 0x2c, 0x29, 0x2d, 0x4a, 0x95, 0x60, 0xc6, 0xa3, 0x05, 0xa1, 0x4c, 0x48, 0x87, + 0x8b, 0x0d, 0xe2, 0x14, 0x09, 0x16, 0x3c, 0x1a, 0xa0, 0x6a, 0x92, 0xd8, 0xc0, 0xa2, 0xc6, 0x80, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x15, 0x12, 0x03, 0x94, 0xf9, 0x00, 0x00, 0x00, +} diff --git a/ledger/transactions/basic/messages.proto b/ledger/transactions/basic/messages.proto new file mode 100644 index 000000000..8885e61d4 --- /dev/null +++ b/ledger/transactions/basic/messages.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package basic; + +import "google/protobuf/any.proto"; + +message TransactionProto { + uint64 nonce = 1; + google.protobuf.Any identity = 2; + google.protobuf.Any signature = 3; + google.protobuf.Any action = 4; +} diff --git a/ledger/transactions/basic/mod.go b/ledger/transactions/basic/mod.go new file mode 100644 index 000000000..e188f026b --- /dev/null +++ b/ledger/transactions/basic/mod.go @@ -0,0 +1,270 @@ +// Package basic implements a kind of transaction that includes a signature and +// a nonce so that it can prevent replay attacks. Access control can also be +// enforced from the identity of the transaction. +// +// The action defines how the transaction will be consumed and it follows the +// same separation logic with a client and a server side. The client only +// creates the action with its arguments and the server will decorate it to +// consume it. +package basic + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "go.dedis.ch/fabric/crypto" + "go.dedis.ch/fabric/crypto/common" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions" + "golang.org/x/xerrors" +) + +// ClientAction is used to create a transaction. +type ClientAction interface { + encoding.Packable + io.WriterTo +} + +// Context is the context provided to a server transaction when consumed. +type Context interface { + GetID() []byte + GetIdentity() arc.Identity +} + +// ServerAction provides the primitives to consume a specialization of a +// transaction. +type ServerAction interface { + ClientAction + + Consume(Context, inventory.WritablePage) error +} + +// ActionFactory provide the primitives to instantiate an action from its +// protobuf message. +type ActionFactory interface { + FromProto(proto.Message) (ServerAction, error) +} + +//go:generate protoc -I ./ --go_out=./ ./messages.proto + +// transaction is an atomic execution. +// +// - implements ledger.transaction +type transaction struct { + hash []byte + nonce uint64 + identity crypto.PublicKey + signature crypto.Signature + action ClientAction +} + +// GetID implements ledger.Transaction. It returns the unique identifier of the +// transaction. +func (t transaction) GetID() []byte { + return t.hash[:] +} + +func (t transaction) GetIdentity() arc.Identity { + return t.identity +} + +func (t transaction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { + pb := &TransactionProto{ + Nonce: t.nonce, + } + + var err error + pb.Identity, err = enc.PackAny(t.identity) + if err != nil { + return nil, xerrors.Errorf("couldn't pack identity: %v", err) + } + + pb.Signature, err = enc.PackAny(t.signature) + if err != nil { + return nil, xerrors.Errorf("couldn't pack signature: %v", err) + } + + pb.Action, err = enc.PackAny(t.action) + if err != nil { + return nil, xerrors.Errorf("couldn't pack action: %v", err) + } + + return pb, nil +} + +func (t transaction) String() string { + return fmt.Sprintf("Transaction[%v]", t.identity) +} + +func (t transaction) WriteTo(w io.Writer) (int64, error) { + buffer := make([]byte, 8) + binary.LittleEndian.PutUint64(buffer[:], t.nonce) + + sum := int64(0) + + n, err := w.Write(buffer) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write nonce: %v", err) + } + + buffer, err = t.identity.MarshalBinary() + if err != nil { + return sum, xerrors.Errorf("couldn't marshal identity: %v", err) + } + + n, err = w.Write(buffer) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write identity: %v", err) + } + + k, err := t.action.WriteTo(w) + sum += k + if err != nil { + return sum, xerrors.Errorf("couldn't write action: %v", err) + } + + return sum, nil +} + +type serverTransaction struct { + transaction +} + +func (t serverTransaction) Consume(page inventory.WritablePage) error { + // TODO: consume nonce + + action, ok := t.action.(ServerAction) + if !ok { + return xerrors.Errorf("action must implement '%T'", action) + } + + err := action.Consume(t, page) + if err != nil { + return err + } + + return nil +} + +// TransactionFactory is an implementation of a Byzcoin transaction factory. +// +// - implements ledger.TransactionFactory +type TransactionFactory struct { + signer crypto.Signer + hashFactory crypto.HashFactory + publicKeyFactory crypto.PublicKeyFactory + signatureFactory crypto.SignatureFactory + actionFactory ActionFactory + encoder encoding.ProtoMarshaler +} + +// NewTransactionFactory returns a new instance of the transaction factory. +// +// - implements ledger.TransactionFactory +func NewTransactionFactory(signer crypto.Signer, f ActionFactory) TransactionFactory { + return TransactionFactory{ + signer: signer, + hashFactory: crypto.NewSha256Factory(), + publicKeyFactory: common.NewPublicKeyFactory(), + signatureFactory: common.NewSignatureFactory(), + actionFactory: f, + encoder: encoding.NewProtoEncoder(), + } +} + +// New returns a new transaction. +func (f TransactionFactory) New(action ClientAction) (transactions.ClientTransaction, error) { + tx := transaction{ + nonce: 0, // TODO: monotonic nonce + identity: f.signer.GetPublicKey(), + action: action, + } + + h := f.hashFactory.New() + _, err := tx.WriteTo(h) + if err != nil { + return tx, xerrors.Errorf("couldn't compute hash: %v", err) + } + + tx.hash = h.Sum(nil) + + tx.signature, err = f.signer.Sign(tx.hash) + if err != nil { + return tx, xerrors.Errorf("couldn't sign tx: %v", err) + } + + return tx, nil +} + +// FromProto implements ledger.TransactionFactory. It returns a new transaction +// built from the protobuf message. +func (f TransactionFactory) FromProto(in proto.Message) (transactions.ServerTransaction, error) { + var pb *TransactionProto + + switch msg := in.(type) { + case *any.Any: + pb = &TransactionProto{} + err := f.encoder.UnmarshalAny(msg, pb) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal input: %v", err) + } + case *TransactionProto: + pb = msg + default: + return nil, xerrors.Errorf("invalid transaction type '%T'", in) + } + + tx := serverTransaction{ + transaction: transaction{ + nonce: pb.GetNonce(), + }, + } + + var err error + tx.action, err = f.actionFactory.FromProto(pb.GetAction()) + if err != nil { + return nil, xerrors.Errorf("couldn't decode action: %v", err) + } + + err = f.fillIdentity(&tx, pb) + if err != nil { + return nil, err + } + + return tx, nil +} + +func (f TransactionFactory) fillIdentity(tx *serverTransaction, pb *TransactionProto) error { + var err error + tx.identity, err = f.publicKeyFactory.FromProto(pb.GetIdentity()) + if err != nil { + return xerrors.Errorf("couldn't decode public key: %v", err) + } + + tx.signature, err = f.signatureFactory.FromProto(pb.GetSignature()) + if err != nil { + return xerrors.Errorf("couldn't decode signature: %v", err) + } + + h := f.hashFactory.New() + _, err = tx.WriteTo(h) + if err != nil { + return xerrors.Errorf("couldn't compute hash: %v", err) + } + + tx.hash = h.Sum(nil) + + err = tx.identity.Verify(tx.hash, tx.signature) + if err != nil { + return xerrors.Errorf("signature does not match tx: %v", err) + } + + return nil +} diff --git a/ledger/transactions/basic/mod_test.go b/ledger/transactions/basic/mod_test.go new file mode 100644 index 000000000..86f37956c --- /dev/null +++ b/ledger/transactions/basic/mod_test.go @@ -0,0 +1,168 @@ +package basic + +import ( + "bytes" + fmt "fmt" + "io" + "testing" + "testing/quick" + + proto "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/empty" + "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/crypto/bls" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/internal/testing/fake" + "go.dedis.ch/fabric/ledger/inventory" + "golang.org/x/xerrors" +) + +func TestTransaction_GetID(t *testing.T) { + f := func(buffer []byte) bool { + tx := transaction{hash: buffer} + + return bytes.Equal(buffer[:], tx.GetID()) + } + + err := quick.Check(f, nil) + require.NoError(t, err) +} + +func TestTransaction_Pack(t *testing.T) { + tx := transaction{ + identity: fake.PublicKey{}, + signature: fake.Signature{}, + action: fakeClientAction{}, + } + + txpb, err := tx.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.NotNil(t, txpb.(*TransactionProto).GetAction()) + + _, err = tx.Pack(fake.BadPackAnyEncoder{}) + require.EqualError(t, err, "couldn't pack identity: fake error") +} + +func TestTransaction_WriteTo(t *testing.T) { + tx := transaction{ + nonce: 1, + identity: fake.PublicKey{}, + action: fakeClientAction{}, + } + + w := new(bytes.Buffer) + + sum, err := tx.WriteTo(w) + require.NoError(t, err) + require.Equal(t, int64(10), sum) + require.Equal(t, "0100000000000000dfcc", fmt.Sprintf("%x", w.Bytes())) + + _, err = tx.WriteTo(fake.NewBadHash()) + require.EqualError(t, err, "couldn't write nonce: fake error") + + tx.identity = fake.NewBadPublicKey() + _, err = tx.WriteTo(&fake.Hash{}) + require.EqualError(t, err, "couldn't marshal identity: fake error") + + tx.identity = fake.PublicKey{} + tx.action = fakeClientAction{err: xerrors.New("oops")} + _, err = tx.WriteTo(&fake.Hash{}) + require.EqualError(t, err, "couldn't write action: oops") +} + +func TestTransactionFactory_New(t *testing.T) { + factory := NewTransactionFactory(bls.NewSigner(), nil) + + clientTx, err := factory.New(fakeClientAction{}) + require.NoError(t, err) + tx := clientTx.(transaction) + require.NotNil(t, tx.action) + require.NotNil(t, tx.signature) + + factory.hashFactory = fake.NewHashFactory(fake.NewBadHash()) + _, err = factory.New(fakeClientAction{}) + require.EqualError(t, err, "couldn't compute hash: couldn't write nonce: fake error") + + factory.hashFactory = fake.NewHashFactory(&fake.Hash{}) + factory.signer = fake.NewBadSigner() + _, err = factory.New(fakeClientAction{}) + require.EqualError(t, err, "couldn't sign tx: fake error") +} + +func TestTransactionFactory_FromProto(t *testing.T) { + factory := NewTransactionFactory(nil, fakeActionFactory{}) + factory.publicKeyFactory = fake.PublicKeyFactory{} + factory.signatureFactory = fake.SignatureFactory{} + + tx := transaction{ + identity: fake.PublicKey{}, + signature: fake.Signature{}, + action: fakeSrvAction{}, + } + + txpb, err := tx.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + _, err = factory.FromProto(txpb) + require.NoError(t, err) + + txany, err := ptypes.MarshalAny(txpb) + require.NoError(t, err) + _, err = factory.FromProto(txany) + require.NoError(t, err) + + _, err = factory.FromProto(nil) + require.EqualError(t, err, "invalid transaction type ''") + + factory.encoder = fake.BadUnmarshalAnyEncoder{} + _, err = factory.FromProto(txany) + require.EqualError(t, err, "couldn't unmarshal input: fake error") + + factory.publicKeyFactory = fake.NewBadPublicKeyFactory() + _, err = factory.FromProto(txpb) + require.EqualError(t, err, "couldn't decode public key: fake error") + + factory.publicKeyFactory = fake.NewPublicKeyFactory(fake.NewInvalidPublicKey()) + _, err = factory.FromProto(txpb) + require.EqualError(t, err, "signature does not match tx: fake error") + + factory.publicKeyFactory = fake.PublicKeyFactory{} + factory.signatureFactory = fake.NewBadSignatureFactory() + _, err = factory.FromProto(txpb) + require.EqualError(t, err, "couldn't decode signature: fake error") + + factory.signatureFactory = fake.SignatureFactory{} + factory.hashFactory = fake.NewHashFactory(fake.NewBadHash()) + _, err = factory.FromProto(txpb) + require.EqualError(t, err, "couldn't compute hash: couldn't write nonce: fake error") +} + +// ----------------------------------------------------------------------------- +// Utility functions + +type fakeClientAction struct { + err error +} + +func (a fakeClientAction) WriteTo(w io.Writer) (int64, error) { + w.Write([]byte{0xcc}) + return 1, a.err +} + +func (a fakeClientAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &empty.Empty{}, nil +} + +type fakeSrvAction struct { + fakeClientAction +} + +func (a fakeSrvAction) Consume(Context, inventory.WritablePage) error { + return nil +} + +type fakeActionFactory struct{} + +func (f fakeActionFactory) FromProto(proto.Message) (ServerAction, error) { + return fakeSrvAction{}, nil +} diff --git a/ledger/transactions/mod.go b/ledger/transactions/mod.go new file mode 100644 index 000000000..53b9412df --- /dev/null +++ b/ledger/transactions/mod.go @@ -0,0 +1,31 @@ +package transactions + +import ( + "github.com/golang/protobuf/proto" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/inventory" +) + +// ClientTransaction is a transaction created by a client that will be sent to +// the network. +type ClientTransaction interface { + encoding.Packable + + // GetID returns a unique identifier for the transaction. + GetID() []byte +} + +// ServerTransaction is an extension of the client transaction that will be +// consumed by the server. +type ServerTransaction interface { + ClientTransaction + + Consume(inventory.WritablePage) error +} + +// TransactionFactory is a factory to create new transactions or decode from +// network messages. +type TransactionFactory interface { + // FromProto returns the transaction from the protobuf message. + FromProto(pb proto.Message) (ServerTransaction, error) +} From 06566411666cc21add3b4c8cab40f60268e6fa15 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Thu, 30 Apr 2020 16:21:29 +0200 Subject: [PATCH 2/9] Byzcoin: implement contract actions --- ledger/byzcoin/actions/contract/action.go | 116 +++++++++++++++--- .../byzcoin/actions/contract/action_test.go | 95 ++++++++++++++ .../byzcoin/actions/contract/messages.pb.go | 42 +++---- .../byzcoin/actions/contract/messages.proto | 4 +- 4 files changed, 220 insertions(+), 37 deletions(-) diff --git a/ledger/byzcoin/actions/contract/action.go b/ledger/byzcoin/actions/contract/action.go index f29c6a005..e7dcb2974 100644 --- a/ledger/byzcoin/actions/contract/action.go +++ b/ledger/byzcoin/actions/contract/action.go @@ -4,6 +4,7 @@ import ( "io" "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger/arc" "go.dedis.ch/fabric/ledger/inventory" @@ -14,34 +15,76 @@ import ( // SpawnAction is a contract transaction action to create a new instance. type SpawnAction struct { ContractID string - Argument proto.Message + // Argument is a generic slice of bytes that the contract is responsible for + // decoding. It allows a fingerprint to be calculated deterministically. + Argument []byte } // Pack implements encoding.Packable. func (act SpawnAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { - return &SpawnActionProto{}, nil + pb := &SpawnActionProto{ + ContractID: act.ContractID, + Argument: act.Argument, + } + + return pb, nil } // WriteTo implements io.WriterTo. -func (act SpawnAction) WriteTo(io.Writer) (int64, error) { - return 0, nil +func (act SpawnAction) WriteTo(w io.Writer) (int64, error) { + sum := int64(0) + + n, err := w.Write([]byte(act.ContractID)) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write contract: %v", err) + } + + n, err = w.Write(act.Argument) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write argument: %v", err) + } + + return sum, nil } // InvokeAction is a contract transaction action to update an existing instance // of the access control allows it. type InvokeAction struct { - Key []byte - Argument proto.Message + Key []byte + // Argument is a generic slice of bytes that the contract is responsible for + // decoding. It allows a fingerprint to be calculated deterministically. + Argument []byte } // Pack implements encoding.Packable. func (act InvokeAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { - return &SpawnActionProto{}, nil + pb := &InvokeActionProto{ + Key: act.Key, + Argument: act.Argument, + } + + return pb, nil } // WriteTo implements io.WriterTo. -func (act InvokeAction) WriteTo(io.Writer) (int64, error) { - return 0, nil +func (act InvokeAction) WriteTo(w io.Writer) (int64, error) { + sum := int64(0) + + n, err := w.Write(act.Key) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write key: %v", err) + } + + n, err = w.Write(act.Argument) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write argument: %v", err) + } + + return sum, nil } // DeleteAction is a contract transaction action to mark an instance as deleted @@ -56,8 +99,16 @@ func (a DeleteAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { } // WriteTo implements io.WriterTo. -func (a DeleteAction) WriteTo(io.Writer) (int64, error) { - return 0, nil +func (a DeleteAction) WriteTo(w io.Writer) (int64, error) { + sum := int64(0) + + n, err := w.Write(a.Key) + sum += int64(n) + if err != nil { + return sum, xerrors.Errorf("couldn't write key: %v", err) + } + + return sum, nil } type serverAction struct { @@ -102,7 +153,7 @@ func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) err = page.Write(instance.Key, instance) if err != nil { - return err + return xerrors.Errorf("couldn't write instance to page: %v", err) } return nil @@ -206,9 +257,46 @@ func (act serverAction) hasAccess(ctx Context, key []byte, rule string) error { return nil } -type actionFactory struct{} +type actionFactory struct { + contracts map[string]Contract + arcFactory arc.AccessControlFactory + encoder encoding.ProtoMarshaler +} func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { + inAny, ok := in.(*any.Any) + if ok { + var err error + in, err = f.encoder.UnmarshalDynamicAny(inAny) + if err != nil { + return nil, xerrors.Errorf("..") + } + } + + action := serverAction{ + contracts: f.contracts, + arcFactory: f.arcFactory, + encoder: f.encoder, + } + + switch pb := in.(type) { + case *SpawnActionProto: + action.ClientAction = SpawnAction{ + ContractID: pb.GetContractID(), + Argument: pb.GetArgument(), + } + case *InvokeActionProto: + action.ClientAction = InvokeAction{ + Key: pb.GetKey(), + Argument: pb.GetArgument(), + } + case *DeleteActionProto: + action.ClientAction = DeleteAction{ + Key: pb.GetKey(), + } + default: + return nil, xerrors.Errorf("invalid message type '%T'", in) + } - return serverAction{}, nil + return action, nil } diff --git a/ledger/byzcoin/actions/contract/action_test.go b/ledger/byzcoin/actions/contract/action_test.go index df2c7ded8..1c464e9c9 100644 --- a/ledger/byzcoin/actions/contract/action_test.go +++ b/ledger/byzcoin/actions/contract/action_test.go @@ -1,6 +1,7 @@ package contract import ( + "bytes" "testing" "github.com/golang/protobuf/proto" @@ -14,6 +15,100 @@ import ( "golang.org/x/xerrors" ) +func TestSpawnAction_Pack(t *testing.T) { + action := SpawnAction{ + ContractID: "deadbeef", + Argument: []byte{0x01}, + } + + pb, err := action.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*SpawnActionProto)(nil), pb) + + actionpb := pb.(*SpawnActionProto) + require.Equal(t, action.ContractID, actionpb.GetContractID()) + require.Equal(t, action.Argument, actionpb.GetArgument()) +} + +func TestSpawnAction_WriteTo(t *testing.T) { + action := SpawnAction{ + ContractID: "deadbeef", + Argument: []byte{0x01}, + } + + buffer := new(bytes.Buffer) + sum, err := action.WriteTo(buffer) + require.NoError(t, err) + require.Equal(t, int64(9), sum) + + _, err = action.WriteTo(fake.NewBadHash()) + require.EqualError(t, err, "couldn't write contract: fake error") + + _, err = action.WriteTo(fake.NewBadHashWithDelay(1)) + require.EqualError(t, err, "couldn't write argument: fake error") +} + +func TestInvokeAction_Pack(t *testing.T) { + action := InvokeAction{ + Key: []byte{0x1}, + Argument: []byte{0x02}, + } + + pb, err := action.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*InvokeActionProto)(nil), pb) + + actionpb := pb.(*InvokeActionProto) + require.Equal(t, action.Key, actionpb.GetKey()) + require.Equal(t, action.Argument, actionpb.GetArgument()) +} + +func TestInvokeAction_WriteTo(t *testing.T) { + action := InvokeAction{ + Key: []byte{0x01}, + Argument: []byte{0x02}, + } + + buffer := new(bytes.Buffer) + sum, err := action.WriteTo(buffer) + require.NoError(t, err) + require.Equal(t, int64(2), sum) + + _, err = action.WriteTo(fake.NewBadHash()) + require.EqualError(t, err, "couldn't write key: fake error") + + _, err = action.WriteTo(fake.NewBadHashWithDelay(1)) + require.EqualError(t, err, "couldn't write argument: fake error") +} + +func TestDeleteAction_Pack(t *testing.T) { + action := DeleteAction{ + Key: []byte{0x01}, + } + + pb, err := action.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*DeleteActionProto)(nil), pb) + + actionpb := pb.(*DeleteActionProto) + require.Equal(t, action.Key, actionpb.GetKey()) +} + +func TestDeleteAction_WriteTo(t *testing.T) { + action := DeleteAction{ + Key: []byte{0x01}, + } + + buffer := new(bytes.Buffer) + + sum, err := action.WriteTo(buffer) + require.NoError(t, err) + require.Equal(t, int64(1), sum) + + _, err = action.WriteTo(fake.NewBadHash()) + require.EqualError(t, err, "couldn't write key: fake error") +} + func TestServerAction_Consume(t *testing.T) { factory := &fakeAccessFactory{access: &fakeAccess{match: true}} contracts := map[string]Contract{ diff --git a/ledger/byzcoin/actions/contract/messages.pb.go b/ledger/byzcoin/actions/contract/messages.pb.go index 9702c7e30..5cbb5ee32 100644 --- a/ledger/byzcoin/actions/contract/messages.pb.go +++ b/ledger/byzcoin/actions/contract/messages.pb.go @@ -94,7 +94,7 @@ func (m *Instance) GetAccessControl() []byte { type SpawnActionProto struct { ContractID string `protobuf:"bytes,1,opt,name=contractID,proto3" json:"contractID,omitempty"` - Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` + Argument []byte `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -132,7 +132,7 @@ func (m *SpawnActionProto) GetContractID() string { return "" } -func (m *SpawnActionProto) GetArgument() *any.Any { +func (m *SpawnActionProto) GetArgument() []byte { if m != nil { return m.Argument } @@ -141,7 +141,7 @@ func (m *SpawnActionProto) GetArgument() *any.Any { type InvokeActionProto struct { Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` + Argument []byte `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -179,7 +179,7 @@ func (m *InvokeActionProto) GetKey() []byte { return nil } -func (m *InvokeActionProto) GetArgument() *any.Any { +func (m *InvokeActionProto) GetArgument() []byte { if m != nil { return m.Argument } @@ -237,21 +237,21 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 255 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x4f, 0x4b, 0xc3, 0x40, - 0x10, 0xc5, 0x59, 0x6b, 0x35, 0x8e, 0x7f, 0x68, 0x17, 0x0f, 0xab, 0x07, 0x09, 0x41, 0x21, 0x78, - 0x48, 0x45, 0x3f, 0x41, 0xb1, 0x97, 0xdc, 0x24, 0x1e, 0x3c, 0x6f, 0x37, 0x63, 0x90, 0x6e, 0x67, - 0x4a, 0x76, 0x53, 0xc9, 0x37, 0xf2, 0x63, 0x4a, 0x37, 0x44, 0x5a, 0x0a, 0x05, 0x6f, 0xbb, 0x8f, - 0x37, 0xef, 0x37, 0x6f, 0xe0, 0x6a, 0x89, 0xce, 0xe9, 0x0a, 0x5d, 0xb6, 0xaa, 0xd9, 0xb3, 0x8c, - 0x0c, 0x93, 0xaf, 0xb5, 0xf1, 0xb7, 0x37, 0x15, 0x73, 0x65, 0x71, 0x12, 0xf4, 0x79, 0xf3, 0x39, - 0xd1, 0xd4, 0x76, 0xa6, 0xe4, 0x47, 0x40, 0x94, 0x93, 0xf3, 0x9a, 0x0c, 0xca, 0x11, 0x0c, 0x16, - 0xd8, 0x2a, 0x11, 0x8b, 0xf4, 0xa2, 0xd8, 0x3c, 0xe5, 0x23, 0x0c, 0xd7, 0xda, 0x36, 0xa8, 0x8e, - 0x62, 0x91, 0x9e, 0x3f, 0x5f, 0x67, 0x5d, 0x52, 0xd6, 0x27, 0x65, 0x53, 0x6a, 0x8b, 0xce, 0x22, - 0xef, 0x00, 0x7a, 0x62, 0x3e, 0x53, 0x83, 0x58, 0xa4, 0x67, 0xc5, 0x96, 0x22, 0x15, 0x9c, 0x96, - 0x68, 0xd1, 0x63, 0xa9, 0x8e, 0x63, 0x91, 0x46, 0x45, 0xff, 0x95, 0xf7, 0x70, 0xa9, 0x8d, 0x41, - 0xe7, 0x5e, 0x37, 0x6e, 0xb6, 0x6a, 0x18, 0x36, 0xd8, 0x15, 0x93, 0x12, 0x46, 0xef, 0x2b, 0xfd, - 0x4d, 0x53, 0xe3, 0xbf, 0x98, 0xde, 0x42, 0xc7, 0x5d, 0xa6, 0xd8, 0x63, 0x3e, 0x41, 0xa4, 0xeb, - 0xaa, 0x59, 0x22, 0xf9, 0x83, 0x15, 0xfe, 0x5c, 0xc9, 0x07, 0x8c, 0x73, 0x5a, 0xf3, 0x02, 0xb7, - 0x31, 0xfb, 0x87, 0xf9, 0x7f, 0xf0, 0x03, 0x8c, 0x67, 0xa1, 0xef, 0xc1, 0xe0, 0xf9, 0x49, 0x18, - 0x7f, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x55, 0x4b, 0xd8, 0xdd, 0xce, 0x01, 0x00, 0x00, + // 254 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0x51, 0x4b, 0xc3, 0x30, + 0x14, 0x85, 0x89, 0x73, 0x5a, 0xaf, 0x53, 0xb6, 0xe0, 0x43, 0xdc, 0x83, 0x94, 0xa2, 0x50, 0x7c, + 0xe8, 0x40, 0x7f, 0x41, 0x71, 0x2f, 0x7d, 0x11, 0x89, 0xbf, 0x20, 0xcb, 0xae, 0x45, 0xd6, 0xdd, + 0x3b, 0x9a, 0x74, 0xd2, 0x7f, 0xe4, 0xcf, 0x94, 0xa5, 0x54, 0x36, 0x06, 0x7b, 0xcb, 0x3d, 0x1c, + 0xbe, 0x93, 0x73, 0xe0, 0x76, 0x8d, 0xce, 0x99, 0x12, 0x5d, 0xb6, 0xa9, 0xd9, 0xb3, 0x8c, 0x2c, + 0x93, 0xaf, 0x8d, 0xf5, 0xd3, 0xfb, 0x92, 0xb9, 0xac, 0x70, 0x16, 0xf4, 0x45, 0xf3, 0x35, 0x33, + 0xd4, 0x76, 0xa6, 0xe4, 0x57, 0x40, 0x54, 0x90, 0xf3, 0x86, 0x2c, 0xca, 0x31, 0x0c, 0x56, 0xd8, + 0x2a, 0x11, 0x8b, 0x74, 0xa4, 0x77, 0x4f, 0xf9, 0x0c, 0xc3, 0xad, 0xa9, 0x1a, 0x54, 0x67, 0xb1, + 0x48, 0xaf, 0x5f, 0xee, 0xb2, 0x8e, 0x94, 0xf5, 0xa4, 0x2c, 0xa7, 0x56, 0x77, 0x16, 0xf9, 0x00, + 0xd0, 0x27, 0x16, 0x73, 0x35, 0x88, 0x45, 0x7a, 0xa5, 0xf7, 0x14, 0xa9, 0xe0, 0x72, 0x89, 0x15, + 0x7a, 0x5c, 0xaa, 0xf3, 0x58, 0xa4, 0x91, 0xee, 0x4f, 0xf9, 0x08, 0x37, 0xc6, 0x5a, 0x74, 0xee, + 0x6d, 0xe7, 0xe6, 0x4a, 0x0d, 0xc3, 0x0f, 0x0e, 0xc5, 0xe4, 0x1d, 0xc6, 0x9f, 0x1b, 0xf3, 0x43, + 0xb9, 0xf5, 0xdf, 0x4c, 0x1f, 0xa1, 0xe3, 0x61, 0xa6, 0x38, 0xca, 0x9c, 0x42, 0x64, 0xea, 0xb2, + 0x59, 0x23, 0xf9, 0x50, 0x61, 0xa4, 0xff, 0xef, 0x24, 0x87, 0x49, 0x41, 0x5b, 0x5e, 0xe1, 0x3e, + 0xf0, 0x78, 0x82, 0x53, 0x88, 0x27, 0x98, 0xcc, 0x43, 0x87, 0x93, 0x88, 0xc5, 0x45, 0x98, 0xeb, + 0xf5, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xe2, 0x94, 0x99, 0xe1, 0xa2, 0x01, 0x00, 0x00, } diff --git a/ledger/byzcoin/actions/contract/messages.proto b/ledger/byzcoin/actions/contract/messages.proto index eb2103a51..25552bfd1 100644 --- a/ledger/byzcoin/actions/contract/messages.proto +++ b/ledger/byzcoin/actions/contract/messages.proto @@ -14,12 +14,12 @@ message Instance { message SpawnActionProto { string contractID = 1; - google.protobuf.Any argument = 2; + bytes argument = 2; } message InvokeActionProto { bytes key = 1; - google.protobuf.Any argument = 2; + bytes argument = 2; } message DeleteActionProto { From 2ebe52c494dd450695ab280f3657b7eedb803df1 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Fri, 1 May 2020 13:35:32 +0200 Subject: [PATCH 3/9] Improve unit tests --- encoding/mod.go | 5 + internal/testing/fake/mod.go | 12 ++ ledger/arc/common/mod.go | 2 +- ledger/arc/darc/action.go | 82 +++++++---- ledger/arc/darc/action_test.go | 130 +++++++++++++++++ ledger/arc/darc/expr.go | 23 +++ ledger/arc/darc/expr_test.go | 18 +++ ledger/arc/darc/messages.pb.go | 80 +++++------ ledger/arc/darc/messages.proto | 4 +- ledger/arc/darc/mod.go | 50 +++++-- ledger/arc/darc/mod_test.go | 35 ++++- ledger/byzcoin/actions/contract/action.go | 116 +++++++++------- .../byzcoin/actions/contract/action_test.go | 131 ++++++++++++++---- ledger/byzcoin/actions/contract/context.go | 21 +-- .../byzcoin/actions/contract/context_test.go | 28 +++- .../byzcoin/actions/contract/messages.pb.go | 42 +++--- .../byzcoin/actions/contract/messages.proto | 4 +- ledger/byzcoin/mod_test.go | 18 ++- ledger/transactions/basic/mod.go | 29 ++-- ledger/transactions/basic/mod_test.go | 14 +- 20 files changed, 614 insertions(+), 230 deletions(-) create mode 100644 ledger/arc/darc/action_test.go diff --git a/encoding/mod.go b/encoding/mod.go index 10efeaeb7..474125129 100644 --- a/encoding/mod.go +++ b/encoding/mod.go @@ -49,6 +49,11 @@ type ProtoMarshaler interface { UnmarshalDynamicAny(any *any.Any) (proto.Message, error) } +// Fingerprinter is an interface to perform fingerprinting on object. +type Fingerprinter interface { + Fingerprint(io.Writer, ProtoMarshaler) error +} + // ProtoEncoder is a default implementation of protobug encoding/decoding. type ProtoEncoder struct { marshaler *jsonpb.Marshaler diff --git a/internal/testing/fake/mod.go b/internal/testing/fake/mod.go index eea73191e..366a90461 100644 --- a/internal/testing/fake/mod.go +++ b/internal/testing/fake/mod.go @@ -30,16 +30,28 @@ type Call struct { // Get returns the nth call ith parameter. func (c *Call) Get(n, i int) interface{} { + if c == nil { + return nil + } + return c.calls[n][i] } // Len returns the number of calls. func (c *Call) Len() int { + if c == nil { + return 0 + } + return len(c.calls) } // Add adds a call to the list. func (c *Call) Add(args ...interface{}) { + if c == nil { + return + } + c.calls = append(c.calls, args) } diff --git a/ledger/arc/common/mod.go b/ledger/arc/common/mod.go index 58c213da0..3ff695b9a 100644 --- a/ledger/arc/common/mod.go +++ b/ledger/arc/common/mod.go @@ -28,7 +28,7 @@ func NewAccessControlFactory() *AccessControlFactory { factories: make(map[reflect.Type]arc.AccessControlFactory), } - factory.Register((*darc.AccessControlProto)(nil), darc.Factory{}) + factory.Register((*darc.AccessProto)(nil), darc.Factory{}) return factory } diff --git a/ledger/arc/darc/action.go b/ledger/arc/darc/action.go index f0939549f..4d99b7d38 100644 --- a/ledger/arc/darc/action.go +++ b/ledger/arc/darc/action.go @@ -3,13 +3,19 @@ package darc import ( "io" - proto "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/proto" "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/arc" "go.dedis.ch/fabric/ledger/inventory" "go.dedis.ch/fabric/ledger/transactions/basic" "golang.org/x/xerrors" ) +const ( + // UpdateAccessRule is the rule to be defined in the DARC to update it. + UpdateAccessRule = "darc:update" +) + // darcAction is a transaction action to create or update an access right // control. type darcAction struct { @@ -18,75 +24,93 @@ type darcAction struct { } // NewCreate returns a new action to create a DARC. -func NewCreate() basic.ClientAction { - return darcAction{} +func NewCreate(access Access) basic.ClientAction { + return darcAction{access: access} } // NewUpdate returns a new action to update a DARC. -func NewUpdate(key []byte) basic.ClientAction { - return darcAction{key: key} +func NewUpdate(key []byte, access Access) basic.ClientAction { + return darcAction{key: key, access: access} } -// Pack implements encoding.Packable. +// Pack implements encoding.Packable. It returns the protobuf message for the +// action. func (act darcAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &ActionProto{ - Key: act.key, - } - access, err := enc.Pack(act.access) if err != nil { - return nil, err + return nil, xerrors.Errorf("couldn't pack access: %v", err) } - pb.Access = access.(*AccessControlProto) + pb := &ActionProto{ + Key: act.key, + Access: access.(*AccessProto), + } return pb, nil } -// WriteTo implements io.WriterTo. -func (act darcAction) WriteTo(w io.Writer) (int64, error) { - // TODO: write to - sum := int64(0) +// Fingerprint implements encoding.Fingerprinter. It serializes the DARC action +// into the writer in a deterministic way. +func (act darcAction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { + _, err := w.Write(act.key) + if err != nil { + return xerrors.Errorf("couldn't write key: %v", err) + } - n, err := w.Write(act.key) - sum += int64(n) + err = act.access.Fingerprint(w, enc) if err != nil { - return sum, err + return xerrors.Errorf("couldn't fingerprint access: %v", err) } - return 0, nil + return nil } -type serverArcAction struct { +// serverAction is the server-side action for DARCs. +type serverAction struct { encoder encoding.ProtoMarshaler darcAction } -func (act serverArcAction) Consume(ctx basic.Context, page inventory.WritablePage) error { +// Consume implements basic.ServerAction. It writes the DARC into the page if it +// is allowed to do so, otherwise it returns an error. +func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) error { accesspb, err := act.encoder.Pack(act.access) if err != nil { - return err + return xerrors.Errorf("couldn't pack access: %v", err) + } + + key := act.key + if key == nil { + // No key defined means a creation request then we use the transaction + // ID as a unique key for the DARC. + key = ctx.GetID() + } else { + // TODO: access control } - err = page.Write(ctx.GetID(), accesspb) + err = page.Write(key, accesspb) if err != nil { - return err + return xerrors.Errorf("couldn't write access: %v", err) } return nil } type actionFactory struct { - darcFactory Factory + encoder encoding.ProtoMarshaler + darcFactory arc.AccessControlFactory } // NewActionFactory returns a new instance of the action factory. func NewActionFactory() basic.ActionFactory { return actionFactory{ + encoder: encoding.NewProtoEncoder(), darcFactory: NewFactory(), } } +// FromProto implements basic.ActionFactory. It returns the server action of the +// protobuf message when approriate, otherwise an error. func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { var pb *ActionProto switch msg := in.(type) { @@ -98,15 +122,15 @@ func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { access, err := f.darcFactory.FromProto(pb.GetAccess()) if err != nil { - return nil, err + return nil, xerrors.Errorf("couldn't decode access: %v", err) } - servAccess := serverArcAction{ + servAccess := serverAction{ darcAction: darcAction{ key: pb.GetKey(), access: access.(Access), }, - encoder: f.darcFactory.encoder, + encoder: f.encoder, } return servAccess, nil diff --git a/ledger/arc/darc/action_test.go b/ledger/arc/darc/action_test.go new file mode 100644 index 000000000..d67bbbc5a --- /dev/null +++ b/ledger/arc/darc/action_test.go @@ -0,0 +1,130 @@ +package darc + +import ( + "bytes" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/internal/testing/fake" + "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions/basic" + "golang.org/x/xerrors" +) + +func TestAction_Pack(t *testing.T) { + action := darcAction{ + key: []byte{0x01}, + access: NewAccess(), + } + + pb, err := action.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*ActionProto)(nil), pb) + + actionpb := pb.(*ActionProto) + require.Equal(t, action.key, actionpb.GetKey()) + + _, err = action.Pack(fake.BadPackEncoder{}) + require.EqualError(t, err, "couldn't pack access: fake error") +} + +func TestAction_Fingerprint(t *testing.T) { + action := darcAction{ + key: []byte{0x01}, + access: Access{rules: map[string]expression{ + "\x02": {matches: map[string]struct{}{"\x03": {}}}, + }}, + } + + buffer := new(bytes.Buffer) + + err := action.Fingerprint(buffer, encoding.NewProtoEncoder()) + require.NoError(t, err) + require.Equal(t, "\x01\x02\x03", buffer.String()) + + err = action.Fingerprint(fake.NewBadHash(), nil) + require.EqualError(t, err, "couldn't write key: fake error") + + err = action.Fingerprint(fake.NewBadHashWithDelay(1), nil) + require.EqualError(t, err, + "couldn't fingerprint access: couldn't write key: fake error") +} + +func TestServerAction_Consume(t *testing.T) { + action := serverAction{ + encoder: encoding.NewProtoEncoder(), + darcAction: darcAction{key: []byte{0x01}}, + } + + call := &fake.Call{} + err := action.Consume(fakeContext{}, fakePage{call: call}) + require.NoError(t, err) + require.Equal(t, 1, call.Len()) + // Key is provided so it's an update. + require.Equal(t, []byte{0x01}, call.Get(0, 0)) + + // No key thus it's a creation. + action.darcAction.key = nil + err = action.Consume(fakeContext{}, fakePage{call: call}) + require.NoError(t, err) + require.Equal(t, 2, call.Len()) + require.Equal(t, []byte{0x34}, call.Get(1, 0)) + + action.encoder = fake.BadPackEncoder{} + err = action.Consume(fakeContext{}, fakePage{}) + require.EqualError(t, err, "couldn't pack access: fake error") + + action.encoder = encoding.NewProtoEncoder() + err = action.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) + require.EqualError(t, err, "couldn't write access: oops") +} + +func TestActionFactory_FromProto(t *testing.T) { + factory := NewActionFactory().(actionFactory) + + actionpb := &ActionProto{Key: []byte{0x02}, Access: &AccessProto{}} + action, err := factory.FromProto(actionpb) + require.NoError(t, err) + require.IsType(t, serverAction{}, action) + + _, err = factory.FromProto(nil) + require.EqualError(t, err, "invalid message type ''") + + factory.darcFactory = badArcFactory{} + _, err = factory.FromProto(&ActionProto{}) + require.EqualError(t, err, "couldn't decode access: oops") +} + +// ----------------------------------------------------------------------------- +// Utility functions + +type fakeContext struct { + basic.Context +} + +func (ctx fakeContext) GetID() []byte { + return []byte{0x34} +} + +type fakePage struct { + inventory.WritablePage + call *fake.Call + err error +} + +func (page fakePage) Write(key []byte, value proto.Message) error { + page.call.Add(key, value) + + return page.err +} + +type badArcFactory struct { + arc.AccessControlFactory +} + +func (f badArcFactory) FromProto(proto.Message) (arc.AccessControl, error) { + return nil, xerrors.New("oops") +} diff --git a/ledger/arc/darc/expr.go b/ledger/arc/darc/expr.go index c96a557e4..8706ae3b1 100644 --- a/ledger/arc/darc/expr.go +++ b/ledger/arc/darc/expr.go @@ -1,6 +1,9 @@ package darc import ( + "io" + "sort" + proto "github.com/golang/protobuf/proto" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger/arc" @@ -56,6 +59,26 @@ func (expr expression) Match(targets []arc.Identity) error { return nil } +// Fingerprint implements encoding.Fingerprinter. It serializes the expression +// into the writer in a deterministic way. +func (expr expression) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { + matches := make(sort.StringSlice, 0, len(expr.matches)) + for key := range expr.matches { + matches = append(matches, key) + } + + sort.Sort(matches) + + for _, match := range matches { + _, err := w.Write([]byte(match)) + if err != nil { + return xerrors.Errorf("couldn't write match: %v", err) + } + } + + return nil +} + // Pack implements encoding.Packable. It returns the protobuf message for the // expression. func (expr expression) Pack(encoding.ProtoMarshaler) (proto.Message, error) { diff --git a/ledger/arc/darc/expr_test.go b/ledger/arc/darc/expr_test.go index 8401e7068..a7a4bdc00 100644 --- a/ledger/arc/darc/expr_test.go +++ b/ledger/arc/darc/expr_test.go @@ -1,10 +1,12 @@ package darc import ( + "bytes" fmt "fmt" "testing" "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/ledger/arc" "golang.org/x/xerrors" ) @@ -52,6 +54,22 @@ func TestExpression_Match(t *testing.T) { require.EqualError(t, err, "couldn't marshal identity: oops") } +func TestExpression_Fingerprint(t *testing.T) { + expr := expression{matches: map[string]struct{}{ + "\x01": {}, + "\x03": {}, + }} + + buffer := new(bytes.Buffer) + + err := expr.Fingerprint(buffer, nil) + require.NoError(t, err) + require.Equal(t, "\x01\x03", buffer.String()) + + err = expr.Fingerprint(fake.NewBadHash(), nil) + require.EqualError(t, err, "couldn't write match: fake error") +} + func TestExpression_Pack(t *testing.T) { idents := []arc.Identity{ fakeIdentity{buffer: []byte{0xaa}}, diff --git a/ledger/arc/darc/messages.pb.go b/ledger/arc/darc/messages.pb.go index 7faa35480..febf18a16 100644 --- a/ledger/arc/darc/messages.pb.go +++ b/ledger/arc/darc/messages.pb.go @@ -59,39 +59,39 @@ func (m *Expression) GetMatches() []string { return nil } -type AccessControlProto struct { +type AccessProto struct { Rules map[string]*Expression `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *AccessControlProto) Reset() { *m = AccessControlProto{} } -func (m *AccessControlProto) String() string { return proto.CompactTextString(m) } -func (*AccessControlProto) ProtoMessage() {} -func (*AccessControlProto) Descriptor() ([]byte, []int) { +func (m *AccessProto) Reset() { *m = AccessProto{} } +func (m *AccessProto) String() string { return proto.CompactTextString(m) } +func (*AccessProto) ProtoMessage() {} +func (*AccessProto) Descriptor() ([]byte, []int) { return fileDescriptor_4dc296cbfe5ffcd5, []int{1} } -func (m *AccessControlProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_AccessControlProto.Unmarshal(m, b) +func (m *AccessProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AccessProto.Unmarshal(m, b) } -func (m *AccessControlProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_AccessControlProto.Marshal(b, m, deterministic) +func (m *AccessProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AccessProto.Marshal(b, m, deterministic) } -func (m *AccessControlProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_AccessControlProto.Merge(m, src) +func (m *AccessProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccessProto.Merge(m, src) } -func (m *AccessControlProto) XXX_Size() int { - return xxx_messageInfo_AccessControlProto.Size(m) +func (m *AccessProto) XXX_Size() int { + return xxx_messageInfo_AccessProto.Size(m) } -func (m *AccessControlProto) XXX_DiscardUnknown() { - xxx_messageInfo_AccessControlProto.DiscardUnknown(m) +func (m *AccessProto) XXX_DiscardUnknown() { + xxx_messageInfo_AccessProto.DiscardUnknown(m) } -var xxx_messageInfo_AccessControlProto proto.InternalMessageInfo +var xxx_messageInfo_AccessProto proto.InternalMessageInfo -func (m *AccessControlProto) GetRules() map[string]*Expression { +func (m *AccessProto) GetRules() map[string]*Expression { if m != nil { return m.Rules } @@ -99,11 +99,11 @@ func (m *AccessControlProto) GetRules() map[string]*Expression { } type ActionProto struct { - Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Access *AccessControlProto `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Access *AccessProto `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ActionProto) Reset() { *m = ActionProto{} } @@ -138,7 +138,7 @@ func (m *ActionProto) GetKey() []byte { return nil } -func (m *ActionProto) GetAccess() *AccessControlProto { +func (m *ActionProto) GetAccess() *AccessProto { if m != nil { return m.Access } @@ -147,8 +147,8 @@ func (m *ActionProto) GetAccess() *AccessControlProto { func init() { proto.RegisterType((*Expression)(nil), "darc.Expression") - proto.RegisterType((*AccessControlProto)(nil), "darc.AccessControlProto") - proto.RegisterMapType((map[string]*Expression)(nil), "darc.AccessControlProto.RulesEntry") + proto.RegisterType((*AccessProto)(nil), "darc.AccessProto") + proto.RegisterMapType((map[string]*Expression)(nil), "darc.AccessProto.RulesEntry") proto.RegisterType((*ActionProto)(nil), "darc.ActionProto") } @@ -157,19 +157,19 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 217 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0x4f, 0x4b, 0x87, 0x30, - 0x18, 0x80, 0xd9, 0xef, 0x97, 0x86, 0xaf, 0x11, 0xb2, 0xd3, 0xe8, 0x24, 0x06, 0xe2, 0x69, 0x84, - 0x5d, 0xaa, 0x9b, 0x84, 0x97, 0x4e, 0xb5, 0x6f, 0xb0, 0xd6, 0x28, 0x49, 0x37, 0xd9, 0x3b, 0x23, - 0x3f, 0x4b, 0x5f, 0x36, 0x74, 0x86, 0x41, 0x74, 0xdb, 0x9f, 0xe7, 0x7d, 0x78, 0x78, 0xe1, 0x7c, - 0xd0, 0x88, 0xf2, 0x55, 0x23, 0x1f, 0x9d, 0xf5, 0x96, 0x9e, 0xbc, 0x48, 0xa7, 0x8a, 0x12, 0xa0, - 0xfd, 0x1c, 0x9d, 0x46, 0xec, 0xac, 0xa1, 0x0c, 0x4e, 0x07, 0xe9, 0xd5, 0x9b, 0x46, 0x46, 0xf2, - 0x63, 0x95, 0x88, 0x9f, 0x6b, 0xf1, 0x45, 0x80, 0x36, 0x4a, 0x69, 0xc4, 0x7b, 0x6b, 0xbc, 0xb3, - 0xfd, 0xe3, 0x2a, 0xb9, 0x85, 0xc8, 0x4d, 0xfd, 0x86, 0xa7, 0xf5, 0x25, 0x5f, 0xa4, 0xfc, 0x2f, - 0xc8, 0xc5, 0x42, 0xb5, 0xc6, 0xbb, 0x59, 0x84, 0x89, 0x8b, 0x07, 0x80, 0xfd, 0x91, 0x66, 0x70, - 0x7c, 0xd7, 0x33, 0x23, 0x39, 0xa9, 0x12, 0xb1, 0x1c, 0x69, 0x09, 0xd1, 0x87, 0xec, 0x27, 0xcd, - 0x0e, 0x39, 0xa9, 0xd2, 0x3a, 0x0b, 0xea, 0x3d, 0x56, 0x84, 0xef, 0xbb, 0xc3, 0x0d, 0x29, 0x9e, - 0x20, 0x6d, 0x94, 0xef, 0xac, 0x09, 0x55, 0xbf, 0x64, 0x67, 0x41, 0x76, 0x05, 0xb1, 0x5c, 0xa3, - 0x36, 0x1b, 0xfb, 0x2f, 0x54, 0x6c, 0xdc, 0x73, 0xbc, 0x6e, 0xe9, 0xfa, 0x3b, 0x00, 0x00, 0xff, - 0xff, 0xdb, 0x76, 0x85, 0xae, 0x37, 0x01, 0x00, 0x00, + // 209 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, + 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0x49, 0x2c, 0x4a, + 0x56, 0x52, 0xe3, 0xe2, 0x72, 0xad, 0x28, 0x28, 0x4a, 0x2d, 0x2e, 0xce, 0xcc, 0xcf, 0x13, 0x92, + 0xe0, 0x62, 0xcf, 0x4d, 0x2c, 0x49, 0xce, 0x48, 0x2d, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, + 0x82, 0x71, 0x95, 0x7a, 0x19, 0xb9, 0xb8, 0x1d, 0x93, 0x93, 0x53, 0x8b, 0x8b, 0x03, 0xc0, 0xba, + 0x8d, 0xb8, 0x58, 0x8b, 0x4a, 0x73, 0xa0, 0xea, 0xb8, 0x8d, 0x64, 0xf4, 0x40, 0xa6, 0xe9, 0x21, + 0xa9, 0xd0, 0x0b, 0x02, 0x49, 0xbb, 0xe6, 0x95, 0x14, 0x55, 0x06, 0x41, 0x94, 0x4a, 0x79, 0x71, + 0x71, 0x21, 0x04, 0x85, 0x04, 0xb8, 0x98, 0xb3, 0x53, 0x2b, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, + 0x83, 0x40, 0x4c, 0x21, 0x35, 0x2e, 0xd6, 0xb2, 0xc4, 0x9c, 0xd2, 0x54, 0x09, 0x26, 0x05, 0x46, + 0x0d, 0x6e, 0x23, 0x01, 0x88, 0x99, 0x08, 0xe7, 0x05, 0x41, 0xa4, 0xad, 0x98, 0x2c, 0x18, 0x95, + 0xbc, 0x40, 0xce, 0x29, 0xc9, 0xcc, 0xcf, 0x83, 0x38, 0x07, 0xc9, 0x30, 0x1e, 0x88, 0x61, 0x9a, + 0x5c, 0x6c, 0x89, 0x60, 0xd7, 0x40, 0x4d, 0x13, 0xc4, 0x70, 0x61, 0x10, 0x54, 0x41, 0x12, 0x1b, + 0x38, 0x40, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x79, 0x71, 0xdf, 0x22, 0x01, 0x00, + 0x00, } diff --git a/ledger/arc/darc/messages.proto b/ledger/arc/darc/messages.proto index bdba3ba9f..04a2f02ab 100644 --- a/ledger/arc/darc/messages.proto +++ b/ledger/arc/darc/messages.proto @@ -6,11 +6,11 @@ message Expression { repeated string matches = 1; } -message AccessControlProto { +message AccessProto { map rules = 1; } message ActionProto { bytes key = 1; - AccessControlProto access = 2; + AccessProto access = 2; } diff --git a/ledger/arc/darc/mod.go b/ledger/arc/darc/mod.go index 772176679..66d820ec0 100644 --- a/ledger/arc/darc/mod.go +++ b/ledger/arc/darc/mod.go @@ -2,6 +2,9 @@ package darc import ( + "io" + "sort" + "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/any" "go.dedis.ch/fabric/encoding" @@ -11,14 +14,6 @@ import ( //go:generate protoc -I ./ --go_out=./ ./messages.proto -// EvolvableAccessControl is an extension of the arc.AccessControl interface to -// evolve the access control. -type EvolvableAccessControl interface { - arc.AccessControl - - Evolve(rule string, targets ...arc.Identity) (Access, error) -} - // Access is the DARC implementation of an Evolvable Access Control. // // - implements darc.EvolvableAccessControl @@ -27,8 +22,8 @@ type Access struct { rules map[string]expression } -// newAccessControl returns a new empty instance of an access control. -func newAccessControl() Access { +// NewAccess returns a new empty instance of an access control. +func NewAccess() Access { return Access{ rules: make(map[string]expression), } @@ -69,9 +64,34 @@ func (ac Access) Match(rule string, targets ...arc.Identity) error { return expr.Match(targets) } +// Fingerprint implements encoding.Fingerprinter. It serializes the access to +// the writer in a deterministic way. +func (ac Access) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { + keys := make(sort.StringSlice, 0, len(ac.rules)) + for key := range ac.rules { + keys = append(keys, key) + } + + sort.Sort(keys) + + for _, key := range keys { + _, err := w.Write([]byte(key)) + if err != nil { + return xerrors.Errorf("couldn't write key: %v", err) + } + + err = ac.rules[key].Fingerprint(w, e) + if err != nil { + return xerrors.Errorf("couldn't fingerprint rule '%s': %v", key, err) + } + } + + return nil +} + // Pack implements encoding.Packable. func (ac Access) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &AccessControlProto{ + pb := &AccessProto{ Rules: make(map[string]*Expression), } @@ -112,21 +132,21 @@ func NewFactory() Factory { // FromProto implements arc.AccessControlFactory. It returns the access control // associated with the protobuf message. func (f Factory) FromProto(in proto.Message) (arc.AccessControl, error) { - var pb *AccessControlProto + var pb *AccessProto switch msg := in.(type) { case *any.Any: - pb = &AccessControlProto{} + pb = &AccessProto{} err := f.encoder.UnmarshalAny(msg, pb) if err != nil { return nil, xerrors.Errorf("couldn't unmarshal message: %v", err) } - case *AccessControlProto: + case *AccessProto: pb = msg default: return nil, xerrors.Errorf("invalid message type '%T'", in) } - ac := newAccessControl() + ac := NewAccess() for rule, exprpb := range pb.GetRules() { expr := newExpression() diff --git a/ledger/arc/darc/mod_test.go b/ledger/arc/darc/mod_test.go index 5ed7b2489..1bc57f14d 100644 --- a/ledger/arc/darc/mod_test.go +++ b/ledger/arc/darc/mod_test.go @@ -1,6 +1,7 @@ package darc import ( + "bytes" "testing" proto "github.com/golang/protobuf/proto" @@ -17,7 +18,8 @@ import ( func TestMessages(t *testing.T) { messages := []proto.Message{ &Expression{}, - &AccessControlProto{}, + &AccessProto{}, + &ActionProto{}, } for _, m := range messages { @@ -26,7 +28,7 @@ func TestMessages(t *testing.T) { } func TestAccess_Evolve(t *testing.T) { - access := newAccessControl() + access := NewAccess() idents := []arc.Identity{ fakeIdentity{buffer: []byte{0xaa}}, @@ -55,7 +57,7 @@ func TestAccess_Match(t *testing.T) { fakeIdentity{buffer: []byte{0xbb}}, } - access, err := newAccessControl().Evolve("fake", idents...) + access, err := NewAccess().Evolve("fake", idents...) require.NoError(t, err) err = access.Match("fake", idents...) @@ -68,20 +70,41 @@ func TestAccess_Match(t *testing.T) { require.EqualError(t, err, "rule 'unknown' not found") } +func TestAccess_Fingerprint(t *testing.T) { + access := Access{ + rules: map[string]expression{ + "\x02": {matches: map[string]struct{}{"\x04": {}}}, + }, + } + + buffer := new(bytes.Buffer) + + err := access.Fingerprint(buffer, nil) + require.NoError(t, err) + require.Equal(t, "\x02\x04", buffer.String()) + + err = access.Fingerprint(fake.NewBadHash(), nil) + require.EqualError(t, err, "couldn't write key: fake error") + + err = access.Fingerprint(fake.NewBadHashWithDelay(1), nil) + require.EqualError(t, err, + "couldn't fingerprint rule '\x02': couldn't write match: fake error") +} + func TestAccess_Pack(t *testing.T) { idents := []arc.Identity{ fakeIdentity{buffer: []byte{0xaa}}, fakeIdentity{buffer: []byte{0xbb}}, } - access, err := newAccessControl().Evolve("fake", idents...) + access, err := NewAccess().Evolve("fake", idents...) require.NoError(t, err) encoder := encoding.NewProtoEncoder() pb, err := access.Pack(encoder) require.NoError(t, err) - require.Len(t, pb.(*AccessControlProto).GetRules(), 1) + require.Len(t, pb.(*AccessProto).GetRules(), 1) _, err = access.Pack(fake.BadPackEncoder{}) require.EqualError(t, err, "couldn't pack expression: fake error") @@ -90,7 +113,7 @@ func TestAccess_Pack(t *testing.T) { func TestFactory_FromProto(t *testing.T) { factory := NewFactory() - pb := &AccessControlProto{ + pb := &AccessProto{ Rules: map[string]*Expression{ "fake": { Matches: []string{"aa", "bb"}, diff --git a/ledger/byzcoin/actions/contract/action.go b/ledger/byzcoin/actions/contract/action.go index e7dcb2974..328227a5f 100644 --- a/ledger/byzcoin/actions/contract/action.go +++ b/ledger/byzcoin/actions/contract/action.go @@ -4,9 +4,10 @@ import ( "io" "github.com/golang/protobuf/proto" - any "github.com/golang/protobuf/ptypes/any" + "github.com/golang/protobuf/ptypes/any" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger/arc" + "go.dedis.ch/fabric/ledger/arc/common" "go.dedis.ch/fabric/ledger/inventory" "go.dedis.ch/fabric/ledger/transactions/basic" "golang.org/x/xerrors" @@ -15,76 +16,74 @@ import ( // SpawnAction is a contract transaction action to create a new instance. type SpawnAction struct { ContractID string - // Argument is a generic slice of bytes that the contract is responsible for - // decoding. It allows a fingerprint to be calculated deterministically. - Argument []byte + Argument proto.Message } // Pack implements encoding.Packable. -func (act SpawnAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { +func (act SpawnAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { + argument, err := enc.MarshalAny(act.Argument) + if err != nil { + return nil, xerrors.Errorf("couldn't pack argument: %v", err) + } + pb := &SpawnActionProto{ ContractID: act.ContractID, - Argument: act.Argument, + Argument: argument, } return pb, nil } -// WriteTo implements io.WriterTo. -func (act SpawnAction) WriteTo(w io.Writer) (int64, error) { - sum := int64(0) - - n, err := w.Write([]byte(act.ContractID)) - sum += int64(n) +// Fingerprint implements encoding.Fingerprinter. +func (act SpawnAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { + _, err := w.Write([]byte(act.ContractID)) if err != nil { - return sum, xerrors.Errorf("couldn't write contract: %v", err) + return xerrors.Errorf("couldn't write contract: %v", err) } - n, err = w.Write(act.Argument) - sum += int64(n) + err = e.MarshalStable(w, act.Argument) if err != nil { - return sum, xerrors.Errorf("couldn't write argument: %v", err) + return xerrors.Errorf("couldn't write argument: %v", err) } - return sum, nil + return nil } // InvokeAction is a contract transaction action to update an existing instance // of the access control allows it. type InvokeAction struct { - Key []byte - // Argument is a generic slice of bytes that the contract is responsible for - // decoding. It allows a fingerprint to be calculated deterministically. - Argument []byte + Key []byte + Argument proto.Message } // Pack implements encoding.Packable. -func (act InvokeAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { +func (act InvokeAction) Pack(e encoding.ProtoMarshaler) (proto.Message, error) { + argument, err := e.MarshalAny(act.Argument) + if err != nil { + return nil, xerrors.Errorf("couldn't pack argument: %v", err) + } + pb := &InvokeActionProto{ Key: act.Key, - Argument: act.Argument, + Argument: argument, } return pb, nil } -// WriteTo implements io.WriterTo. -func (act InvokeAction) WriteTo(w io.Writer) (int64, error) { - sum := int64(0) - - n, err := w.Write(act.Key) - sum += int64(n) +// Fingerprint implements encoding.Fingeprinter. +func (act InvokeAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { + _, err := w.Write(act.Key) if err != nil { - return sum, xerrors.Errorf("couldn't write key: %v", err) + return xerrors.Errorf("couldn't write key: %v", err) } - n, err = w.Write(act.Argument) - sum += int64(n) + err = e.MarshalStable(w, act.Argument) if err != nil { - return sum, xerrors.Errorf("couldn't write argument: %v", err) + return xerrors.Errorf("couldn't write argument: %v", err) } - return sum, nil + return nil } // DeleteAction is a contract transaction action to mark an instance as deleted @@ -98,17 +97,14 @@ func (a DeleteAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { return &DeleteActionProto{Key: a.Key}, nil } -// WriteTo implements io.WriterTo. -func (a DeleteAction) WriteTo(w io.Writer) (int64, error) { - sum := int64(0) - - n, err := w.Write(a.Key) - sum += int64(n) +// Fingerprint implements encoding.Fingerprinter. +func (a DeleteAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { + _, err := w.Write(a.Key) if err != nil { - return sum, xerrors.Errorf("couldn't write key: %v", err) + return xerrors.Errorf("couldn't write key: %v", err) } - return sum, nil + return nil } type serverAction struct { @@ -119,7 +115,7 @@ type serverAction struct { } func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) error { - txCtx := transactionContext{ + txCtx := actionContext{ Context: ctx, arcFactory: act.arcFactory, page: page, @@ -144,10 +140,11 @@ func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) DeleteAction: action, }) default: - return xerrors.New("missing action") + return xerrors.Errorf("invalid action type '%T'", act.ClientAction) } if err != nil { + // No wrapping to avoid redundancy in the error message. return err } @@ -184,7 +181,7 @@ func (act serverAction) consumeSpawn(ctx SpawnContext) (*Instance, error) { valueAny, err := act.encoder.MarshalAny(value) if err != nil { - return nil, err + return nil, xerrors.Errorf("couldn't pack value: %v", err) } instance := &Instance{ @@ -223,7 +220,7 @@ func (act serverAction) consumeInvoke(ctx InvokeContext) (*Instance, error) { valueAny, err := act.encoder.MarshalAny(value) if err != nil { - return nil, err + return nil, xerrors.Errorf("couldn't pack value: %v", err) } instance.Value = valueAny @@ -245,7 +242,7 @@ func (act serverAction) consumeDelete(ctx DeleteContext) (*Instance, error) { func (act serverAction) hasAccess(ctx Context, key []byte, rule string) error { access, err := ctx.GetArc(key) if err != nil { - return xerrors.Errorf("couldn't decode access: %v", err) + return xerrors.Errorf("couldn't read access: %v", err) } err = access.Match(rule, ctx.GetIdentity()) @@ -257,19 +254,38 @@ func (act serverAction) hasAccess(ctx Context, key []byte, rule string) error { return nil } -type actionFactory struct { +// ActionFactory is a factory to decode protobuf messages into contract actions +// and register static contracts. +type ActionFactory struct { contracts map[string]Contract arcFactory arc.AccessControlFactory encoder encoding.ProtoMarshaler } -func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { +// NewActionFactory returns a new empty instance of the factory. +func NewActionFactory() ActionFactory { + return ActionFactory{ + contracts: make(map[string]Contract), + arcFactory: common.NewAccessControlFactory(), + encoder: encoding.NewProtoEncoder(), + } +} + +// Register registers the contract using the name as the identifier. If an +// identifier already exists, it will be overwritten. +func (f ActionFactory) Register(name string, contract Contract) { + f.contracts[name] = contract +} + +// FromProto implements basic.ActionFactory. It returns the server action of a +// protobuf message when appropriate, otherwise an error. +func (f ActionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { inAny, ok := in.(*any.Any) if ok { var err error in, err = f.encoder.UnmarshalDynamicAny(inAny) if err != nil { - return nil, xerrors.Errorf("..") + return nil, xerrors.Errorf("couldn't unmarshal message: %v", err) } } diff --git a/ledger/byzcoin/actions/contract/action_test.go b/ledger/byzcoin/actions/contract/action_test.go index 1c464e9c9..16898d527 100644 --- a/ledger/byzcoin/actions/contract/action_test.go +++ b/ledger/byzcoin/actions/contract/action_test.go @@ -18,7 +18,7 @@ import ( func TestSpawnAction_Pack(t *testing.T) { action := SpawnAction{ ContractID: "deadbeef", - Argument: []byte{0x01}, + Argument: &empty.Empty{}, } pb, err := action.Pack(encoding.NewProtoEncoder()) @@ -27,31 +27,36 @@ func TestSpawnAction_Pack(t *testing.T) { actionpb := pb.(*SpawnActionProto) require.Equal(t, action.ContractID, actionpb.GetContractID()) - require.Equal(t, action.Argument, actionpb.GetArgument()) + require.True(t, ptypes.Is(actionpb.GetArgument(), action.Argument)) + + _, err = action.Pack(fake.BadMarshalAnyEncoder{}) + require.EqualError(t, err, "couldn't pack argument: fake error") } -func TestSpawnAction_WriteTo(t *testing.T) { +func TestSpawnAction_Fingerprint(t *testing.T) { action := SpawnAction{ ContractID: "deadbeef", - Argument: []byte{0x01}, + Argument: &empty.Empty{}, } buffer := new(bytes.Buffer) - sum, err := action.WriteTo(buffer) + encoder := encoding.NewProtoEncoder() + + err := action.Fingerprint(buffer, encoder) require.NoError(t, err) - require.Equal(t, int64(9), sum) + require.Equal(t, "deadbeef{}", buffer.String()) - _, err = action.WriteTo(fake.NewBadHash()) + err = action.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write contract: fake error") - _, err = action.WriteTo(fake.NewBadHashWithDelay(1)) + err = action.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) require.EqualError(t, err, "couldn't write argument: fake error") } func TestInvokeAction_Pack(t *testing.T) { action := InvokeAction{ - Key: []byte{0x1}, - Argument: []byte{0x02}, + Key: []byte{0x01}, + Argument: &empty.Empty{}, } pb, err := action.Pack(encoding.NewProtoEncoder()) @@ -60,24 +65,29 @@ func TestInvokeAction_Pack(t *testing.T) { actionpb := pb.(*InvokeActionProto) require.Equal(t, action.Key, actionpb.GetKey()) - require.Equal(t, action.Argument, actionpb.GetArgument()) + require.True(t, ptypes.Is(actionpb.GetArgument(), action.Argument)) + + _, err = action.Pack(fake.BadMarshalAnyEncoder{}) + require.EqualError(t, err, "couldn't pack argument: fake error") } func TestInvokeAction_WriteTo(t *testing.T) { action := InvokeAction{ Key: []byte{0x01}, - Argument: []byte{0x02}, + Argument: &empty.Empty{}, } buffer := new(bytes.Buffer) - sum, err := action.WriteTo(buffer) + encoder := encoding.NewProtoEncoder() + + err := action.Fingerprint(buffer, encoder) require.NoError(t, err) - require.Equal(t, int64(2), sum) + require.Equal(t, "\x01{}", buffer.String()) - _, err = action.WriteTo(fake.NewBadHash()) + err = action.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write key: fake error") - _, err = action.WriteTo(fake.NewBadHashWithDelay(1)) + err = action.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) require.EqualError(t, err, "couldn't write argument: fake error") } @@ -100,12 +110,13 @@ func TestDeleteAction_WriteTo(t *testing.T) { } buffer := new(bytes.Buffer) + encoder := encoding.NewProtoEncoder() - sum, err := action.WriteTo(buffer) + err := action.Fingerprint(buffer, encoder) require.NoError(t, err) - require.Equal(t, int64(1), sum) + require.Equal(t, "\x01", buffer.String()) - _, err = action.WriteTo(fake.NewBadHash()) + err = action.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write key: fake error") } @@ -136,10 +147,28 @@ func TestServerAction_Consume(t *testing.T) { err := action.Consume(fakeContext{id: []byte("b")}, page) require.NoError(t, err) + err = action.Consume(fakeContext{id: []byte("a")}, page) + require.EqualError(t, err, "instance already exists") + + action.ClientAction = SpawnAction{ContractID: "unknown"} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "contract 'unknown' not found") + action.ClientAction = SpawnAction{ContractID: "bad"} err = action.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't execute spawn: oops") + action.ClientAction = SpawnAction{ContractID: "fake"} + factory.err = xerrors.New("oops") + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, + "no access: couldn't read access: couldn't decode access: oops") + + factory.err = nil + action.encoder = fake.BadMarshalAnyEncoder{} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't pack value: fake error") + // 2. Consume an invoke transaction. action.encoder = encoding.NewProtoEncoder() action.ClientAction = InvokeAction{Key: []byte("b")} @@ -154,7 +183,7 @@ func TestServerAction_Consume(t *testing.T) { action.ClientAction = InvokeAction{Key: []byte("c")} err = action.Consume(fakeContext{}, page) require.EqualError(t, err, - "couldn't read the instance: couldn't read the entry: not found") + "couldn't read the instance: couldn't read the value: not found") action.ClientAction = InvokeAction{Key: []byte("z")} err = action.Consume(fakeContext{}, page) @@ -163,7 +192,8 @@ func TestServerAction_Consume(t *testing.T) { action.ClientAction = InvokeAction{Key: []byte("b")} factory.err = xerrors.New("oops") err = action.Consume(fakeContext{}, page) - require.EqualError(t, err, "no access: couldn't decode access: oops") + require.EqualError(t, err, + "no access: couldn't read access: couldn't decode access: oops") factory.err = nil factory.access.match = false @@ -176,6 +206,11 @@ func TestServerAction_Consume(t *testing.T) { err = action.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't invoke: oops") + action.ClientAction = InvokeAction{Key: []byte("a")} + action.encoder = fake.BadMarshalAnyEncoder{} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't pack value: fake error") + // 3. Consume a delete transaction. action.ClientAction = DeleteAction{Key: []byte("a")} @@ -185,12 +220,60 @@ func TestServerAction_Consume(t *testing.T) { action.ClientAction = DeleteAction{Key: []byte("c")} err = action.Consume(fakeContext{}, page) require.EqualError(t, err, - "couldn't read the instance: couldn't read the entry: not found") + "couldn't read the instance: couldn't read the value: not found") // 4. Consume an invalid transaction. + page.err = xerrors.New("oops") + action.ClientAction = DeleteAction{Key: []byte("a")} + err = action.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't write instance to page: oops") + action.ClientAction = nil err = action.Consume(fakeContext{}, page) - require.EqualError(t, err, "missing action") + require.EqualError(t, err, "invalid action type ''") +} + +func TestActionFactory_Register(t *testing.T) { + factory := NewActionFactory() + + factory.Register("a", fakeContract{}) + factory.Register("b", fakeContract{}) + require.Len(t, factory.contracts, 2) + + factory.Register("a", fakeContract{}) + require.Len(t, factory.contracts, 2) +} + +func TestActionFactory_FromProto(t *testing.T) { + factory := NewActionFactory() + + spawnpb := &SpawnActionProto{ContractID: "A"} + action, err := factory.FromProto(spawnpb) + require.NoError(t, err) + require.NotNil(t, action) + + spawnAny, err := ptypes.MarshalAny(spawnpb) + require.NoError(t, err) + action, err = factory.FromProto(spawnAny) + require.NoError(t, err) + require.NotNil(t, action) + + invokepb := &InvokeActionProto{Key: []byte{0x01}} + action, err = factory.FromProto(invokepb) + require.NoError(t, err) + require.NotNil(t, action) + + deletepb := &DeleteActionProto{Key: []byte{0x01}} + action, err = factory.FromProto(deletepb) + require.NoError(t, err) + require.NotNil(t, action) + + _, err = factory.FromProto(nil) + require.EqualError(t, err, "invalid message type ''") + + factory.encoder = fake.BadUnmarshalDynEncoder{} + _, err = factory.FromProto(spawnAny) + require.EqualError(t, err, "couldn't unmarshal message: fake error") } // ----------------------------------------------------------------------------- @@ -235,7 +318,7 @@ func (page fakePage) Read(key []byte) (proto.Message, error) { return nil, xerrors.New("not found") } - return instance, page.err + return instance, nil } func (page fakePage) Write(key []byte, value proto.Message) error { diff --git a/ledger/byzcoin/actions/contract/context.go b/ledger/byzcoin/actions/contract/context.go index 17a1eab58..7dc69c6ff 100644 --- a/ledger/byzcoin/actions/contract/context.go +++ b/ledger/byzcoin/actions/contract/context.go @@ -7,37 +7,40 @@ import ( "golang.org/x/xerrors" ) -type transactionContext struct { +type actionContext struct { basic.Context arcFactory arc.AccessControlFactory page inventory.Page } -func (ctx transactionContext) GetArc(key []byte) (arc.AccessControl, error) { +// GetArc implements Context. It returns the access control stored in the given +// key if appropriate, otherwise an error. +func (ctx actionContext) GetArc(key []byte) (arc.AccessControl, error) { value, err := ctx.page.Read(key) if err != nil { - return nil, err + return nil, xerrors.Errorf("couldn't read value: %v", err) } access, err := ctx.arcFactory.FromProto(value) if err != nil { - return nil, err + return nil, xerrors.Errorf("couldn't decode access: %v", err) } return access, nil } -// Read implements consumer.Context. It returns the instance stored at the given -// key, or an error if it does not find it. -func (ctx transactionContext) Read(key []byte) (*Instance, error) { +// Read implements Context. It returns the instance stored at the given key, or +// an error if it does not find it. +func (ctx actionContext) Read(key []byte) (*Instance, error) { entry, err := ctx.page.Read(key) if err != nil { - return nil, xerrors.Errorf("couldn't read the entry: %v", err) + return nil, xerrors.Errorf("couldn't read the value: %v", err) } instance, ok := entry.(*Instance) if !ok { - return nil, xerrors.Errorf("...") + return nil, xerrors.Errorf("invalid message type '%T' != '%T'", + entry, instance) } return instance, nil diff --git a/ledger/byzcoin/actions/contract/context_test.go b/ledger/byzcoin/actions/contract/context_test.go index 03c185693..d54d0f663 100644 --- a/ledger/byzcoin/actions/contract/context_test.go +++ b/ledger/byzcoin/actions/contract/context_test.go @@ -5,17 +5,35 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/any" + "github.com/golang/protobuf/ptypes/empty" "github.com/stretchr/testify/require" ) -func TestTransactionContext_Read(t *testing.T) { - ctx := transactionContext{ +func TestActionContext_GetArc(t *testing.T) { + ctx := actionContext{ + arcFactory: &fakeAccessFactory{access: &fakeAccess{}}, + page: fakePage{ + store: map[string]proto.Message{"a": &empty.Empty{}}, + }, + } + + arc, err := ctx.GetArc([]byte("a")) + require.NoError(t, err) + require.NotNil(t, arc) + + _, err = ctx.GetArc(nil) + require.EqualError(t, err, "couldn't read value: not found") +} + +func TestActionContext_Read(t *testing.T) { + ctx := actionContext{ page: fakePage{ store: map[string]proto.Message{ "a": &Instance{ ContractID: "abc", Value: &any.Any{}, }, + "b": &empty.Empty{}, }, }, } @@ -26,5 +44,9 @@ func TestTransactionContext_Read(t *testing.T) { require.NotNil(t, instance.Value) _, err = ctx.Read(nil) - require.EqualError(t, err, "couldn't read the entry: not found") + require.EqualError(t, err, "couldn't read the value: not found") + + _, err = ctx.Read([]byte("b")) + require.EqualError(t, err, + "invalid message type '*empty.Empty' != '*contract.Instance'") } diff --git a/ledger/byzcoin/actions/contract/messages.pb.go b/ledger/byzcoin/actions/contract/messages.pb.go index 5cbb5ee32..9702c7e30 100644 --- a/ledger/byzcoin/actions/contract/messages.pb.go +++ b/ledger/byzcoin/actions/contract/messages.pb.go @@ -94,7 +94,7 @@ func (m *Instance) GetAccessControl() []byte { type SpawnActionProto struct { ContractID string `protobuf:"bytes,1,opt,name=contractID,proto3" json:"contractID,omitempty"` - Argument []byte `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` + Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -132,7 +132,7 @@ func (m *SpawnActionProto) GetContractID() string { return "" } -func (m *SpawnActionProto) GetArgument() []byte { +func (m *SpawnActionProto) GetArgument() *any.Any { if m != nil { return m.Argument } @@ -141,7 +141,7 @@ func (m *SpawnActionProto) GetArgument() []byte { type InvokeActionProto struct { Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Argument []byte `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` + Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -179,7 +179,7 @@ func (m *InvokeActionProto) GetKey() []byte { return nil } -func (m *InvokeActionProto) GetArgument() []byte { +func (m *InvokeActionProto) GetArgument() *any.Any { if m != nil { return m.Argument } @@ -237,21 +237,21 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 254 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0x51, 0x4b, 0xc3, 0x30, - 0x14, 0x85, 0x89, 0x73, 0x5a, 0xaf, 0x53, 0xb6, 0xe0, 0x43, 0xdc, 0x83, 0x94, 0xa2, 0x50, 0x7c, - 0xe8, 0x40, 0x7f, 0x41, 0x71, 0x2f, 0x7d, 0x11, 0x89, 0xbf, 0x20, 0xcb, 0xae, 0x45, 0xd6, 0xdd, - 0x3b, 0x9a, 0x74, 0xd2, 0x7f, 0xe4, 0xcf, 0x94, 0xa5, 0x54, 0x36, 0x06, 0x7b, 0xcb, 0x3d, 0x1c, - 0xbe, 0x93, 0x73, 0xe0, 0x76, 0x8d, 0xce, 0x99, 0x12, 0x5d, 0xb6, 0xa9, 0xd9, 0xb3, 0x8c, 0x2c, - 0x93, 0xaf, 0x8d, 0xf5, 0xd3, 0xfb, 0x92, 0xb9, 0xac, 0x70, 0x16, 0xf4, 0x45, 0xf3, 0x35, 0x33, - 0xd4, 0x76, 0xa6, 0xe4, 0x57, 0x40, 0x54, 0x90, 0xf3, 0x86, 0x2c, 0xca, 0x31, 0x0c, 0x56, 0xd8, - 0x2a, 0x11, 0x8b, 0x74, 0xa4, 0x77, 0x4f, 0xf9, 0x0c, 0xc3, 0xad, 0xa9, 0x1a, 0x54, 0x67, 0xb1, - 0x48, 0xaf, 0x5f, 0xee, 0xb2, 0x8e, 0x94, 0xf5, 0xa4, 0x2c, 0xa7, 0x56, 0x77, 0x16, 0xf9, 0x00, - 0xd0, 0x27, 0x16, 0x73, 0x35, 0x88, 0x45, 0x7a, 0xa5, 0xf7, 0x14, 0xa9, 0xe0, 0x72, 0x89, 0x15, - 0x7a, 0x5c, 0xaa, 0xf3, 0x58, 0xa4, 0x91, 0xee, 0x4f, 0xf9, 0x08, 0x37, 0xc6, 0x5a, 0x74, 0xee, - 0x6d, 0xe7, 0xe6, 0x4a, 0x0d, 0xc3, 0x0f, 0x0e, 0xc5, 0xe4, 0x1d, 0xc6, 0x9f, 0x1b, 0xf3, 0x43, - 0xb9, 0xf5, 0xdf, 0x4c, 0x1f, 0xa1, 0xe3, 0x61, 0xa6, 0x38, 0xca, 0x9c, 0x42, 0x64, 0xea, 0xb2, - 0x59, 0x23, 0xf9, 0x50, 0x61, 0xa4, 0xff, 0xef, 0x24, 0x87, 0x49, 0x41, 0x5b, 0x5e, 0xe1, 0x3e, - 0xf0, 0x78, 0x82, 0x53, 0x88, 0x27, 0x98, 0xcc, 0x43, 0x87, 0x93, 0x88, 0xc5, 0x45, 0x98, 0xeb, - 0xf5, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xe2, 0x94, 0x99, 0xe1, 0xa2, 0x01, 0x00, 0x00, + // 255 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x4f, 0x4b, 0xc3, 0x40, + 0x10, 0xc5, 0x59, 0x6b, 0x35, 0x8e, 0x7f, 0x68, 0x17, 0x0f, 0xab, 0x07, 0x09, 0x41, 0x21, 0x78, + 0x48, 0x45, 0x3f, 0x41, 0xb1, 0x97, 0xdc, 0x24, 0x1e, 0x3c, 0x6f, 0x37, 0x63, 0x90, 0x6e, 0x67, + 0x4a, 0x76, 0x53, 0xc9, 0x37, 0xf2, 0x63, 0x4a, 0x37, 0x44, 0x5a, 0x0a, 0x05, 0x6f, 0xbb, 0x8f, + 0x37, 0xef, 0x37, 0x6f, 0xe0, 0x6a, 0x89, 0xce, 0xe9, 0x0a, 0x5d, 0xb6, 0xaa, 0xd9, 0xb3, 0x8c, + 0x0c, 0x93, 0xaf, 0xb5, 0xf1, 0xb7, 0x37, 0x15, 0x73, 0x65, 0x71, 0x12, 0xf4, 0x79, 0xf3, 0x39, + 0xd1, 0xd4, 0x76, 0xa6, 0xe4, 0x47, 0x40, 0x94, 0x93, 0xf3, 0x9a, 0x0c, 0xca, 0x11, 0x0c, 0x16, + 0xd8, 0x2a, 0x11, 0x8b, 0xf4, 0xa2, 0xd8, 0x3c, 0xe5, 0x23, 0x0c, 0xd7, 0xda, 0x36, 0xa8, 0x8e, + 0x62, 0x91, 0x9e, 0x3f, 0x5f, 0x67, 0x5d, 0x52, 0xd6, 0x27, 0x65, 0x53, 0x6a, 0x8b, 0xce, 0x22, + 0xef, 0x00, 0x7a, 0x62, 0x3e, 0x53, 0x83, 0x58, 0xa4, 0x67, 0xc5, 0x96, 0x22, 0x15, 0x9c, 0x96, + 0x68, 0xd1, 0x63, 0xa9, 0x8e, 0x63, 0x91, 0x46, 0x45, 0xff, 0x95, 0xf7, 0x70, 0xa9, 0x8d, 0x41, + 0xe7, 0x5e, 0x37, 0x6e, 0xb6, 0x6a, 0x18, 0x36, 0xd8, 0x15, 0x93, 0x12, 0x46, 0xef, 0x2b, 0xfd, + 0x4d, 0x53, 0xe3, 0xbf, 0x98, 0xde, 0x42, 0xc7, 0x5d, 0xa6, 0xd8, 0x63, 0x3e, 0x41, 0xa4, 0xeb, + 0xaa, 0x59, 0x22, 0xf9, 0x83, 0x15, 0xfe, 0x5c, 0xc9, 0x07, 0x8c, 0x73, 0x5a, 0xf3, 0x02, 0xb7, + 0x31, 0xfb, 0x87, 0xf9, 0x7f, 0xf0, 0x03, 0x8c, 0x67, 0xa1, 0xef, 0xc1, 0xe0, 0xf9, 0x49, 0x18, + 0x7f, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x55, 0x4b, 0xd8, 0xdd, 0xce, 0x01, 0x00, 0x00, } diff --git a/ledger/byzcoin/actions/contract/messages.proto b/ledger/byzcoin/actions/contract/messages.proto index 25552bfd1..eb2103a51 100644 --- a/ledger/byzcoin/actions/contract/messages.proto +++ b/ledger/byzcoin/actions/contract/messages.proto @@ -14,12 +14,12 @@ message Instance { message SpawnActionProto { string contractID = 1; - bytes argument = 2; + google.protobuf.Any argument = 2; } message InvokeActionProto { bytes key = 1; - bytes argument = 2; + google.protobuf.Any argument = 2; } message DeleteActionProto { diff --git a/ledger/byzcoin/mod_test.go b/ledger/byzcoin/mod_test.go index a14918c29..88bffa9b2 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -58,10 +58,12 @@ func TestLedger_Basic(t *testing.T) { defer cancel() txs := ledgers[2].Watch(ctx) - txFactory := basic.NewTransactionFactory(bls.NewSigner(), nil) + signer := bls.NewSigner() + txFactory := basic.NewTransactionFactory(signer, nil) // Try to create a DARC. - tx, err := txFactory.New(darc.NewCreate()) + access := makeDarc(t, signer) + tx, err := txFactory.New(darc.NewCreate(access)) require.NoError(t, err) err = actors[1].AddTransaction(tx) @@ -77,10 +79,10 @@ func TestLedger_Basic(t *testing.T) { value, err := ledgers[2].GetValue(tx.GetID()) require.NoError(t, err) - require.IsType(t, (*darc.AccessControlProto)(nil), value) + require.IsType(t, (*darc.AccessProto)(nil), value) // Then update it. - tx, err = txFactory.New(darc.NewUpdate(tx.GetID())) + tx, err = txFactory.New(darc.NewUpdate(tx.GetID(), access)) require.NoError(t, err) err = actors[0].AddTransaction(tx) @@ -157,6 +159,14 @@ func makeLedger(t *testing.T, n int) ([]ledger.Ledger, []ledger.Actor, crypto.Co return ledgers, actors, ca } +func makeDarc(t *testing.T, signer crypto.Signer) darc.Access { + access := darc.NewAccess() + access, err := access.Evolve(darc.UpdateAccessRule, signer.GetPublicKey()) + require.NoError(t, err) + + return access +} + type fakeBlock struct { blockchain.Block } diff --git a/ledger/transactions/basic/mod.go b/ledger/transactions/basic/mod.go index e188f026b..eb02036d5 100644 --- a/ledger/transactions/basic/mod.go +++ b/ledger/transactions/basic/mod.go @@ -27,7 +27,7 @@ import ( // ClientAction is used to create a transaction. type ClientAction interface { encoding.Packable - io.WriterTo + encoding.Fingerprinter } // Context is the context provided to a server transaction when consumed. @@ -101,36 +101,31 @@ func (t transaction) String() string { return fmt.Sprintf("Transaction[%v]", t.identity) } -func (t transaction) WriteTo(w io.Writer) (int64, error) { +func (t transaction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { buffer := make([]byte, 8) binary.LittleEndian.PutUint64(buffer[:], t.nonce) - sum := int64(0) - - n, err := w.Write(buffer) - sum += int64(n) + _, err := w.Write(buffer) if err != nil { - return sum, xerrors.Errorf("couldn't write nonce: %v", err) + return xerrors.Errorf("couldn't write nonce: %v", err) } buffer, err = t.identity.MarshalBinary() if err != nil { - return sum, xerrors.Errorf("couldn't marshal identity: %v", err) + return xerrors.Errorf("couldn't marshal identity: %v", err) } - n, err = w.Write(buffer) - sum += int64(n) + _, err = w.Write(buffer) if err != nil { - return sum, xerrors.Errorf("couldn't write identity: %v", err) + return xerrors.Errorf("couldn't write identity: %v", err) } - k, err := t.action.WriteTo(w) - sum += k + err = t.action.Fingerprint(w, enc) if err != nil { - return sum, xerrors.Errorf("couldn't write action: %v", err) + return xerrors.Errorf("couldn't write action: %v", err) } - return sum, nil + return nil } type serverTransaction struct { @@ -188,7 +183,7 @@ func (f TransactionFactory) New(action ClientAction) (transactions.ClientTransac } h := f.hashFactory.New() - _, err := tx.WriteTo(h) + err := tx.Fingerprint(h, f.encoder) if err != nil { return tx, xerrors.Errorf("couldn't compute hash: %v", err) } @@ -254,7 +249,7 @@ func (f TransactionFactory) fillIdentity(tx *serverTransaction, pb *TransactionP } h := f.hashFactory.New() - _, err = tx.WriteTo(h) + err = tx.Fingerprint(h, f.encoder) if err != nil { return xerrors.Errorf("couldn't compute hash: %v", err) } diff --git a/ledger/transactions/basic/mod_test.go b/ledger/transactions/basic/mod_test.go index 86f37956c..a95a6d84e 100644 --- a/ledger/transactions/basic/mod_test.go +++ b/ledger/transactions/basic/mod_test.go @@ -52,22 +52,22 @@ func TestTransaction_WriteTo(t *testing.T) { } w := new(bytes.Buffer) + encoder := encoding.NewProtoEncoder() - sum, err := tx.WriteTo(w) + err := tx.Fingerprint(w, encoder) require.NoError(t, err) - require.Equal(t, int64(10), sum) require.Equal(t, "0100000000000000dfcc", fmt.Sprintf("%x", w.Bytes())) - _, err = tx.WriteTo(fake.NewBadHash()) + err = tx.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write nonce: fake error") tx.identity = fake.NewBadPublicKey() - _, err = tx.WriteTo(&fake.Hash{}) + err = tx.Fingerprint(&fake.Hash{}, encoder) require.EqualError(t, err, "couldn't marshal identity: fake error") tx.identity = fake.PublicKey{} tx.action = fakeClientAction{err: xerrors.New("oops")} - _, err = tx.WriteTo(&fake.Hash{}) + err = tx.Fingerprint(&fake.Hash{}, encoder) require.EqualError(t, err, "couldn't write action: oops") } @@ -144,9 +144,9 @@ type fakeClientAction struct { err error } -func (a fakeClientAction) WriteTo(w io.Writer) (int64, error) { +func (a fakeClientAction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { w.Write([]byte{0xcc}) - return 1, a.err + return a.err } func (a fakeClientAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { From ed350c1285536bd70c86311698ec1111ef47f061 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Fri, 1 May 2020 15:12:59 +0200 Subject: [PATCH 4/9] Improve unit tests --- internal/testing/fake/mod.go | 26 +++++++++ ledger/transactions/basic/mod.go | 66 ++++++++++++++-------- ledger/transactions/basic/mod_test.go | 81 ++++++++++++++++++++++----- 3 files changed, 136 insertions(+), 37 deletions(-) diff --git a/internal/testing/fake/mod.go b/internal/testing/fake/mod.go index 366a90461..64ee1b2c3 100644 --- a/internal/testing/fake/mod.go +++ b/internal/testing/fake/mod.go @@ -506,6 +506,27 @@ func (f VerifierFactory) FromAuthority(ca crypto.CollectiveAuthority) (crypto.Ve return f.verifier, f.err } +// Counter is a helper to delay errors or actions. It can be nil without panics. +type Counter struct { + Value int +} + +// Done returns true when the counter reached zero. +func (c *Counter) Done() bool { + if c == nil { + return true + } + return c.Value <= 0 +} + +// Decrease decrements the counter. +func (c *Counter) Decrease() { + if c == nil { + return + } + c.Value-- +} + // BadPackEncoder is a fake implementation of encoding.ProtoMarshaler. type BadPackEncoder struct { encoding.ProtoEncoder @@ -519,10 +540,15 @@ func (e BadPackEncoder) Pack(encoding.Packable) (proto.Message, error) { // BadPackAnyEncoder is a fake implementation of encoding.ProtoMarshaler. type BadPackAnyEncoder struct { encoding.ProtoEncoder + Counter *Counter } // PackAny implements encoding.ProtoMarshaler. func (e BadPackAnyEncoder) PackAny(encoding.Packable) (*any.Any, error) { + defer e.Counter.Decrease() + if !e.Counter.Done() { + return &any.Any{}, nil + } return nil, xerrors.New("fake error") } diff --git a/ledger/transactions/basic/mod.go b/ledger/transactions/basic/mod.go index eb02036d5..857797cd4 100644 --- a/ledger/transactions/basic/mod.go +++ b/ledger/transactions/basic/mod.go @@ -24,6 +24,8 @@ import ( "golang.org/x/xerrors" ) +//go:generate protoc -I ./ --go_out=./ ./messages.proto + // ClientAction is used to create a transaction. type ClientAction interface { encoding.Packable @@ -32,7 +34,10 @@ type ClientAction interface { // Context is the context provided to a server transaction when consumed. type Context interface { + // GetID returns the unique identifier of the transaction. GetID() []byte + + // GetIdentity returns the identity who signed the transaction. GetIdentity() arc.Identity } @@ -50,11 +55,9 @@ type ActionFactory interface { FromProto(proto.Message) (ServerAction, error) } -//go:generate protoc -I ./ --go_out=./ ./messages.proto - // transaction is an atomic execution. // -// - implements ledger.transaction +// - implements transactions.ClientTransaction type transaction struct { hash []byte nonce uint64 @@ -63,16 +66,20 @@ type transaction struct { action ClientAction } -// GetID implements ledger.Transaction. It returns the unique identifier of the -// transaction. +// GetID implements transactions.ClientTransaction. It returns the unique +// identifier of the transaction. func (t transaction) GetID() []byte { return t.hash[:] } +// GetIdentity implements basic.Context. It returns the identity who signed the +// transaction. func (t transaction) GetIdentity() arc.Identity { return t.identity } +// Pack implements encoding.Packable. It returns the protobuf message of the +// transaction. func (t transaction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { pb := &TransactionProto{ Nonce: t.nonce, @@ -97,10 +104,8 @@ func (t transaction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { return pb, nil } -func (t transaction) String() string { - return fmt.Sprintf("Transaction[%v]", t.identity) -} - +// Fingerprint implements encoding.Fingerprinter. It serializes the transaction +// into the writer in a deterministic way. func (t transaction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { buffer := make([]byte, 8) binary.LittleEndian.PutUint64(buffer[:], t.nonce) @@ -128,21 +133,33 @@ func (t transaction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error return nil } +// String implements fmt.Stringer. It returns a string representation of the +// transaction. +func (t transaction) String() string { + return fmt.Sprintf("Transaction[%v]", t.identity) +} + +// serverTransaction is an extension of the transaction that can be consumed. +// +// - implements transactions.ServerTransaction type serverTransaction struct { transaction } +// Consume implements transactions.ServerTransaction. It first insures the nonce +// is correct and writes the new one into the page. It then consumes the action +// of the transaction. func (t serverTransaction) Consume(page inventory.WritablePage) error { // TODO: consume nonce action, ok := t.action.(ServerAction) if !ok { - return xerrors.Errorf("action must implement '%T'", action) + return xerrors.Errorf("action must implement 'basic.ServerAction'") } err := action.Consume(t, page) if err != nil { - return err + return xerrors.Errorf("couldn't consume action: %v", err) } return nil @@ -161,8 +178,6 @@ type TransactionFactory struct { } // NewTransactionFactory returns a new instance of the transaction factory. -// -// - implements ledger.TransactionFactory func NewTransactionFactory(signer crypto.Signer, f ActionFactory) TransactionFactory { return TransactionFactory{ signer: signer, @@ -174,7 +189,8 @@ func NewTransactionFactory(signer crypto.Signer, f ActionFactory) TransactionFac } } -// New returns a new transaction. +// New returns a new transaction from the given action. The transaction will be +// signed. func (f TransactionFactory) New(action ClientAction) (transactions.ClientTransaction, error) { tx := transaction{ nonce: 0, // TODO: monotonic nonce @@ -216,18 +232,18 @@ func (f TransactionFactory) FromProto(in proto.Message) (transactions.ServerTran return nil, xerrors.Errorf("invalid transaction type '%T'", in) } + action, err := f.actionFactory.FromProto(pb.GetAction()) + if err != nil { + return nil, xerrors.Errorf("couldn't decode action: %v", err) + } + tx := serverTransaction{ transaction: transaction{ - nonce: pb.GetNonce(), + nonce: pb.GetNonce(), + action: action, }, } - var err error - tx.action, err = f.actionFactory.FromProto(pb.GetAction()) - if err != nil { - return nil, xerrors.Errorf("couldn't decode action: %v", err) - } - err = f.fillIdentity(&tx, pb) if err != nil { return nil, err @@ -237,17 +253,19 @@ func (f TransactionFactory) FromProto(in proto.Message) (transactions.ServerTran } func (f TransactionFactory) fillIdentity(tx *serverTransaction, pb *TransactionProto) error { - var err error - tx.identity, err = f.publicKeyFactory.FromProto(pb.GetIdentity()) + identity, err := f.publicKeyFactory.FromProto(pb.GetIdentity()) if err != nil { return xerrors.Errorf("couldn't decode public key: %v", err) } - tx.signature, err = f.signatureFactory.FromProto(pb.GetSignature()) + signature, err := f.signatureFactory.FromProto(pb.GetSignature()) if err != nil { return xerrors.Errorf("couldn't decode signature: %v", err) } + tx.identity = identity + tx.signature = signature + h := f.hashFactory.New() err = tx.Fingerprint(h, f.encoder) if err != nil { diff --git a/ledger/transactions/basic/mod_test.go b/ledger/transactions/basic/mod_test.go index a95a6d84e..33341eb2a 100644 --- a/ledger/transactions/basic/mod_test.go +++ b/ledger/transactions/basic/mod_test.go @@ -2,7 +2,6 @@ package basic import ( "bytes" - fmt "fmt" "io" "testing" "testing/quick" @@ -13,11 +12,22 @@ import ( "github.com/stretchr/testify/require" "go.dedis.ch/fabric/crypto/bls" "go.dedis.ch/fabric/encoding" + internal "go.dedis.ch/fabric/internal/testing" "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/ledger/inventory" "golang.org/x/xerrors" ) +func TestMessages(t *testing.T) { + messages := []proto.Message{ + &TransactionProto{}, + } + + for _, m := range messages { + internal.CoverProtoMessage(t, m) + } +} + func TestTransaction_GetID(t *testing.T) { f := func(buffer []byte) bool { tx := transaction{hash: buffer} @@ -29,6 +39,12 @@ func TestTransaction_GetID(t *testing.T) { require.NoError(t, err) } +func TestTransaction_GetIdentity(t *testing.T) { + tx := transaction{identity: fake.PublicKey{}} + + require.NotNil(t, tx.GetIdentity()) +} + func TestTransaction_Pack(t *testing.T) { tx := transaction{ identity: fake.PublicKey{}, @@ -42,35 +58,66 @@ func TestTransaction_Pack(t *testing.T) { _, err = tx.Pack(fake.BadPackAnyEncoder{}) require.EqualError(t, err, "couldn't pack identity: fake error") + + _, err = tx.Pack(fake.BadPackAnyEncoder{Counter: &fake.Counter{Value: 1}}) + require.EqualError(t, err, "couldn't pack signature: fake error") + + _, err = tx.Pack(fake.BadPackAnyEncoder{Counter: &fake.Counter{Value: 2}}) + require.EqualError(t, err, "couldn't pack action: fake error") } -func TestTransaction_WriteTo(t *testing.T) { +func TestTransaction_Fingerprint(t *testing.T) { tx := transaction{ - nonce: 1, + nonce: 0x0102030405060708, identity: fake.PublicKey{}, action: fakeClientAction{}, } - w := new(bytes.Buffer) - encoder := encoding.NewProtoEncoder() + buffer := new(bytes.Buffer) - err := tx.Fingerprint(w, encoder) + err := tx.Fingerprint(buffer, nil) require.NoError(t, err) - require.Equal(t, "0100000000000000dfcc", fmt.Sprintf("%x", w.Bytes())) + require.Equal(t, "\x08\x07\x06\x05\x04\x03\x02\x01\xdf\xcc", buffer.String()) - err = tx.Fingerprint(fake.NewBadHash(), encoder) + err = tx.Fingerprint(fake.NewBadHash(), nil) require.EqualError(t, err, "couldn't write nonce: fake error") + err = tx.Fingerprint(fake.NewBadHashWithDelay(1), nil) + require.EqualError(t, err, "couldn't write identity: fake error") + tx.identity = fake.NewBadPublicKey() - err = tx.Fingerprint(&fake.Hash{}, encoder) + err = tx.Fingerprint(buffer, nil) require.EqualError(t, err, "couldn't marshal identity: fake error") tx.identity = fake.PublicKey{} tx.action = fakeClientAction{err: xerrors.New("oops")} - err = tx.Fingerprint(&fake.Hash{}, encoder) + err = tx.Fingerprint(buffer, nil) require.EqualError(t, err, "couldn't write action: oops") } +func TestTransaction_String(t *testing.T) { + tx := transaction{identity: fake.PublicKey{}} + + require.Equal(t, "Transaction[fake.PublicKey]", tx.String()) +} + +func TestServerTransaction_Consume(t *testing.T) { + tx := serverTransaction{ + transaction: transaction{action: fakeSrvAction{}}, + } + + err := tx.Consume(nil) + require.NoError(t, err) + + tx.transaction.action = fakeClientAction{} + err = tx.Consume(nil) + require.EqualError(t, err, "action must implement 'basic.ServerAction'") + + tx.transaction.action = fakeSrvAction{err: xerrors.New("oops")} + err = tx.Consume(nil) + require.EqualError(t, err, "couldn't consume action: oops") +} + func TestTransactionFactory_New(t *testing.T) { factory := NewTransactionFactory(bls.NewSigner(), nil) @@ -118,6 +165,11 @@ func TestTransactionFactory_FromProto(t *testing.T) { _, err = factory.FromProto(txany) require.EqualError(t, err, "couldn't unmarshal input: fake error") + factory.actionFactory = fakeActionFactory{err: xerrors.New("oops")} + _, err = factory.FromProto(txpb) + require.EqualError(t, err, "couldn't decode action: oops") + + factory.actionFactory = fakeActionFactory{} factory.publicKeyFactory = fake.NewBadPublicKeyFactory() _, err = factory.FromProto(txpb) require.EqualError(t, err, "couldn't decode public key: fake error") @@ -155,14 +207,17 @@ func (a fakeClientAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { type fakeSrvAction struct { fakeClientAction + err error } func (a fakeSrvAction) Consume(Context, inventory.WritablePage) error { - return nil + return a.err } -type fakeActionFactory struct{} +type fakeActionFactory struct { + err error +} func (f fakeActionFactory) FromProto(proto.Message) (ServerAction, error) { - return fakeSrvAction{}, nil + return fakeSrvAction{}, f.err } From 200e1a0a05a73ba00cff6bf739e72c0256995489 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Fri, 1 May 2020 16:13:44 +0200 Subject: [PATCH 5/9] DARC: implement access control when updating --- go.sum | 5 ---- ledger/arc/darc/action.go | 30 +++++++++++++++++++++--- ledger/arc/darc/action_test.go | 42 +++++++++++++++++++++++++++++++--- ledger/arc/darc/expr_test.go | 5 ++-- ledger/arc/darc/mod.go | 7 +++++- ledger/arc/darc/mod_test.go | 4 ++++ ledger/byzcoin/mod_test.go | 18 --------------- ledger/byzcoin/txproc_test.go | 11 +-------- 8 files changed, 79 insertions(+), 43 deletions(-) diff --git a/go.sum b/go.sum index 255c180bd..ad45df79a 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -20,8 +18,6 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -79,7 +75,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/ledger/arc/darc/action.go b/ledger/arc/darc/action.go index 4d99b7d38..46f3c6a3e 100644 --- a/ledger/arc/darc/action.go +++ b/ledger/arc/darc/action.go @@ -23,6 +23,8 @@ type darcAction struct { access Access } +// TODO: client factory + // NewCreate returns a new action to create a DARC. func NewCreate(access Access) basic.ClientAction { return darcAction{access: access} @@ -67,7 +69,8 @@ func (act darcAction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) erro // serverAction is the server-side action for DARCs. type serverAction struct { - encoder encoding.ProtoMarshaler + encoder encoding.ProtoMarshaler + darcFactory arc.AccessControlFactory darcAction } @@ -79,13 +82,33 @@ func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) return xerrors.Errorf("couldn't pack access: %v", err) } + err = act.access.Match(UpdateAccessRule, ctx.GetIdentity()) + if err != nil { + // This prevents to update the arc so that no one is allowed to update + // it in the future. + return xerrors.New("transaction identity should be allowed to update") + } + key := act.key if key == nil { // No key defined means a creation request then we use the transaction // ID as a unique key for the DARC. key = ctx.GetID() } else { - // TODO: access control + value, err := page.Read(key) + if err != nil { + return xerrors.Errorf("couldn't read value: %v", err) + } + + access, err := act.darcFactory.FromProto(value) + if err != nil { + return xerrors.Errorf("couldn't decode access: %v", err) + } + + err = access.Match(UpdateAccessRule, ctx.GetIdentity()) + if err != nil { + return xerrors.Errorf("no access: %v", err) + } } err = page.Write(key, accesspb) @@ -126,11 +149,12 @@ func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { } servAccess := serverAction{ + encoder: f.encoder, + darcFactory: f.darcFactory, darcAction: darcAction{ key: pb.GetKey(), access: access.(Access), }, - encoder: f.encoder, } return servAccess, nil diff --git a/ledger/arc/darc/action_test.go b/ledger/arc/darc/action_test.go index d67bbbc5a..7b7707c7c 100644 --- a/ledger/arc/darc/action_test.go +++ b/ledger/arc/darc/action_test.go @@ -54,13 +54,17 @@ func TestAction_Fingerprint(t *testing.T) { } func TestServerAction_Consume(t *testing.T) { + access, err := NewAccess().Evolve(UpdateAccessRule, fakeIdentity{buffer: []byte("doggy")}) + require.NoError(t, err) + action := serverAction{ - encoder: encoding.NewProtoEncoder(), - darcAction: darcAction{key: []byte{0x01}}, + encoder: encoding.NewProtoEncoder(), + darcFactory: NewFactory(), + darcAction: darcAction{key: []byte{0x01}, access: access}, } call := &fake.Call{} - err := action.Consume(fakeContext{}, fakePage{call: call}) + err = action.Consume(fakeContext{}, fakePage{call: call}) require.NoError(t, err) require.Equal(t, 1, call.Len()) // Key is provided so it's an update. @@ -80,6 +84,20 @@ func TestServerAction_Consume(t *testing.T) { action.encoder = encoding.NewProtoEncoder() err = action.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) require.EqualError(t, err, "couldn't write access: oops") + + action.darcAction.key = []byte{0x01} + err = action.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) + require.EqualError(t, err, "couldn't read value: oops") + + action.darcFactory = badArcFactory{} + err = action.Consume(fakeContext{}, fakePage{}) + require.EqualError(t, err, "couldn't decode access: oops") + + action.darcFactory = NewFactory() + action.access.rules[UpdateAccessRule].matches["cat"] = struct{}{} + err = action.Consume(fakeContext{identity: []byte("cat")}, fakePage{}) + require.EqualError(t, err, + "no access: couldn't match 'darc:update': couldn't match identity 'cat'") } func TestActionFactory_FromProto(t *testing.T) { @@ -101,20 +119,38 @@ func TestActionFactory_FromProto(t *testing.T) { // ----------------------------------------------------------------------------- // Utility functions +var testAccess = &AccessProto{ + Rules: map[string]*Expression{ + UpdateAccessRule: {Matches: []string{"doggy"}}, + }, +} + type fakeContext struct { basic.Context + identity []byte } func (ctx fakeContext) GetID() []byte { return []byte{0x34} } +func (ctx fakeContext) GetIdentity() arc.Identity { + if ctx.identity != nil { + return fakeIdentity{buffer: ctx.identity} + } + return fakeIdentity{buffer: []byte("doggy")} +} + type fakePage struct { inventory.WritablePage call *fake.Call err error } +func (page fakePage) Read(key []byte) (proto.Message, error) { + return testAccess, page.err +} + func (page fakePage) Write(key []byte, value proto.Message) error { page.call.Add(key, value) diff --git a/ledger/arc/darc/expr_test.go b/ledger/arc/darc/expr_test.go index a7a4bdc00..3a2328d15 100644 --- a/ledger/arc/darc/expr_test.go +++ b/ledger/arc/darc/expr_test.go @@ -2,7 +2,6 @@ package darc import ( "bytes" - fmt "fmt" "testing" "github.com/stretchr/testify/require" @@ -48,7 +47,7 @@ func TestExpression_Match(t *testing.T) { require.NoError(t, err) err = expr.Match([]arc.Identity{fakeIdentity{buffer: []byte{0xcc}}}) - require.EqualError(t, err, "couldn't match identity '0xcc'") + require.EqualError(t, err, "couldn't match identity '\xcc'") err = expr.Match([]arc.Identity{fakeIdentity{err: xerrors.New("oops")}}) require.EqualError(t, err, "couldn't marshal identity: oops") @@ -98,5 +97,5 @@ func (i fakeIdentity) MarshalText() ([]byte, error) { } func (i fakeIdentity) String() string { - return fmt.Sprintf("%#x", i.buffer) + return string(i.buffer) } diff --git a/ledger/arc/darc/mod.go b/ledger/arc/darc/mod.go index 66d820ec0..60c906a59 100644 --- a/ledger/arc/darc/mod.go +++ b/ledger/arc/darc/mod.go @@ -61,7 +61,12 @@ func (ac Access) Match(rule string, targets ...arc.Identity) error { return xerrors.Errorf("rule '%s' not found", rule) } - return expr.Match(targets) + err := expr.Match(targets) + if err != nil { + return xerrors.Errorf("couldn't match '%s': %v", rule, err) + } + + return nil } // Fingerprint implements encoding.Fingerprinter. It serializes the access to diff --git a/ledger/arc/darc/mod_test.go b/ledger/arc/darc/mod_test.go index 1bc57f14d..4e1918512 100644 --- a/ledger/arc/darc/mod_test.go +++ b/ledger/arc/darc/mod_test.go @@ -68,6 +68,10 @@ func TestAccess_Match(t *testing.T) { err = access.Match("unknown", idents...) require.EqualError(t, err, "rule 'unknown' not found") + + err = access.Match("fake", fakeIdentity{buffer: []byte{0xcc}}) + require.EqualError(t, err, + "couldn't match 'fake': couldn't match identity '\xcc'") } func TestAccess_Fingerprint(t *testing.T) { diff --git a/ledger/byzcoin/mod_test.go b/ledger/byzcoin/mod_test.go index 88bffa9b2..4aff1e627 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -8,7 +8,6 @@ import ( proto "github.com/golang/protobuf/proto" "github.com/stretchr/testify/require" - "go.dedis.ch/fabric/blockchain" "go.dedis.ch/fabric/crypto" "go.dedis.ch/fabric/crypto/bls" "go.dedis.ch/fabric/encoding" @@ -166,20 +165,3 @@ func makeDarc(t *testing.T, signer crypto.Signer) darc.Access { return access } - -type fakeBlock struct { - blockchain.Block -} - -func (b fakeBlock) GetIndex() uint64 { - return 0 -} - -type fakeBlockchain struct { - blockchain.Blockchain - err error -} - -func (bc fakeBlockchain) GetBlock() (blockchain.Block, error) { - return fakeBlock{}, bc.err -} diff --git a/ledger/byzcoin/txproc_test.go b/ledger/byzcoin/txproc_test.go index 83522a146..7eba3e4f6 100644 --- a/ledger/byzcoin/txproc_test.go +++ b/ledger/byzcoin/txproc_test.go @@ -7,7 +7,6 @@ import ( any "github.com/golang/protobuf/ptypes/any" "github.com/stretchr/testify/require" "go.dedis.ch/fabric/ledger/inventory" - "go.dedis.ch/fabric/ledger/transactions" "golang.org/x/xerrors" ) @@ -59,6 +58,7 @@ func TestTxProcessor_Process(t *testing.T) { proc.inventory = fakeInventory{page: &fakePage{}} page, err = proc.process(payload) require.NoError(t, err) + require.NotNil(t, page) } func TestTxProcessor_Commit(t *testing.T) { @@ -146,12 +146,3 @@ func (inv fakeInventory) Stage(f func(inventory.WritablePage) error) (inventory. func (inv fakeInventory) Commit([]byte) error { return inv.err } - -type fakeTxFactory struct { - transactions.TransactionFactory - err error -} - -func (f fakeTxFactory) FromProto(proto.Message) (transactions.ServerTransaction, error) { - return nil, f.err -} From 232c74b6b39d62003b5794a9a7b7ad100a499494 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Fri, 1 May 2020 17:04:55 +0200 Subject: [PATCH 6/9] Move roster related code to a package --- consensus/viewchange/mod.go | 9 ++ internal/testing/fake/mod.go | 1 + ledger/byzcoin/{actions/mod.go => action.go} | 10 +- ledger/byzcoin/action_test.go | 1 + .../byzcoin/{actions => }/contract/action.go | 0 .../{actions => }/contract/action_test.go | 0 .../byzcoin/{actions => }/contract/context.go | 0 .../{actions => }/contract/context_test.go | 0 .../{actions => }/contract/messages.pb.go | 0 .../{actions => }/contract/messages.proto | 0 ledger/byzcoin/{actions => }/contract/mod.go | 1 + .../{actions => }/contract/mod_test.go | 0 ledger/byzcoin/messages.pb.go | 92 +++++-------------- ledger/byzcoin/messages.proto | 14 +-- ledger/byzcoin/mod.go | 20 ++-- ledger/byzcoin/mod_test.go | 11 +-- ledger/byzcoin/roster/action.go | 1 + ledger/byzcoin/roster/messages.pb.go | 91 ++++++++++++++++++ ledger/byzcoin/roster/messages.proto | 11 +++ ledger/byzcoin/{roster.go => roster/mod.go} | 11 ++- .../{roster_test.go => roster/mod_test.go} | 22 ++++- 21 files changed, 194 insertions(+), 101 deletions(-) rename ledger/byzcoin/{actions/mod.go => action.go} (76%) create mode 100644 ledger/byzcoin/action_test.go rename ledger/byzcoin/{actions => }/contract/action.go (100%) rename ledger/byzcoin/{actions => }/contract/action_test.go (100%) rename ledger/byzcoin/{actions => }/contract/context.go (100%) rename ledger/byzcoin/{actions => }/contract/context_test.go (100%) rename ledger/byzcoin/{actions => }/contract/messages.pb.go (100%) rename ledger/byzcoin/{actions => }/contract/messages.proto (100%) rename ledger/byzcoin/{actions => }/contract/mod.go (94%) rename ledger/byzcoin/{actions => }/contract/mod_test.go (100%) create mode 100644 ledger/byzcoin/roster/action.go create mode 100644 ledger/byzcoin/roster/messages.pb.go create mode 100644 ledger/byzcoin/roster/messages.proto rename ledger/byzcoin/{roster.go => roster/mod.go} (94%) rename ledger/byzcoin/{roster_test.go => roster/mod_test.go} (89%) diff --git a/consensus/viewchange/mod.go b/consensus/viewchange/mod.go index 159e634cc..f9abb20b6 100644 --- a/consensus/viewchange/mod.go +++ b/consensus/viewchange/mod.go @@ -1,8 +1,10 @@ package viewchange import ( + "github.com/golang/protobuf/proto" "go.dedis.ch/fabric/consensus" "go.dedis.ch/fabric/crypto" + "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/mino" ) @@ -35,6 +37,7 @@ type ChangeSet struct { // EvolvableAuthority is an extension of the collective authority to provide // primitives to append new players to it. type EvolvableAuthority interface { + encoding.Packable crypto.CollectiveAuthority // Apply must apply the change set to the collective authority. It should @@ -42,6 +45,12 @@ type EvolvableAuthority interface { Apply(ChangeSet) EvolvableAuthority } +type AuthorityFactory interface { + New(crypto.CollectiveAuthority) EvolvableAuthority + + FromProto(proto.Message) (EvolvableAuthority, error) +} + // Governance is an interface to get information about the collective authority // of a proposal. type Governance interface { diff --git a/internal/testing/fake/mod.go b/internal/testing/fake/mod.go index 64ee1b2c3..9ff7e3b3f 100644 --- a/internal/testing/fake/mod.go +++ b/internal/testing/fake/mod.go @@ -135,6 +135,7 @@ func (i *PublicKeyIterator) GetNext() crypto.PublicKey { // CollectiveAuthority is a fake implementation of the cosi.CollectiveAuthority // interface. type CollectiveAuthority struct { + encoding.Packable crypto.CollectiveAuthority addrs []mino.Address signers []crypto.AggregateSigner diff --git a/ledger/byzcoin/actions/mod.go b/ledger/byzcoin/action.go similarity index 76% rename from ledger/byzcoin/actions/mod.go rename to ledger/byzcoin/action.go index 7f4e56b73..293712333 100644 --- a/ledger/byzcoin/actions/mod.go +++ b/ledger/byzcoin/action.go @@ -1,9 +1,9 @@ -package actions +package byzcoin import ( "reflect" - "github.com/golang/protobuf/proto" + proto "github.com/golang/protobuf/proto" any "github.com/golang/protobuf/ptypes/any" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger/arc/darc" @@ -11,6 +11,9 @@ import ( "golang.org/x/xerrors" ) +// ActionFactory is an action factory that can process several types of actions. +// +// - implements basic.ActionFactory type ActionFactory struct { encoder encoding.ProtoMarshaler registry map[reflect.Type]basic.ActionFactory @@ -28,11 +31,14 @@ func NewActionFactory() basic.ActionFactory { return f } +// Register registers the factory for the protobuf message. func (f ActionFactory) Register(pb proto.Message, factory basic.ActionFactory) { key := reflect.TypeOf(pb) f.registry[key] = factory } +// FromProto implements basic.ActionFactory. It returns the server action for +// the protobuf message if appropriate, otherwise an error. func (f ActionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { inAny, ok := in.(*any.Any) if ok { diff --git a/ledger/byzcoin/action_test.go b/ledger/byzcoin/action_test.go new file mode 100644 index 000000000..f61ee0488 --- /dev/null +++ b/ledger/byzcoin/action_test.go @@ -0,0 +1 @@ +package byzcoin diff --git a/ledger/byzcoin/actions/contract/action.go b/ledger/byzcoin/contract/action.go similarity index 100% rename from ledger/byzcoin/actions/contract/action.go rename to ledger/byzcoin/contract/action.go diff --git a/ledger/byzcoin/actions/contract/action_test.go b/ledger/byzcoin/contract/action_test.go similarity index 100% rename from ledger/byzcoin/actions/contract/action_test.go rename to ledger/byzcoin/contract/action_test.go diff --git a/ledger/byzcoin/actions/contract/context.go b/ledger/byzcoin/contract/context.go similarity index 100% rename from ledger/byzcoin/actions/contract/context.go rename to ledger/byzcoin/contract/context.go diff --git a/ledger/byzcoin/actions/contract/context_test.go b/ledger/byzcoin/contract/context_test.go similarity index 100% rename from ledger/byzcoin/actions/contract/context_test.go rename to ledger/byzcoin/contract/context_test.go diff --git a/ledger/byzcoin/actions/contract/messages.pb.go b/ledger/byzcoin/contract/messages.pb.go similarity index 100% rename from ledger/byzcoin/actions/contract/messages.pb.go rename to ledger/byzcoin/contract/messages.pb.go diff --git a/ledger/byzcoin/actions/contract/messages.proto b/ledger/byzcoin/contract/messages.proto similarity index 100% rename from ledger/byzcoin/actions/contract/messages.proto rename to ledger/byzcoin/contract/messages.proto diff --git a/ledger/byzcoin/actions/contract/mod.go b/ledger/byzcoin/contract/mod.go similarity index 94% rename from ledger/byzcoin/actions/contract/mod.go rename to ledger/byzcoin/contract/mod.go index 0939255b7..595393454 100644 --- a/ledger/byzcoin/actions/contract/mod.go +++ b/ledger/byzcoin/contract/mod.go @@ -1,3 +1,4 @@ +// Package contract is for static smart contracts. package contract import ( diff --git a/ledger/byzcoin/actions/contract/mod_test.go b/ledger/byzcoin/contract/mod_test.go similarity index 100% rename from ledger/byzcoin/actions/contract/mod_test.go rename to ledger/byzcoin/contract/mod_test.go diff --git a/ledger/byzcoin/messages.pb.go b/ledger/byzcoin/messages.pb.go index 1d6ba5684..86850fc07 100644 --- a/ledger/byzcoin/messages.pb.go +++ b/ledger/byzcoin/messages.pb.go @@ -7,6 +7,7 @@ import ( fmt "fmt" proto "github.com/golang/protobuf/proto" any "github.com/golang/protobuf/ptypes/any" + roster "go.dedis.ch/fabric/ledger/byzcoin/roster" math "math" ) @@ -21,55 +22,13 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package -type Roster struct { - Addresses [][]byte `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"` - PublicKeys []*any.Any `protobuf:"bytes,2,rep,name=publicKeys,proto3" json:"publicKeys,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Roster) Reset() { *m = Roster{} } -func (m *Roster) String() string { return proto.CompactTextString(m) } -func (*Roster) ProtoMessage() {} -func (*Roster) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{0} -} - -func (m *Roster) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Roster.Unmarshal(m, b) -} -func (m *Roster) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Roster.Marshal(b, m, deterministic) -} -func (m *Roster) XXX_Merge(src proto.Message) { - xxx_messageInfo_Roster.Merge(m, src) -} -func (m *Roster) XXX_Size() int { - return xxx_messageInfo_Roster.Size(m) -} -func (m *Roster) XXX_DiscardUnknown() { - xxx_messageInfo_Roster.DiscardUnknown(m) -} - -var xxx_messageInfo_Roster proto.InternalMessageInfo - -func (m *Roster) GetAddresses() [][]byte { - if m != nil { - return m.Addresses - } - return nil -} - -func (m *Roster) GetPublicKeys() []*any.Any { - if m != nil { - return m.PublicKeys - } - return nil -} - +// GenesisPayload is the payload of the very first block. type GenesisPayload struct { - Roster *Roster `protobuf:"bytes,1,opt,name=roster,proto3" json:"roster,omitempty"` + // Roster is the initial roster for the chain. It can then evolves through + // transactions. + Roster *roster.Roster `protobuf:"bytes,1,opt,name=roster,proto3" json:"roster,omitempty"` + // Footprint is an integrity check of the final state of the inventory after + // applying the genesis payload. Footprint []byte `protobuf:"bytes,2,opt,name=footprint,proto3" json:"footprint,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -80,7 +39,7 @@ func (m *GenesisPayload) Reset() { *m = GenesisPayload{} } func (m *GenesisPayload) String() string { return proto.CompactTextString(m) } func (*GenesisPayload) ProtoMessage() {} func (*GenesisPayload) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{1} + return fileDescriptor_4dc296cbfe5ffcd5, []int{0} } func (m *GenesisPayload) XXX_Unmarshal(b []byte) error { @@ -101,7 +60,7 @@ func (m *GenesisPayload) XXX_DiscardUnknown() { var xxx_messageInfo_GenesisPayload proto.InternalMessageInfo -func (m *GenesisPayload) GetRoster() *Roster { +func (m *GenesisPayload) GetRoster() *roster.Roster { if m != nil { return m.Roster } @@ -131,7 +90,7 @@ func (m *BlockPayload) Reset() { *m = BlockPayload{} } func (m *BlockPayload) String() string { return proto.CompactTextString(m) } func (*BlockPayload) ProtoMessage() {} func (*BlockPayload) Descriptor() ([]byte, []int) { - return fileDescriptor_4dc296cbfe5ffcd5, []int{2} + return fileDescriptor_4dc296cbfe5ffcd5, []int{1} } func (m *BlockPayload) XXX_Unmarshal(b []byte) error { @@ -167,7 +126,6 @@ func (m *BlockPayload) GetFootprint() []byte { } func init() { - proto.RegisterType((*Roster)(nil), "byzcoin.Roster") proto.RegisterType((*GenesisPayload)(nil), "byzcoin.GenesisPayload") proto.RegisterType((*BlockPayload)(nil), "byzcoin.BlockPayload") } @@ -177,20 +135,18 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 232 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x8e, 0xbf, 0x4f, 0xc3, 0x30, - 0x10, 0x85, 0x95, 0x56, 0x0a, 0xe2, 0x1a, 0x15, 0x29, 0x62, 0x08, 0x88, 0x21, 0xca, 0x42, 0x26, - 0x57, 0x2a, 0x0c, 0xac, 0xb0, 0x30, 0xb0, 0xa0, 0x2c, 0x2c, 0x2c, 0xb6, 0x73, 0x89, 0x2c, 0x8c, - 0x2f, 0xf2, 0xb9, 0x83, 0xf9, 0xeb, 0x51, 0xe3, 0x96, 0x1f, 0x03, 0xac, 0xef, 0xde, 0x7d, 0xef, - 0x83, 0xf5, 0x3b, 0x32, 0xcb, 0x11, 0x59, 0x4c, 0x9e, 0x02, 0x95, 0x27, 0x2a, 0x7e, 0x68, 0x32, - 0xee, 0xf2, 0x62, 0x24, 0x1a, 0x2d, 0x6e, 0xe6, 0x58, 0xed, 0x86, 0x8d, 0x74, 0x31, 0x75, 0x9a, - 0x57, 0xc8, 0x3b, 0xe2, 0x80, 0xbe, 0xbc, 0x82, 0x53, 0xd9, 0xf7, 0x1e, 0x99, 0x91, 0xab, 0xac, - 0x5e, 0xb6, 0x45, 0xf7, 0x1d, 0x94, 0xb7, 0x00, 0xd3, 0x4e, 0x59, 0xa3, 0x9f, 0x30, 0x72, 0xb5, - 0xa8, 0x97, 0xed, 0x6a, 0x7b, 0x2e, 0x12, 0x57, 0x1c, 0xb9, 0xe2, 0xde, 0xc5, 0xee, 0x47, 0xaf, - 0x79, 0x81, 0xf5, 0x23, 0x3a, 0x64, 0xc3, 0xcf, 0x32, 0x5a, 0x92, 0x7d, 0x79, 0x0d, 0xb9, 0x9f, - 0xf7, 0xaa, 0xac, 0xce, 0xda, 0xd5, 0xf6, 0x4c, 0x1c, 0x24, 0x45, 0xd2, 0xe8, 0x0e, 0xe7, 0xbd, - 0xce, 0x40, 0x14, 0x26, 0x6f, 0x5c, 0xa8, 0x16, 0x75, 0xb6, 0xd7, 0xf9, 0x0a, 0x9a, 0x01, 0x8a, - 0x07, 0x4b, 0xfa, 0xed, 0x88, 0xbd, 0x83, 0x22, 0x78, 0xe9, 0x58, 0xea, 0x60, 0xc8, 0x25, 0xff, - 0xbf, 0x04, 0x7f, 0x35, 0xff, 0xdf, 0x51, 0xf9, 0xfc, 0x79, 0xf3, 0x19, 0x00, 0x00, 0xff, 0xff, - 0xa9, 0xb1, 0x7d, 0x29, 0x5b, 0x01, 0x00, 0x00, + // 207 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x8e, 0xb1, 0x4b, 0xc5, 0x30, + 0x10, 0xc6, 0x89, 0xc2, 0x13, 0xf3, 0x4a, 0x87, 0xe2, 0x50, 0x1f, 0x0e, 0xe5, 0x21, 0xd2, 0x29, + 0x81, 0xba, 0xb8, 0xea, 0xe2, 0x2a, 0x19, 0xdc, 0xd3, 0xf6, 0x1a, 0x82, 0x31, 0x57, 0x72, 0x71, + 0x88, 0x7f, 0xbd, 0xd0, 0xb4, 0x48, 0x97, 0x37, 0x1d, 0xf7, 0xdd, 0xef, 0xbe, 0xef, 0xe3, 0xe5, + 0x37, 0x10, 0x69, 0x03, 0x24, 0xe6, 0x80, 0x11, 0xab, 0x9b, 0x3e, 0xfd, 0x0e, 0x68, 0xfd, 0xe9, + 0xde, 0x20, 0x1a, 0x07, 0x72, 0x91, 0xfb, 0x9f, 0x49, 0x6a, 0x9f, 0x32, 0x73, 0x7a, 0x74, 0x30, + 0x1a, 0x08, 0x72, 0x45, 0x65, 0x40, 0x8a, 0x10, 0xe4, 0xde, 0xe9, 0xfc, 0xc9, 0xcb, 0x77, 0xf0, + 0x40, 0x96, 0x3e, 0x74, 0x72, 0xa8, 0xc7, 0xea, 0x89, 0x1f, 0x32, 0x5a, 0xb3, 0x86, 0xb5, 0xc7, + 0xae, 0x14, 0x79, 0x15, 0x6a, 0x19, 0x6a, 0xbd, 0x56, 0x0f, 0xfc, 0x76, 0x42, 0x8c, 0x73, 0xb0, + 0x3e, 0xd6, 0x57, 0x0d, 0x6b, 0x0b, 0xf5, 0x2f, 0x9c, 0x27, 0x5e, 0xbc, 0x39, 0x1c, 0xbe, 0x36, + 0xd7, 0x17, 0x5e, 0xc4, 0xa0, 0x3d, 0xe9, 0x21, 0x5a, 0xf4, 0x54, 0xb3, 0xe6, 0xba, 0x3d, 0x76, + 0x77, 0x22, 0xf7, 0x17, 0x5b, 0x7f, 0xf1, 0xea, 0x93, 0xda, 0x91, 0x97, 0x73, 0xfa, 0xc3, 0xf2, + 0xf9, 0xfc, 0x17, 0x00, 0x00, 0xff, 0xff, 0x09, 0x8a, 0xcf, 0xe0, 0x22, 0x01, 0x00, 0x00, } diff --git a/ledger/byzcoin/messages.proto b/ledger/byzcoin/messages.proto index 86084f601..cacaf7361 100644 --- a/ledger/byzcoin/messages.proto +++ b/ledger/byzcoin/messages.proto @@ -3,14 +3,16 @@ syntax = "proto3"; package byzcoin; import "google/protobuf/any.proto"; +import "ledger/byzcoin/roster/messages.proto"; -message Roster { - repeated bytes addresses = 1; - repeated google.protobuf.Any publicKeys = 2; -} - +// GenesisPayload is the payload of the very first block. message GenesisPayload { - Roster roster = 1; + // Roster is the initial roster for the chain. It can then evolves through + // transactions. + roster.Roster roster = 1; + + // Footprint is an integrity check of the final state of the inventory after + // applying the genesis payload. bytes footprint = 2; } diff --git a/ledger/byzcoin/mod.go b/ledger/byzcoin/mod.go index 0344fa9f0..7ecab6c47 100644 --- a/ledger/byzcoin/mod.go +++ b/ledger/byzcoin/mod.go @@ -15,7 +15,7 @@ import ( "go.dedis.ch/fabric/crypto" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger" - "go.dedis.ch/fabric/ledger/byzcoin/actions" + "go.dedis.ch/fabric/ledger/byzcoin/roster" "go.dedis.ch/fabric/ledger/inventory" "go.dedis.ch/fabric/ledger/transactions" "go.dedis.ch/fabric/ledger/transactions/basic" @@ -24,7 +24,7 @@ import ( "golang.org/x/xerrors" ) -//go:generate protoc -I ./ --go_out=./ ./messages.proto +//go:generate protoc -I ./ -I ../../. --go_out=Mledger/byzcoin/roster/messages.proto=go.dedis.ch/fabric/ledger/byzcoin/roster:. ./messages.proto const ( initialRoundTime = 50 * time.Millisecond @@ -59,15 +59,15 @@ type Ledger struct { // NewLedger creates a new Byzcoin ledger. func NewLedger(mino mino.Mino, signer crypto.AggregateSigner) *Ledger { - factory := basic.NewTransactionFactory(signer, actions.NewActionFactory()) + factory := basic.NewTransactionFactory(signer, NewActionFactory()) decoder := func(pb proto.Message) (gossip.Rumor, error) { return factory.FromProto(pb) } proc := newTxProcessor(factory) gov := governance{ - inventory: proc.inventory, - rosterFactory: newRosterFactory(mino.GetAddressFactory(), signer.GetPublicKeyFactory()), + inventory: proc.inventory, + authorityFactory: roster.NewRosterFactory(mino.GetAddressFactory(), signer.GetPublicKeyFactory()), } cosi := flatcosi.NewFlat(mino, signer) consensus := cosipbft.NewCoSiPBFT(mino, cosi, gov) @@ -344,12 +344,12 @@ func (a actorLedger) Setup(players mino.Players) error { return xerrors.Errorf("players must implement '%T'", authority) } - rosterpb, err := a.encoder.Pack(a.governance.rosterFactory.New(authority)) + rosterpb, err := a.encoder.Pack(a.governance.authorityFactory.New(authority)) if err != nil { return xerrors.Errorf("couldn't pack roster: %v", err) } - payload := &GenesisPayload{Roster: rosterpb.(*Roster)} + payload := &GenesisPayload{Roster: rosterpb.(*roster.Roster)} page, err := a.proc.setup(payload) if err != nil { @@ -392,8 +392,8 @@ func (a actorLedger) Close() error { // // - implements viewchange.Governance type governance struct { - inventory inventory.Inventory - rosterFactory rosterFactory + inventory inventory.Inventory + authorityFactory viewchange.AuthorityFactory } // GetAuthority implements viewchange.Governance. It returns the authority for @@ -409,7 +409,7 @@ func (gov governance) GetAuthority(index uint64) (viewchange.EvolvableAuthority, return nil, xerrors.Errorf("couldn't read roster: %v", err) } - roster, err := gov.rosterFactory.FromProto(rosterpb) + roster, err := gov.authorityFactory.FromProto(rosterpb) if err != nil { return nil, xerrors.Errorf("couldn't decode roster: %v", err) } diff --git a/ledger/byzcoin/mod_test.go b/ledger/byzcoin/mod_test.go index 4aff1e627..790f0f480 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -15,6 +15,7 @@ import ( "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/ledger" "go.dedis.ch/fabric/ledger/arc/darc" + "go.dedis.ch/fabric/ledger/byzcoin/roster" "go.dedis.ch/fabric/ledger/transactions/basic" "go.dedis.ch/fabric/mino" "go.dedis.ch/fabric/mino/minoch" @@ -24,7 +25,6 @@ import ( func TestMessages(t *testing.T) { messages := []proto.Message{ &BlockPayload{}, - &Roster{}, &GenesisPayload{}, } @@ -97,18 +97,15 @@ func TestLedger_Basic(t *testing.T) { } func TestGovernance_GetAuthority(t *testing.T) { - factory := rosterFactory{ - addressFactory: fake.AddressFactory{}, - pubkeyFactory: fake.PublicKeyFactory{}, - } + factory := roster.NewRosterFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) roster := factory.New(fake.NewAuthority(3, fake.NewSigner)) rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) gov := governance{ - rosterFactory: factory, - inventory: fakeInventory{page: &fakePage{value: rosterpb}}, + authorityFactory: factory, + inventory: fakeInventory{page: &fakePage{value: rosterpb}}, } authority, err := gov.GetAuthority(3) diff --git a/ledger/byzcoin/roster/action.go b/ledger/byzcoin/roster/action.go new file mode 100644 index 000000000..edd96d794 --- /dev/null +++ b/ledger/byzcoin/roster/action.go @@ -0,0 +1 @@ +package roster diff --git a/ledger/byzcoin/roster/messages.pb.go b/ledger/byzcoin/roster/messages.pb.go new file mode 100644 index 000000000..8e55e35a8 --- /dev/null +++ b/ledger/byzcoin/roster/messages.pb.go @@ -0,0 +1,91 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: messages.proto + +package roster + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// Roster is a list of participants each having an address and a public key. +type Roster struct { + Addresses [][]byte `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"` + PublicKeys []*any.Any `protobuf:"bytes,2,rep,name=publicKeys,proto3" json:"publicKeys,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Roster) Reset() { *m = Roster{} } +func (m *Roster) String() string { return proto.CompactTextString(m) } +func (*Roster) ProtoMessage() {} +func (*Roster) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{0} +} + +func (m *Roster) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Roster.Unmarshal(m, b) +} +func (m *Roster) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Roster.Marshal(b, m, deterministic) +} +func (m *Roster) XXX_Merge(src proto.Message) { + xxx_messageInfo_Roster.Merge(m, src) +} +func (m *Roster) XXX_Size() int { + return xxx_messageInfo_Roster.Size(m) +} +func (m *Roster) XXX_DiscardUnknown() { + xxx_messageInfo_Roster.DiscardUnknown(m) +} + +var xxx_messageInfo_Roster proto.InternalMessageInfo + +func (m *Roster) GetAddresses() [][]byte { + if m != nil { + return m.Addresses + } + return nil +} + +func (m *Roster) GetPublicKeys() []*any.Any { + if m != nil { + return m.PublicKeys + } + return nil +} + +func init() { + proto.RegisterType((*Roster)(nil), "roster.Roster") +} + +func init() { + proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) +} + +var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ + // 137 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, + 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0xca, 0x2f, 0x2e, + 0x49, 0x2d, 0x92, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x26, 0x95, 0xa6, + 0xe9, 0x27, 0xe6, 0x55, 0x42, 0x94, 0x28, 0xc5, 0x70, 0xb1, 0x05, 0x81, 0x15, 0x09, 0xc9, 0x70, + 0x71, 0x26, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0xa7, 0x16, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0xf0, + 0x04, 0x21, 0x04, 0x84, 0x4c, 0xb8, 0xb8, 0x0a, 0x4a, 0x93, 0x72, 0x32, 0x93, 0xbd, 0x53, 0x2b, + 0x8b, 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0xe6, 0xea, 0xc1, 0xcc, 0xd5, + 0x73, 0xcc, 0xab, 0x0c, 0x42, 0x52, 0x97, 0xc4, 0x06, 0x96, 0x31, 0x06, 0x04, 0x00, 0x00, 0xff, + 0xff, 0x36, 0x9c, 0x09, 0x87, 0x99, 0x00, 0x00, 0x00, +} diff --git a/ledger/byzcoin/roster/messages.proto b/ledger/byzcoin/roster/messages.proto new file mode 100644 index 000000000..300bf743c --- /dev/null +++ b/ledger/byzcoin/roster/messages.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package roster; + +import "google/protobuf/any.proto"; + +// Roster is a list of participants each having an address and a public key. +message Roster { + repeated bytes addresses = 1; + repeated google.protobuf.Any publicKeys = 2; +} \ No newline at end of file diff --git a/ledger/byzcoin/roster.go b/ledger/byzcoin/roster/mod.go similarity index 94% rename from ledger/byzcoin/roster.go rename to ledger/byzcoin/roster/mod.go index fb81f5920..7bc704810 100644 --- a/ledger/byzcoin/roster.go +++ b/ledger/byzcoin/roster/mod.go @@ -1,4 +1,4 @@ -package byzcoin +package roster import ( proto "github.com/golang/protobuf/proto" @@ -10,6 +10,8 @@ import ( "golang.org/x/xerrors" ) +//go:generate protoc -I ./ --go_out=./ ./messages.proto + // iterator is a generic implementation of an iterator over a list of conodes. type iterator struct { index int @@ -149,19 +151,22 @@ func (r roster) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { } // rosterFactory provide functions to create and decode a roster. +// +// - implements viewchange.AuthorityFactory type rosterFactory struct { addressFactory mino.AddressFactory pubkeyFactory crypto.PublicKeyFactory } -func newRosterFactory(af mino.AddressFactory, pf crypto.PublicKeyFactory) rosterFactory { +// NewRosterFactory creates a new instance of the authority factory. +func NewRosterFactory(af mino.AddressFactory, pf crypto.PublicKeyFactory) viewchange.AuthorityFactory { return rosterFactory{ addressFactory: af, pubkeyFactory: pf, } } -func (f rosterFactory) New(authority crypto.CollectiveAuthority) roster { +func (f rosterFactory) New(authority crypto.CollectiveAuthority) viewchange.EvolvableAuthority { addrs := make([]mino.Address, authority.Len()) pubkeys := make([]crypto.PublicKey, authority.Len()) diff --git a/ledger/byzcoin/roster_test.go b/ledger/byzcoin/roster/mod_test.go similarity index 89% rename from ledger/byzcoin/roster_test.go rename to ledger/byzcoin/roster/mod_test.go index 3794f8d6c..7c7ba834d 100644 --- a/ledger/byzcoin/roster_test.go +++ b/ledger/byzcoin/roster/mod_test.go @@ -1,14 +1,26 @@ -package byzcoin +package roster import ( "testing" + proto "github.com/golang/protobuf/proto" "github.com/stretchr/testify/require" "go.dedis.ch/fabric/encoding" + internal "go.dedis.ch/fabric/internal/testing" "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/mino" ) +func TestMessages(t *testing.T) { + messages := []proto.Message{ + &Roster{}, + } + + for _, m := range messages { + internal.CoverProtoMessage(t, m) + } +} + func TestIterator_HasNext(t *testing.T) { iter := &iterator{ roster: &roster{addrs: make([]mino.Address, 3)}, @@ -43,7 +55,7 @@ func TestIterator_GetNext(t *testing.T) { } func TestAddressIterator_GetNext(t *testing.T) { - roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)) + roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)).(roster) iter := &addressIterator{ iterator: &iterator{ roster: &roster, @@ -59,7 +71,7 @@ func TestAddressIterator_GetNext(t *testing.T) { } func TestPublicKeyIterator_GetNext(t *testing.T) { - roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)) + roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)).(roster) iter := &publicKeyIterator{ iterator: &iterator{ roster: &roster, @@ -108,7 +120,7 @@ func TestRoster_GetPublicKey(t *testing.T) { } func TestRoster_Pack(t *testing.T) { - roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)) + roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)).(roster) rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) @@ -127,7 +139,7 @@ func TestRosterFactory_FromProto(t *testing.T) { rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) - factory := newRosterFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) + factory := NewRosterFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}).(rosterFactory) decoded, err := factory.FromProto(rosterpb) require.NoError(t, err) From 63c0b0b116f2172a666c9c005c2e45eb126dadaf Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Mon, 4 May 2020 10:59:55 +0200 Subject: [PATCH 7/9] Byzcoin: initial implementation of the roster change It implements the roster change with only the support of removals and without any access rights control. --- blockchain/skipchain/mod_test.go | 5 +- consensus/cosipbft/mod.go | 13 +- consensus/cosipbft/mod_test.go | 4 +- consensus/viewchange/mod.go | 5 +- ledger/byzcoin/mod.go | 72 ++---- ledger/byzcoin/mod_test.go | 54 ++--- ledger/byzcoin/roster/action.go | 1 - ledger/byzcoin/roster/messages.pb.go | 89 +++++++- ledger/byzcoin/roster/messages.proto | 10 +- ledger/byzcoin/roster/mod.go | 25 +- ledger/byzcoin/roster/task.go | 213 ++++++++++++++++++ ledger/byzcoin/roster/task_test.go | 69 ++++++ ledger/byzcoin/{action.go => task.go} | 34 ++- .../byzcoin/{action_test.go => task_test.go} | 0 ledger/byzcoin/txproc.go | 5 +- ledger/byzcoin/txproc_test.go | 9 +- ledger/transactions/basic/mod.go | 2 + 17 files changed, 485 insertions(+), 125 deletions(-) delete mode 100644 ledger/byzcoin/roster/action.go create mode 100644 ledger/byzcoin/roster/task.go create mode 100644 ledger/byzcoin/roster/task_test.go rename ledger/byzcoin/{action.go => task.go} (50%) rename ledger/byzcoin/{action_test.go => task_test.go} (100%) diff --git a/blockchain/skipchain/mod_test.go b/blockchain/skipchain/mod_test.go index 70ccf76cf..3541b9a3a 100644 --- a/blockchain/skipchain/mod_test.go +++ b/blockchain/skipchain/mod_test.go @@ -352,6 +352,7 @@ func (rand fakeRandGenerator) Read(buffer []byte) (int, error) { } type fakeGovernance struct { + viewchange.Governance authority fake.CollectiveAuthority } @@ -359,6 +360,6 @@ func (gov fakeGovernance) GetAuthority(index uint64) (viewchange.EvolvableAuthor return gov.authority, nil } -func (gov fakeGovernance) GetChangeSet(uint64) viewchange.ChangeSet { - return viewchange.ChangeSet{} +func (gov fakeGovernance) GetChangeSet(uint64) (viewchange.ChangeSet, error) { + return viewchange.ChangeSet{}, nil } diff --git a/consensus/cosipbft/mod.go b/consensus/cosipbft/mod.go index d55743707..52c0f7ae2 100644 --- a/consensus/cosipbft/mod.go +++ b/consensus/cosipbft/mod.go @@ -136,7 +136,11 @@ func (a pbftActor) Propose(p consensus.Proposal) error { return nil } - changeset := a.governance.GetChangeSet(p.GetIndex() - 1) + changeset, err := a.governance.GetChangeSet(p.GetIndex() - 1) + if err != nil { + return xerrors.Errorf("couldn't get change set: %v", err) + } + changeset.Leader = leader ctx := context.Background() @@ -249,10 +253,15 @@ func (h handler) Hash(addr mino.Address, in proto.Message) (Digest, error) { last.GetTo(), proposal.GetPreviousHash()) } + changeset, err := h.governance.GetChangeSet(proposal.GetIndex() - 1) + if err != nil { + return nil, xerrors.Errorf("couldn't get change set: %v", err) + } + forwardLink := forwardLink{ from: proposal.GetPreviousHash(), to: proposal.GetHash(), - changeset: h.governance.GetChangeSet(proposal.GetIndex() - 1), + changeset: changeset, } leader := h.viewchange.Verify(proposal, authority) diff --git a/consensus/cosipbft/mod_test.go b/consensus/cosipbft/mod_test.go index 7ace4f86b..07516397d 100644 --- a/consensus/cosipbft/mod_test.go +++ b/consensus/cosipbft/mod_test.go @@ -502,8 +502,8 @@ func (gov fakeGovernance) GetAuthority(index uint64) (viewchange.EvolvableAuthor return gov.authority, gov.err } -func (gov fakeGovernance) GetChangeSet(uint64) viewchange.ChangeSet { - return gov.changeset +func (gov fakeGovernance) GetChangeSet(uint64) (viewchange.ChangeSet, error) { + return gov.changeset, nil } type fakeQueue struct { diff --git a/consensus/viewchange/mod.go b/consensus/viewchange/mod.go index f9abb20b6..0f55d43e9 100644 --- a/consensus/viewchange/mod.go +++ b/consensus/viewchange/mod.go @@ -45,6 +45,7 @@ type EvolvableAuthority interface { Apply(ChangeSet) EvolvableAuthority } +// AuthorityFactory is an interface to instantiate evolvable authorities. type AuthorityFactory interface { New(crypto.CollectiveAuthority) EvolvableAuthority @@ -54,6 +55,8 @@ type AuthorityFactory interface { // Governance is an interface to get information about the collective authority // of a proposal. type Governance interface { + GetAuthorityFactory() AuthorityFactory + // GetAuthority must return the authority that governs the proposal at the // given index. It will be used to sign the forward link to the next // proposal. @@ -61,5 +64,5 @@ type Governance interface { // GetChangeSet must return the changes to the authority that will be // applied for the proposal following the given index. - GetChangeSet(index uint64) ChangeSet + GetChangeSet(index uint64) (ChangeSet, error) } diff --git a/ledger/byzcoin/mod.go b/ledger/byzcoin/mod.go index 7ecab6c47..5260fb10b 100644 --- a/ledger/byzcoin/mod.go +++ b/ledger/byzcoin/mod.go @@ -16,7 +16,7 @@ import ( "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger" "go.dedis.ch/fabric/ledger/byzcoin/roster" - "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/inventory/mem" "go.dedis.ch/fabric/ledger/transactions" "go.dedis.ch/fabric/ledger/transactions/basic" "go.dedis.ch/fabric/mino" @@ -34,7 +34,7 @@ const ( var ( // AuthorityKey is a reserved instance key for the roster of the chain. It // may evolve after each block. - authorityKey = []byte{0x01} + authorityKey = []byte(roster.AuthorityKey) ) // Ledger is a distributed public ledger implemented by using a blockchain. Each @@ -50,7 +50,7 @@ type Ledger struct { gossiper gossip.Gossiper bag *txBag proc *txProcessor - governance governance + governance viewchange.Governance encoder encoding.ProtoMarshaler txFactory transactions.TransactionFactory closing chan struct{} @@ -58,30 +58,27 @@ type Ledger struct { } // NewLedger creates a new Byzcoin ledger. -func NewLedger(mino mino.Mino, signer crypto.AggregateSigner) *Ledger { - factory := basic.NewTransactionFactory(signer, NewActionFactory()) +func NewLedger(m mino.Mino, signer crypto.AggregateSigner) *Ledger { + inventory := mem.NewInventory() + taskFactory, gov := newtaskFactory(m, signer, inventory) + + txFactory := basic.NewTransactionFactory(signer, taskFactory) decoder := func(pb proto.Message) (gossip.Rumor, error) { - return factory.FromProto(pb) + return txFactory.FromProto(pb) } - proc := newTxProcessor(factory) - gov := governance{ - inventory: proc.inventory, - authorityFactory: roster.NewRosterFactory(mino.GetAddressFactory(), signer.GetPublicKeyFactory()), - } - cosi := flatcosi.NewFlat(mino, signer) - consensus := cosipbft.NewCoSiPBFT(mino, cosi, gov) + consensus := cosipbft.NewCoSiPBFT(m, flatcosi.NewFlat(m, signer), gov) return &Ledger{ - addr: mino.GetAddress(), + addr: m.GetAddress(), signer: signer, - bc: skipchain.NewSkipchain(mino, consensus), - gossiper: gossip.NewFlat(mino, decoder), + bc: skipchain.NewSkipchain(m, consensus), + gossiper: gossip.NewFlat(m, decoder), bag: newTxBag(), - proc: proc, + proc: newTxProcessor(txFactory, inventory), governance: gov, encoder: encoding.NewProtoEncoder(), - txFactory: factory, + txFactory: txFactory, closing: make(chan struct{}), initiated: make(chan error, 1), } @@ -344,7 +341,7 @@ func (a actorLedger) Setup(players mino.Players) error { return xerrors.Errorf("players must implement '%T'", authority) } - rosterpb, err := a.encoder.Pack(a.governance.authorityFactory.New(authority)) + rosterpb, err := a.encoder.Pack(a.governance.GetAuthorityFactory().New(authority)) if err != nil { return xerrors.Errorf("couldn't pack roster: %v", err) } @@ -385,40 +382,3 @@ func (a actorLedger) Close() error { return nil } - -// Governance is an implementation of viewchange.Governance so that the module -// can act on the roster which is done through transactions. -// TODO: implement the roster txs -// -// - implements viewchange.Governance -type governance struct { - inventory inventory.Inventory - authorityFactory viewchange.AuthorityFactory -} - -// GetAuthority implements viewchange.Governance. It returns the authority for -// the given block index by reading the inventory page associated. -func (gov governance) GetAuthority(index uint64) (viewchange.EvolvableAuthority, error) { - page, err := gov.inventory.GetPage(index) - if err != nil { - return nil, xerrors.Errorf("couldn't read page: %v", err) - } - - rosterpb, err := page.Read(authorityKey) - if err != nil { - return nil, xerrors.Errorf("couldn't read roster: %v", err) - } - - roster, err := gov.authorityFactory.FromProto(rosterpb) - if err != nil { - return nil, xerrors.Errorf("couldn't decode roster: %v", err) - } - - return roster, nil -} - -// GetChangeSet implements viewchange.Governance. It returns the change set for -// that block by reading the transactions. -func (gov governance) GetChangeSet(index uint64) viewchange.ChangeSet { - return viewchange.ChangeSet{} -} diff --git a/ledger/byzcoin/mod_test.go b/ledger/byzcoin/mod_test.go index 790f0f480..30b045018 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "go.dedis.ch/fabric/crypto" "go.dedis.ch/fabric/crypto/bls" - "go.dedis.ch/fabric/encoding" internal "go.dedis.ch/fabric/internal/testing" "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/ledger" @@ -19,7 +18,6 @@ import ( "go.dedis.ch/fabric/ledger/transactions/basic" "go.dedis.ch/fabric/mino" "go.dedis.ch/fabric/mino/minoch" - "golang.org/x/xerrors" ) func TestMessages(t *testing.T) { @@ -60,9 +58,8 @@ func TestLedger_Basic(t *testing.T) { signer := bls.NewSigner() txFactory := basic.NewTransactionFactory(signer, nil) - // Try to create a DARC. - access := makeDarc(t, signer) - tx, err := txFactory.New(darc.NewCreate(access)) + // Execute a roster change tx by removing one of the participants. + tx, err := txFactory.New(roster.NewClientTask([]uint32{15})) require.NoError(t, err) err = actors[1].AddTransaction(tx) @@ -76,15 +73,16 @@ func TestLedger_Basic(t *testing.T) { t.Fatal("timeout 1") } - value, err := ledgers[2].GetValue(tx.GetID()) + roster, err := ledgers[5].(*Ledger).governance.GetAuthority(1) require.NoError(t, err) - require.IsType(t, (*darc.AccessProto)(nil), value) + require.Equal(t, 19, roster.Len()) - // Then update it. - tx, err = txFactory.New(darc.NewUpdate(tx.GetID(), access)) + // Try to create a DARC. + access := makeDarc(t, signer) + tx, err = txFactory.New(darc.NewCreate(access)) require.NoError(t, err) - err = actors[0].AddTransaction(tx) + err = actors[1].AddTransaction(tx) require.NoError(t, err) select { @@ -94,35 +92,25 @@ func TestLedger_Basic(t *testing.T) { case <-time.After(1 * time.Second): t.Fatal("timeout 2") } -} -func TestGovernance_GetAuthority(t *testing.T) { - factory := roster.NewRosterFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - roster := factory.New(fake.NewAuthority(3, fake.NewSigner)) - rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) + value, err := ledgers[2].GetValue(tx.GetID()) require.NoError(t, err) + require.IsType(t, (*darc.AccessProto)(nil), value) - gov := governance{ - authorityFactory: factory, - inventory: fakeInventory{page: &fakePage{value: rosterpb}}, - } - - authority, err := gov.GetAuthority(3) + // Then update it. + tx, err = txFactory.New(darc.NewUpdate(tx.GetID(), access)) require.NoError(t, err) - require.Equal(t, 3, authority.Len()) - gov.inventory = fakeInventory{err: xerrors.New("oops")} - _, err = gov.GetAuthority(3) - require.EqualError(t, err, "couldn't read page: oops") - - gov.inventory = fakeInventory{page: &fakePage{err: xerrors.New("oops")}} - _, err = gov.GetAuthority(3) - require.EqualError(t, err, "couldn't read roster: oops") + err = actors[0].AddTransaction(tx) + require.NoError(t, err) - gov.inventory = fakeInventory{page: &fakePage{}} - _, err = gov.GetAuthority(3) - require.EqualError(t, err, "couldn't decode roster: invalid message type ''") + select { + case res := <-txs: + require.NotNil(t, res) + require.Equal(t, tx.GetID(), res.GetTransactionID()) + case <-time.After(1 * time.Second): + t.Fatal("timeout 3") + } } // ----------------------------------------------------------------------------- diff --git a/ledger/byzcoin/roster/action.go b/ledger/byzcoin/roster/action.go deleted file mode 100644 index edd96d794..000000000 --- a/ledger/byzcoin/roster/action.go +++ /dev/null @@ -1 +0,0 @@ -package roster diff --git a/ledger/byzcoin/roster/messages.pb.go b/ledger/byzcoin/roster/messages.pb.go index 8e55e35a8..7c7e11b53 100644 --- a/ledger/byzcoin/roster/messages.pb.go +++ b/ledger/byzcoin/roster/messages.pb.go @@ -69,8 +69,88 @@ func (m *Roster) GetPublicKeys() []*any.Any { return nil } +type ChangeSet struct { + Remove []uint32 `protobuf:"varint,1,rep,packed,name=remove,proto3" json:"remove,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChangeSet) Reset() { *m = ChangeSet{} } +func (m *ChangeSet) String() string { return proto.CompactTextString(m) } +func (*ChangeSet) ProtoMessage() {} +func (*ChangeSet) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{1} +} + +func (m *ChangeSet) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChangeSet.Unmarshal(m, b) +} +func (m *ChangeSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChangeSet.Marshal(b, m, deterministic) +} +func (m *ChangeSet) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChangeSet.Merge(m, src) +} +func (m *ChangeSet) XXX_Size() int { + return xxx_messageInfo_ChangeSet.Size(m) +} +func (m *ChangeSet) XXX_DiscardUnknown() { + xxx_messageInfo_ChangeSet.DiscardUnknown(m) +} + +var xxx_messageInfo_ChangeSet proto.InternalMessageInfo + +func (m *ChangeSet) GetRemove() []uint32 { + if m != nil { + return m.Remove + } + return nil +} + +type ActionProto struct { + Remove []uint32 `protobuf:"varint,1,rep,packed,name=remove,proto3" json:"remove,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ActionProto) Reset() { *m = ActionProto{} } +func (m *ActionProto) String() string { return proto.CompactTextString(m) } +func (*ActionProto) ProtoMessage() {} +func (*ActionProto) Descriptor() ([]byte, []int) { + return fileDescriptor_4dc296cbfe5ffcd5, []int{2} +} + +func (m *ActionProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ActionProto.Unmarshal(m, b) +} +func (m *ActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ActionProto.Marshal(b, m, deterministic) +} +func (m *ActionProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_ActionProto.Merge(m, src) +} +func (m *ActionProto) XXX_Size() int { + return xxx_messageInfo_ActionProto.Size(m) +} +func (m *ActionProto) XXX_DiscardUnknown() { + xxx_messageInfo_ActionProto.DiscardUnknown(m) +} + +var xxx_messageInfo_ActionProto proto.InternalMessageInfo + +func (m *ActionProto) GetRemove() []uint32 { + if m != nil { + return m.Remove + } + return nil +} + func init() { proto.RegisterType((*Roster)(nil), "roster.Roster") + proto.RegisterType((*ChangeSet)(nil), "roster.ChangeSet") + proto.RegisterType((*ActionProto)(nil), "roster.ActionProto") } func init() { @@ -78,7 +158,7 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 137 bytes of a gzipped FileDescriptorProto + // 182 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0xca, 0x2f, 0x2e, 0x49, 0x2d, 0x92, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x26, 0x95, 0xa6, @@ -86,6 +166,9 @@ var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ 0x71, 0x26, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0xa7, 0x16, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0xf0, 0x04, 0x21, 0x04, 0x84, 0x4c, 0xb8, 0xb8, 0x0a, 0x4a, 0x93, 0x72, 0x32, 0x93, 0xbd, 0x53, 0x2b, 0x8b, 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0xe6, 0xea, 0xc1, 0xcc, 0xd5, - 0x73, 0xcc, 0xab, 0x0c, 0x42, 0x52, 0x97, 0xc4, 0x06, 0x96, 0x31, 0x06, 0x04, 0x00, 0x00, 0xff, - 0xff, 0x36, 0x9c, 0x09, 0x87, 0x99, 0x00, 0x00, 0x00, + 0x73, 0xcc, 0xab, 0x0c, 0x42, 0x52, 0xa7, 0xa4, 0xcc, 0xc5, 0xe9, 0x9c, 0x91, 0x98, 0x97, 0x9e, + 0x1a, 0x9c, 0x5a, 0x22, 0x24, 0xc6, 0xc5, 0x56, 0x94, 0x9a, 0x9b, 0x5f, 0x96, 0x0a, 0x36, 0x9d, + 0x37, 0x08, 0xca, 0x53, 0x52, 0xe5, 0xe2, 0x76, 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0x0b, 0x00, 0x3b, + 0x1a, 0x87, 0xb2, 0x24, 0x36, 0xb0, 0x2d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x39, 0x8d, + 0x6d, 0x15, 0xe5, 0x00, 0x00, 0x00, } diff --git a/ledger/byzcoin/roster/messages.proto b/ledger/byzcoin/roster/messages.proto index 300bf743c..12562fec7 100644 --- a/ledger/byzcoin/roster/messages.proto +++ b/ledger/byzcoin/roster/messages.proto @@ -8,4 +8,12 @@ import "google/protobuf/any.proto"; message Roster { repeated bytes addresses = 1; repeated google.protobuf.Any publicKeys = 2; -} \ No newline at end of file +} + +message ChangeSet { + repeated uint32 remove = 1; +} + +message ActionProto { + repeated uint32 remove = 1; +} diff --git a/ledger/byzcoin/roster/mod.go b/ledger/byzcoin/roster/mod.go index 7bc704810..57ff27b34 100644 --- a/ledger/byzcoin/roster/mod.go +++ b/ledger/byzcoin/roster/mod.go @@ -88,9 +88,28 @@ func (r roster) Take(updaters ...mino.FilterUpdater) mino.Players { // Apply implements viewchange.EvolvableAuthority. It returns a new authority // after applying the change set. -func (r roster) Apply(viewchange.ChangeSet) viewchange.EvolvableAuthority { - // TODO: implement - return r +func (r roster) Apply(changeset viewchange.ChangeSet) viewchange.EvolvableAuthority { + addrs := make([]mino.Address, r.Len()) + pubkeys := make([]crypto.PublicKey, r.Len()) + + for i, addr := range r.addrs { + addrs[i] = addr + pubkeys[i] = r.pubkeys[i] + } + + for _, i := range changeset.Remove { + if int(i) < len(addrs) { + addrs = append(addrs[:i], addrs[i+1:]...) + pubkeys = append(pubkeys[:i], pubkeys[i+1:]...) + } + } + + roster := roster{ + addrs: addrs, + pubkeys: pubkeys, + } + + return roster } // Len implements mino.Players. It returns the length of the roster. diff --git a/ledger/byzcoin/roster/task.go b/ledger/byzcoin/roster/task.go new file mode 100644 index 000000000..1d00f087f --- /dev/null +++ b/ledger/byzcoin/roster/task.go @@ -0,0 +1,213 @@ +package roster + +import ( + "encoding/binary" + "io" + + "github.com/golang/protobuf/proto" + "go.dedis.ch/fabric/consensus/viewchange" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/ledger/inventory" + "go.dedis.ch/fabric/ledger/transactions/basic" + "golang.org/x/xerrors" +) + +const ( + // AuthorityKey is the key used to store the roster. + AuthorityKey = "authority:value" + + // ArcKey is the ke used to store the access rights control of the roster. + ArcKey = "authority:arc" +) + +var authorityKey = []byte(AuthorityKey) +var changesetKey = []byte("authority:changeset") + +// clientTask is the client task implementation to update the roster of a +// consensus using the transactions for access rights control. +type clientTask struct { + remove []uint32 +} + +// NewClientTask creates a new roster client task that can be used to create a +// transaction. +func NewClientTask(r []uint32) basic.ClientAction { + return clientTask{ + remove: r, + } +} + +func (t clientTask) GetChangeSet() viewchange.ChangeSet { + changeset := viewchange.ChangeSet{ + Remove: t.remove, + } + + return changeset +} + +// Pack implements encoding.Packable. It returns the protobuf message for the +// client task. +func (t clientTask) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { + pb := &ActionProto{ + Remove: t.remove, + } + + return pb, nil +} + +// Fingerprint implements encoding.Fingerprinter. It serializes the client task +// to the writer in a deterministic way. +func (t clientTask) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { + buffer := make([]byte, 4*len(t.remove)) + for i, index := range t.remove { + binary.LittleEndian.PutUint32(buffer[i*4:], index) + } + + _, err := w.Write(buffer) + if err != nil { + return xerrors.Errorf("couldn't write remove indices: %v", err) + } + + return nil +} + +// serverTask is the extension of the client task to consume the task and update +// the inventory page accordingly. +type serverTask struct { + clientTask + encoder encoding.ProtoMarshaler + rosterFactory viewchange.AuthorityFactory +} + +// Consume implements basic.ServerAction. It executes the task and write the +// changes to the page. +func (t serverTask) Consume(ctx basic.Context, page inventory.WritablePage) error { + // 1. Access rights control + // TODO: implement + + // 2. Update the roster stored in the inventory. + value, err := page.Read(authorityKey) + if err != nil { + return err + } + + roster, err := t.rosterFactory.FromProto(value) + if err != nil { + return err + } + + changeset := t.GetChangeSet() + roster = roster.Apply(changeset) + + value, err = t.encoder.Pack(roster) + if err != nil { + return err + } + + err = page.Write(authorityKey, value) + if err != nil { + return err + } + + // 3. Store the changeset so it can be read later on. + changesetpb := &ChangeSet{ + Remove: changeset.Remove, + } + + err = page.Write(changesetKey, changesetpb) + if err != nil { + return err + } + + return nil +} + +// TaskManager manages the roster tasks by providing a factory and a governance +// implementation. +// +// - implements basic.TaskManager +// - implements viewchange.Governance +type TaskManager struct { + encoder encoding.ProtoMarshaler + inventory inventory.Inventory + rosterFactory viewchange.AuthorityFactory +} + +// NewTaskManager returns a new instance of the action factory. +func NewTaskManager(f viewchange.AuthorityFactory, i inventory.Inventory) TaskManager { + return TaskManager{ + encoder: encoding.NewProtoEncoder(), + inventory: i, + rosterFactory: f, + } +} + +// GetAuthorityFactory implements viewchange.AuthorityFactory. It returns the +// authority factory. +func (f TaskManager) GetAuthorityFactory() viewchange.AuthorityFactory { + return f.rosterFactory +} + +// GetAuthority implements viewchange.Governance. It returns the authority for +// the given block index by reading the inventory page associated. +func (f TaskManager) GetAuthority(index uint64) (viewchange.EvolvableAuthority, error) { + page, err := f.inventory.GetPage(index) + if err != nil { + return nil, xerrors.Errorf("couldn't read page: %v", err) + } + + rosterpb, err := page.Read(authorityKey) + if err != nil { + return nil, xerrors.Errorf("couldn't read roster: %v", err) + } + + roster, err := f.rosterFactory.FromProto(rosterpb) + if err != nil { + return nil, xerrors.Errorf("couldn't decode roster: %v", err) + } + + return roster, nil +} + +// GetChangeSet implements viewchange.Governance. It returns the change set for +// that block by reading the transactions. +func (f TaskManager) GetChangeSet(index uint64) (viewchange.ChangeSet, error) { + cs := viewchange.ChangeSet{} + + page, err := f.inventory.GetPage(index) + if err != nil { + return cs, xerrors.Errorf("couldn't read page: %v", err) + } + + pb, err := page.Read(changesetKey) + if err != nil { + return cs, nil + } + + changesetpb, ok := pb.(*ChangeSet) + if !ok { + return cs, nil + } + + cs.Remove = changesetpb.GetRemove() + + return cs, nil +} + +// FromProto implements basic.ActionFactory. +func (f TaskManager) FromProto(in proto.Message) (basic.ServerAction, error) { + var pb *ActionProto + switch msg := in.(type) { + case *ActionProto: + pb = msg + } + + action := serverTask{ + clientTask: clientTask{ + remove: pb.Remove, + }, + encoder: f.encoder, + rosterFactory: f.rosterFactory, + } + return action, nil +} diff --git a/ledger/byzcoin/roster/task_test.go b/ledger/byzcoin/roster/task_test.go new file mode 100644 index 000000000..b5cc6919c --- /dev/null +++ b/ledger/byzcoin/roster/task_test.go @@ -0,0 +1,69 @@ +package roster + +import ( + "testing" + + proto "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/encoding" + "go.dedis.ch/fabric/internal/testing/fake" + "go.dedis.ch/fabric/ledger/inventory" + "golang.org/x/xerrors" +) + +func TestTaskManager_GetAuthorityFactory(t *testing.T) { + factory := NewRosterFactory(nil, nil) + manager := NewTaskManager(factory, nil) + + require.NotNil(t, manager.GetAuthorityFactory()) +} + +func TestTaskManager_GetAuthority(t *testing.T) { + factory := NewRosterFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) + + roster := factory.New(fake.NewAuthority(3, fake.NewSigner)) + rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + + manager := NewTaskManager(factory, fakeInventory{value: rosterpb}) + + authority, err := manager.GetAuthority(3) + require.NoError(t, err) + require.Equal(t, 3, authority.Len()) + + manager.inventory = fakeInventory{err: xerrors.New("oops")} + _, err = manager.GetAuthority(3) + require.EqualError(t, err, "couldn't read page: oops") + + manager.inventory = fakeInventory{errPage: xerrors.New("oops")} + _, err = manager.GetAuthority(3) + require.EqualError(t, err, "couldn't read roster: oops") + + manager.inventory = fakeInventory{} + _, err = manager.GetAuthority(3) + require.EqualError(t, err, "couldn't decode roster: invalid message type ''") +} + +// ----------------------------------------------------------------------------- +// Utility functions + +type fakePage struct { + inventory.Page + value proto.Message + err error +} + +func (p fakePage) Read([]byte) (proto.Message, error) { + return p.value, p.err +} + +type fakeInventory struct { + inventory.Inventory + value proto.Message + err error + errPage error +} + +func (i fakeInventory) GetPage(uint64) (inventory.Page, error) { + return fakePage{value: i.value, err: i.errPage}, i.err +} diff --git a/ledger/byzcoin/action.go b/ledger/byzcoin/task.go similarity index 50% rename from ledger/byzcoin/action.go rename to ledger/byzcoin/task.go index 293712333..f59d5d73a 100644 --- a/ledger/byzcoin/action.go +++ b/ledger/byzcoin/task.go @@ -3,43 +3,53 @@ package byzcoin import ( "reflect" - proto "github.com/golang/protobuf/proto" - any "github.com/golang/protobuf/ptypes/any" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "go.dedis.ch/fabric/consensus/viewchange" + "go.dedis.ch/fabric/crypto" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger/arc/darc" + "go.dedis.ch/fabric/ledger/byzcoin/roster" + "go.dedis.ch/fabric/ledger/inventory" "go.dedis.ch/fabric/ledger/transactions/basic" + "go.dedis.ch/fabric/mino" "golang.org/x/xerrors" ) -// ActionFactory is an action factory that can process several types of actions. +// taskFactory is an action factory that can process several types of actions. // -// - implements basic.ActionFactory -type ActionFactory struct { +// - implements basic.TaskFactory +type taskFactory struct { encoder encoding.ProtoMarshaler registry map[reflect.Type]basic.ActionFactory } -// NewActionFactory creates a new action factory. -func NewActionFactory() basic.ActionFactory { - f := ActionFactory{ +func newtaskFactory(m mino.Mino, signer crypto.Signer, + i inventory.Inventory) (*taskFactory, viewchange.Governance) { + + f := &taskFactory{ encoder: encoding.NewProtoEncoder(), registry: make(map[reflect.Type]basic.ActionFactory), } + rosterFactory := roster.NewRosterFactory(m.GetAddressFactory(), signer.GetPublicKeyFactory()) + gov := roster.NewTaskManager(rosterFactory, i) + f.Register((*darc.ActionProto)(nil), darc.NewActionFactory()) + f.Register((*roster.ActionProto)(nil), gov) - return f + return f, gov } // Register registers the factory for the protobuf message. -func (f ActionFactory) Register(pb proto.Message, factory basic.ActionFactory) { +func (f *taskFactory) Register(pb proto.Message, factory basic.ActionFactory) { key := reflect.TypeOf(pb) f.registry[key] = factory } -// FromProto implements basic.ActionFactory. It returns the server action for +// FromProto implements basic.TaskFactory. It returns the server action for // the protobuf message if appropriate, otherwise an error. -func (f ActionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { +func (f *taskFactory) FromProto(in proto.Message) (basic.ServerAction, error) { inAny, ok := in.(*any.Any) if ok { var err error diff --git a/ledger/byzcoin/action_test.go b/ledger/byzcoin/task_test.go similarity index 100% rename from ledger/byzcoin/action_test.go rename to ledger/byzcoin/task_test.go diff --git a/ledger/byzcoin/txproc.go b/ledger/byzcoin/txproc.go index 644b50584..49b7230fd 100644 --- a/ledger/byzcoin/txproc.go +++ b/ledger/byzcoin/txproc.go @@ -6,7 +6,6 @@ import ( proto "github.com/golang/protobuf/proto" "go.dedis.ch/fabric" "go.dedis.ch/fabric/ledger/inventory" - "go.dedis.ch/fabric/ledger/inventory/mem" "go.dedis.ch/fabric/ledger/transactions" "golang.org/x/xerrors" ) @@ -20,9 +19,9 @@ type txProcessor struct { txFactory transactions.TransactionFactory } -func newTxProcessor(f transactions.TransactionFactory) *txProcessor { +func newTxProcessor(f transactions.TransactionFactory, i inventory.Inventory) *txProcessor { return &txProcessor{ - inventory: mem.NewInventory(), + inventory: i, txFactory: f, } } diff --git a/ledger/byzcoin/txproc_test.go b/ledger/byzcoin/txproc_test.go index 7eba3e4f6..81d0d5820 100644 --- a/ledger/byzcoin/txproc_test.go +++ b/ledger/byzcoin/txproc_test.go @@ -11,8 +11,7 @@ import ( ) func TestTxProcessor_Validate(t *testing.T) { - proc := newTxProcessor(nil) - proc.inventory = fakeInventory{} + proc := newTxProcessor(nil, fakeInventory{}) err := proc.Validate(0, &BlockPayload{}) require.NoError(t, err) @@ -46,8 +45,7 @@ func TestTxProcessor_Validate(t *testing.T) { } func TestTxProcessor_Process(t *testing.T) { - proc := newTxProcessor(nil) - proc.inventory = fakeInventory{page: &fakePage{index: 999}} + proc := newTxProcessor(nil, fakeInventory{page: &fakePage{index: 999}}) page, err := proc.process(&BlockPayload{}) require.NoError(t, err) @@ -62,8 +60,7 @@ func TestTxProcessor_Process(t *testing.T) { } func TestTxProcessor_Commit(t *testing.T) { - proc := newTxProcessor(nil) - proc.inventory = fakeInventory{} + proc := newTxProcessor(nil, fakeInventory{}) err := proc.Commit(&BlockPayload{}) require.NoError(t, err) diff --git a/ledger/transactions/basic/mod.go b/ledger/transactions/basic/mod.go index 857797cd4..d6f25703a 100644 --- a/ledger/transactions/basic/mod.go +++ b/ledger/transactions/basic/mod.go @@ -26,6 +26,8 @@ import ( //go:generate protoc -I ./ --go_out=./ ./messages.proto +// TODO: rename action to task + // ClientAction is used to create a transaction. type ClientAction interface { encoding.Packable From 0bb570ff9e1f494b191a32ed9c9b5fcef42bfdd5 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Mon, 4 May 2020 11:29:46 +0200 Subject: [PATCH 8/9] Rename action to task --- ledger/arc/darc/messages.pb.go | 47 +++-- ledger/arc/darc/messages.proto | 2 +- ledger/arc/darc/mod_test.go | 2 +- ledger/arc/darc/{action.go => task.go} | 58 +++--- .../arc/darc/{action_test.go => task_test.go} | 70 +++---- ledger/byzcoin/contract/context.go | 12 +- ledger/byzcoin/contract/context_test.go | 8 +- ledger/byzcoin/contract/messages.pb.go | 146 +++++++------- ledger/byzcoin/contract/messages.proto | 6 +- ledger/byzcoin/contract/mod.go | 3 +- ledger/byzcoin/contract/mod_test.go | 6 +- .../byzcoin/contract/{action.go => task.go} | 102 +++++----- .../contract/{action_test.go => task_test.go} | 190 +++++++++--------- ledger/byzcoin/roster/messages.pb.go | 43 ++-- ledger/byzcoin/roster/messages.proto | 2 +- ledger/byzcoin/roster/task.go | 20 +- ledger/byzcoin/task.go | 24 +-- ledger/transactions/basic/messages.pb.go | 16 +- ledger/transactions/basic/messages.proto | 2 +- ledger/transactions/basic/mod.go | 75 ++++--- ledger/transactions/basic/mod_test.go | 58 +++--- 21 files changed, 445 insertions(+), 447 deletions(-) rename ledger/arc/darc/{action.go => task.go} (69%) rename ledger/arc/darc/{action_test.go => task_test.go} (63%) rename ledger/byzcoin/contract/{action.go => task.go} (69%) rename ledger/byzcoin/contract/{action_test.go => task_test.go} (58%) diff --git a/ledger/arc/darc/messages.pb.go b/ledger/arc/darc/messages.pb.go index febf18a16..0bc3a285e 100644 --- a/ledger/arc/darc/messages.pb.go +++ b/ledger/arc/darc/messages.pb.go @@ -98,7 +98,7 @@ func (m *AccessProto) GetRules() map[string]*Expression { return nil } -type ActionProto struct { +type Task struct { Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Access *AccessProto `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -106,39 +106,39 @@ type ActionProto struct { XXX_sizecache int32 `json:"-"` } -func (m *ActionProto) Reset() { *m = ActionProto{} } -func (m *ActionProto) String() string { return proto.CompactTextString(m) } -func (*ActionProto) ProtoMessage() {} -func (*ActionProto) Descriptor() ([]byte, []int) { +func (m *Task) Reset() { *m = Task{} } +func (m *Task) String() string { return proto.CompactTextString(m) } +func (*Task) ProtoMessage() {} +func (*Task) Descriptor() ([]byte, []int) { return fileDescriptor_4dc296cbfe5ffcd5, []int{2} } -func (m *ActionProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ActionProto.Unmarshal(m, b) +func (m *Task) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Task.Unmarshal(m, b) } -func (m *ActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ActionProto.Marshal(b, m, deterministic) +func (m *Task) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Task.Marshal(b, m, deterministic) } -func (m *ActionProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_ActionProto.Merge(m, src) +func (m *Task) XXX_Merge(src proto.Message) { + xxx_messageInfo_Task.Merge(m, src) } -func (m *ActionProto) XXX_Size() int { - return xxx_messageInfo_ActionProto.Size(m) +func (m *Task) XXX_Size() int { + return xxx_messageInfo_Task.Size(m) } -func (m *ActionProto) XXX_DiscardUnknown() { - xxx_messageInfo_ActionProto.DiscardUnknown(m) +func (m *Task) XXX_DiscardUnknown() { + xxx_messageInfo_Task.DiscardUnknown(m) } -var xxx_messageInfo_ActionProto proto.InternalMessageInfo +var xxx_messageInfo_Task proto.InternalMessageInfo -func (m *ActionProto) GetKey() []byte { +func (m *Task) GetKey() []byte { if m != nil { return m.Key } return nil } -func (m *ActionProto) GetAccess() *AccessProto { +func (m *Task) GetAccess() *AccessProto { if m != nil { return m.Access } @@ -149,7 +149,7 @@ func init() { proto.RegisterType((*Expression)(nil), "darc.Expression") proto.RegisterType((*AccessProto)(nil), "darc.AccessProto") proto.RegisterMapType((map[string]*Expression)(nil), "darc.AccessProto.RulesEntry") - proto.RegisterType((*ActionProto)(nil), "darc.ActionProto") + proto.RegisterType((*Task)(nil), "darc.Task") } func init() { @@ -157,7 +157,7 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 209 bytes of a gzipped FileDescriptorProto + // 206 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0x49, 0x2c, 0x4a, 0x56, 0x52, 0xe3, 0xe2, 0x72, 0xad, 0x28, 0x28, 0x4a, 0x2d, 0x2e, 0xce, 0xcc, 0xcf, 0x13, 0x92, @@ -168,8 +168,7 @@ var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ 0x71, 0x21, 0x04, 0x85, 0x04, 0xb8, 0x98, 0xb3, 0x53, 0x2b, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x40, 0x4c, 0x21, 0x35, 0x2e, 0xd6, 0xb2, 0xc4, 0x9c, 0xd2, 0x54, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x01, 0x88, 0x99, 0x08, 0xe7, 0x05, 0x41, 0xa4, 0xad, 0x98, 0x2c, 0x18, 0x95, - 0xbc, 0x40, 0xce, 0x29, 0xc9, 0xcc, 0xcf, 0x83, 0x38, 0x07, 0xc9, 0x30, 0x1e, 0x88, 0x61, 0x9a, - 0x5c, 0x6c, 0x89, 0x60, 0xd7, 0x40, 0x4d, 0x13, 0xc4, 0x70, 0x61, 0x10, 0x54, 0x41, 0x12, 0x1b, - 0x38, 0x40, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x79, 0x71, 0xdf, 0x22, 0x01, 0x00, - 0x00, + 0x9c, 0xb9, 0x58, 0x42, 0x12, 0x8b, 0xb3, 0x91, 0x4d, 0xe1, 0x81, 0x98, 0xa2, 0xc9, 0xc5, 0x96, + 0x08, 0x76, 0x06, 0xd4, 0x18, 0x41, 0x0c, 0xa7, 0x05, 0x41, 0x15, 0x24, 0xb1, 0x81, 0x43, 0xc2, + 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x8d, 0x34, 0x9a, 0x08, 0x1b, 0x01, 0x00, 0x00, } diff --git a/ledger/arc/darc/messages.proto b/ledger/arc/darc/messages.proto index 04a2f02ab..a52a4767e 100644 --- a/ledger/arc/darc/messages.proto +++ b/ledger/arc/darc/messages.proto @@ -10,7 +10,7 @@ message AccessProto { map rules = 1; } -message ActionProto { +message Task { bytes key = 1; AccessProto access = 2; } diff --git a/ledger/arc/darc/mod_test.go b/ledger/arc/darc/mod_test.go index 4e1918512..b08229935 100644 --- a/ledger/arc/darc/mod_test.go +++ b/ledger/arc/darc/mod_test.go @@ -19,7 +19,7 @@ func TestMessages(t *testing.T) { messages := []proto.Message{ &Expression{}, &AccessProto{}, - &ActionProto{}, + &Task{}, } for _, m := range messages { diff --git a/ledger/arc/darc/action.go b/ledger/arc/darc/task.go similarity index 69% rename from ledger/arc/darc/action.go rename to ledger/arc/darc/task.go index 46f3c6a3e..bdce3e9e3 100644 --- a/ledger/arc/darc/action.go +++ b/ledger/arc/darc/task.go @@ -16,34 +16,34 @@ const ( UpdateAccessRule = "darc:update" ) -// darcAction is a transaction action to create or update an access right -// control. -type darcAction struct { +// clientTask is the client task of a transaction that will allow an authorized +// identity to create or update a DARC. +type clientTask struct { key []byte access Access } // TODO: client factory -// NewCreate returns a new action to create a DARC. -func NewCreate(access Access) basic.ClientAction { - return darcAction{access: access} +// NewCreate returns a new task to create a DARC. +func NewCreate(access Access) basic.ClientTask { + return clientTask{access: access} } -// NewUpdate returns a new action to update a DARC. -func NewUpdate(key []byte, access Access) basic.ClientAction { - return darcAction{key: key, access: access} +// NewUpdate returns a new task to update a DARC. +func NewUpdate(key []byte, access Access) basic.ClientTask { + return clientTask{key: key, access: access} } // Pack implements encoding.Packable. It returns the protobuf message for the -// action. -func (act darcAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { +// task. +func (act clientTask) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { access, err := enc.Pack(act.access) if err != nil { return nil, xerrors.Errorf("couldn't pack access: %v", err) } - pb := &ActionProto{ + pb := &Task{ Key: act.key, Access: access.(*AccessProto), } @@ -51,9 +51,9 @@ func (act darcAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { return pb, nil } -// Fingerprint implements encoding.Fingerprinter. It serializes the DARC action +// Fingerprint implements encoding.Fingerprinter. It serializes the client task // into the writer in a deterministic way. -func (act darcAction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { +func (act clientTask) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { _, err := w.Write(act.key) if err != nil { return xerrors.Errorf("couldn't write key: %v", err) @@ -67,16 +67,16 @@ func (act darcAction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) erro return nil } -// serverAction is the server-side action for DARCs. -type serverAction struct { +// serverTask is the server task for a DARC transaction. +type serverTask struct { + clientTask encoder encoding.ProtoMarshaler darcFactory arc.AccessControlFactory - darcAction } -// Consume implements basic.ServerAction. It writes the DARC into the page if it +// Consume implements basic.ServerTask. It writes the DARC into the page if it // is allowed to do so, otherwise it returns an error. -func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) error { +func (act serverTask) Consume(ctx basic.Context, page inventory.WritablePage) error { accesspb, err := act.encoder.Pack(act.access) if err != nil { return xerrors.Errorf("couldn't pack access: %v", err) @@ -119,25 +119,25 @@ func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) return nil } -type actionFactory struct { +type taskFactory struct { encoder encoding.ProtoMarshaler darcFactory arc.AccessControlFactory } -// NewActionFactory returns a new instance of the action factory. -func NewActionFactory() basic.ActionFactory { - return actionFactory{ +// NewTaskFactory returns a new instance of the task factory. +func NewTaskFactory() basic.TaskFactory { + return taskFactory{ encoder: encoding.NewProtoEncoder(), darcFactory: NewFactory(), } } -// FromProto implements basic.ActionFactory. It returns the server action of the +// FromProto implements basic.TaskFactory. It returns the server task of the // protobuf message when approriate, otherwise an error. -func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { - var pb *ActionProto +func (f taskFactory) FromProto(in proto.Message) (basic.ServerTask, error) { + var pb *Task switch msg := in.(type) { - case *ActionProto: + case *Task: pb = msg default: return nil, xerrors.Errorf("invalid message type '%T'", in) @@ -148,10 +148,10 @@ func (f actionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { return nil, xerrors.Errorf("couldn't decode access: %v", err) } - servAccess := serverAction{ + servAccess := serverTask{ encoder: f.encoder, darcFactory: f.darcFactory, - darcAction: darcAction{ + clientTask: clientTask{ key: pb.GetKey(), access: access.(Access), }, diff --git a/ledger/arc/darc/action_test.go b/ledger/arc/darc/task_test.go similarity index 63% rename from ledger/arc/darc/action_test.go rename to ledger/arc/darc/task_test.go index 7b7707c7c..42da0fe90 100644 --- a/ledger/arc/darc/action_test.go +++ b/ledger/arc/darc/task_test.go @@ -14,25 +14,25 @@ import ( "golang.org/x/xerrors" ) -func TestAction_Pack(t *testing.T) { - action := darcAction{ +func TestClientTask_Pack(t *testing.T) { + task := clientTask{ key: []byte{0x01}, access: NewAccess(), } - pb, err := action.Pack(encoding.NewProtoEncoder()) + pb, err := task.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) - require.IsType(t, (*ActionProto)(nil), pb) + require.IsType(t, (*Task)(nil), pb) - actionpb := pb.(*ActionProto) - require.Equal(t, action.key, actionpb.GetKey()) + taskpb := pb.(*Task) + require.Equal(t, task.key, taskpb.GetKey()) - _, err = action.Pack(fake.BadPackEncoder{}) + _, err = task.Pack(fake.BadPackEncoder{}) require.EqualError(t, err, "couldn't pack access: fake error") } -func TestAction_Fingerprint(t *testing.T) { - action := darcAction{ +func TestClientTask_Fingerprint(t *testing.T) { + task := clientTask{ key: []byte{0x01}, access: Access{rules: map[string]expression{ "\x02": {matches: map[string]struct{}{"\x03": {}}}, @@ -41,78 +41,78 @@ func TestAction_Fingerprint(t *testing.T) { buffer := new(bytes.Buffer) - err := action.Fingerprint(buffer, encoding.NewProtoEncoder()) + err := task.Fingerprint(buffer, encoding.NewProtoEncoder()) require.NoError(t, err) require.Equal(t, "\x01\x02\x03", buffer.String()) - err = action.Fingerprint(fake.NewBadHash(), nil) + err = task.Fingerprint(fake.NewBadHash(), nil) require.EqualError(t, err, "couldn't write key: fake error") - err = action.Fingerprint(fake.NewBadHashWithDelay(1), nil) + err = task.Fingerprint(fake.NewBadHashWithDelay(1), nil) require.EqualError(t, err, "couldn't fingerprint access: couldn't write key: fake error") } -func TestServerAction_Consume(t *testing.T) { +func TestServerTask_Consume(t *testing.T) { access, err := NewAccess().Evolve(UpdateAccessRule, fakeIdentity{buffer: []byte("doggy")}) require.NoError(t, err) - action := serverAction{ + task := serverTask{ encoder: encoding.NewProtoEncoder(), darcFactory: NewFactory(), - darcAction: darcAction{key: []byte{0x01}, access: access}, + clientTask: clientTask{key: []byte{0x01}, access: access}, } call := &fake.Call{} - err = action.Consume(fakeContext{}, fakePage{call: call}) + err = task.Consume(fakeContext{}, fakePage{call: call}) require.NoError(t, err) require.Equal(t, 1, call.Len()) // Key is provided so it's an update. require.Equal(t, []byte{0x01}, call.Get(0, 0)) // No key thus it's a creation. - action.darcAction.key = nil - err = action.Consume(fakeContext{}, fakePage{call: call}) + task.clientTask.key = nil + err = task.Consume(fakeContext{}, fakePage{call: call}) require.NoError(t, err) require.Equal(t, 2, call.Len()) require.Equal(t, []byte{0x34}, call.Get(1, 0)) - action.encoder = fake.BadPackEncoder{} - err = action.Consume(fakeContext{}, fakePage{}) + task.encoder = fake.BadPackEncoder{} + err = task.Consume(fakeContext{}, fakePage{}) require.EqualError(t, err, "couldn't pack access: fake error") - action.encoder = encoding.NewProtoEncoder() - err = action.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) + task.encoder = encoding.NewProtoEncoder() + err = task.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) require.EqualError(t, err, "couldn't write access: oops") - action.darcAction.key = []byte{0x01} - err = action.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) + task.clientTask.key = []byte{0x01} + err = task.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) require.EqualError(t, err, "couldn't read value: oops") - action.darcFactory = badArcFactory{} - err = action.Consume(fakeContext{}, fakePage{}) + task.darcFactory = badArcFactory{} + err = task.Consume(fakeContext{}, fakePage{}) require.EqualError(t, err, "couldn't decode access: oops") - action.darcFactory = NewFactory() - action.access.rules[UpdateAccessRule].matches["cat"] = struct{}{} - err = action.Consume(fakeContext{identity: []byte("cat")}, fakePage{}) + task.darcFactory = NewFactory() + task.access.rules[UpdateAccessRule].matches["cat"] = struct{}{} + err = task.Consume(fakeContext{identity: []byte("cat")}, fakePage{}) require.EqualError(t, err, "no access: couldn't match 'darc:update': couldn't match identity 'cat'") } -func TestActionFactory_FromProto(t *testing.T) { - factory := NewActionFactory().(actionFactory) +func TestTaskFactory_FromProto(t *testing.T) { + factory := NewTaskFactory().(taskFactory) - actionpb := &ActionProto{Key: []byte{0x02}, Access: &AccessProto{}} - action, err := factory.FromProto(actionpb) + taskpb := &Task{Key: []byte{0x02}, Access: &AccessProto{}} + task, err := factory.FromProto(taskpb) require.NoError(t, err) - require.IsType(t, serverAction{}, action) + require.IsType(t, serverTask{}, task) _, err = factory.FromProto(nil) require.EqualError(t, err, "invalid message type ''") factory.darcFactory = badArcFactory{} - _, err = factory.FromProto(&ActionProto{}) + _, err = factory.FromProto(&Task{}) require.EqualError(t, err, "couldn't decode access: oops") } diff --git a/ledger/byzcoin/contract/context.go b/ledger/byzcoin/contract/context.go index 7dc69c6ff..2d152e793 100644 --- a/ledger/byzcoin/contract/context.go +++ b/ledger/byzcoin/contract/context.go @@ -7,7 +7,7 @@ import ( "golang.org/x/xerrors" ) -type actionContext struct { +type taskContext struct { basic.Context arcFactory arc.AccessControlFactory page inventory.Page @@ -15,7 +15,7 @@ type actionContext struct { // GetArc implements Context. It returns the access control stored in the given // key if appropriate, otherwise an error. -func (ctx actionContext) GetArc(key []byte) (arc.AccessControl, error) { +func (ctx taskContext) GetArc(key []byte) (arc.AccessControl, error) { value, err := ctx.page.Read(key) if err != nil { return nil, xerrors.Errorf("couldn't read value: %v", err) @@ -31,7 +31,7 @@ func (ctx actionContext) GetArc(key []byte) (arc.AccessControl, error) { // Read implements Context. It returns the instance stored at the given key, or // an error if it does not find it. -func (ctx actionContext) Read(key []byte) (*Instance, error) { +func (ctx taskContext) Read(key []byte) (*Instance, error) { entry, err := ctx.page.Read(key) if err != nil { return nil, xerrors.Errorf("couldn't read the value: %v", err) @@ -50,18 +50,18 @@ func (ctx actionContext) Read(key []byte) (*Instance, error) { // transaction. type SpawnContext struct { Context - SpawnAction + SpawnTask } // InvokeContext is the context provided to a smart contract execution of an // invoke transaction. type InvokeContext struct { Context - InvokeAction + InvokeTask } // DeleteContext is the context to delete an instance. type DeleteContext struct { Context - DeleteAction + DeleteTask } diff --git a/ledger/byzcoin/contract/context_test.go b/ledger/byzcoin/contract/context_test.go index d54d0f663..3f58e31aa 100644 --- a/ledger/byzcoin/contract/context_test.go +++ b/ledger/byzcoin/contract/context_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestActionContext_GetArc(t *testing.T) { - ctx := actionContext{ +func TestTaskContext_GetArc(t *testing.T) { + ctx := taskContext{ arcFactory: &fakeAccessFactory{access: &fakeAccess{}}, page: fakePage{ store: map[string]proto.Message{"a": &empty.Empty{}}, @@ -25,8 +25,8 @@ func TestActionContext_GetArc(t *testing.T) { require.EqualError(t, err, "couldn't read value: not found") } -func TestActionContext_Read(t *testing.T) { - ctx := actionContext{ +func TestTaskContext_Read(t *testing.T) { + ctx := taskContext{ page: fakePage{ store: map[string]proto.Message{ "a": &Instance{ diff --git a/ledger/byzcoin/contract/messages.pb.go b/ledger/byzcoin/contract/messages.pb.go index 9702c7e30..e6b3e8985 100644 --- a/ledger/byzcoin/contract/messages.pb.go +++ b/ledger/byzcoin/contract/messages.pb.go @@ -92,7 +92,7 @@ func (m *Instance) GetAccessControl() []byte { return nil } -type SpawnActionProto struct { +type SpawnTaskProto struct { ContractID string `protobuf:"bytes,1,opt,name=contractID,proto3" json:"contractID,omitempty"` Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -100,46 +100,46 @@ type SpawnActionProto struct { XXX_sizecache int32 `json:"-"` } -func (m *SpawnActionProto) Reset() { *m = SpawnActionProto{} } -func (m *SpawnActionProto) String() string { return proto.CompactTextString(m) } -func (*SpawnActionProto) ProtoMessage() {} -func (*SpawnActionProto) Descriptor() ([]byte, []int) { +func (m *SpawnTaskProto) Reset() { *m = SpawnTaskProto{} } +func (m *SpawnTaskProto) String() string { return proto.CompactTextString(m) } +func (*SpawnTaskProto) ProtoMessage() {} +func (*SpawnTaskProto) Descriptor() ([]byte, []int) { return fileDescriptor_4dc296cbfe5ffcd5, []int{1} } -func (m *SpawnActionProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_SpawnActionProto.Unmarshal(m, b) +func (m *SpawnTaskProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SpawnTaskProto.Unmarshal(m, b) } -func (m *SpawnActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_SpawnActionProto.Marshal(b, m, deterministic) +func (m *SpawnTaskProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SpawnTaskProto.Marshal(b, m, deterministic) } -func (m *SpawnActionProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_SpawnActionProto.Merge(m, src) +func (m *SpawnTaskProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_SpawnTaskProto.Merge(m, src) } -func (m *SpawnActionProto) XXX_Size() int { - return xxx_messageInfo_SpawnActionProto.Size(m) +func (m *SpawnTaskProto) XXX_Size() int { + return xxx_messageInfo_SpawnTaskProto.Size(m) } -func (m *SpawnActionProto) XXX_DiscardUnknown() { - xxx_messageInfo_SpawnActionProto.DiscardUnknown(m) +func (m *SpawnTaskProto) XXX_DiscardUnknown() { + xxx_messageInfo_SpawnTaskProto.DiscardUnknown(m) } -var xxx_messageInfo_SpawnActionProto proto.InternalMessageInfo +var xxx_messageInfo_SpawnTaskProto proto.InternalMessageInfo -func (m *SpawnActionProto) GetContractID() string { +func (m *SpawnTaskProto) GetContractID() string { if m != nil { return m.ContractID } return "" } -func (m *SpawnActionProto) GetArgument() *any.Any { +func (m *SpawnTaskProto) GetArgument() *any.Any { if m != nil { return m.Argument } return nil } -type InvokeActionProto struct { +type InvokeTaskProto struct { Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Argument *any.Any `protobuf:"bytes,2,opt,name=argument,proto3" json:"argument,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -147,78 +147,78 @@ type InvokeActionProto struct { XXX_sizecache int32 `json:"-"` } -func (m *InvokeActionProto) Reset() { *m = InvokeActionProto{} } -func (m *InvokeActionProto) String() string { return proto.CompactTextString(m) } -func (*InvokeActionProto) ProtoMessage() {} -func (*InvokeActionProto) Descriptor() ([]byte, []int) { +func (m *InvokeTaskProto) Reset() { *m = InvokeTaskProto{} } +func (m *InvokeTaskProto) String() string { return proto.CompactTextString(m) } +func (*InvokeTaskProto) ProtoMessage() {} +func (*InvokeTaskProto) Descriptor() ([]byte, []int) { return fileDescriptor_4dc296cbfe5ffcd5, []int{2} } -func (m *InvokeActionProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_InvokeActionProto.Unmarshal(m, b) +func (m *InvokeTaskProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InvokeTaskProto.Unmarshal(m, b) } -func (m *InvokeActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_InvokeActionProto.Marshal(b, m, deterministic) +func (m *InvokeTaskProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InvokeTaskProto.Marshal(b, m, deterministic) } -func (m *InvokeActionProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_InvokeActionProto.Merge(m, src) +func (m *InvokeTaskProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_InvokeTaskProto.Merge(m, src) } -func (m *InvokeActionProto) XXX_Size() int { - return xxx_messageInfo_InvokeActionProto.Size(m) +func (m *InvokeTaskProto) XXX_Size() int { + return xxx_messageInfo_InvokeTaskProto.Size(m) } -func (m *InvokeActionProto) XXX_DiscardUnknown() { - xxx_messageInfo_InvokeActionProto.DiscardUnknown(m) +func (m *InvokeTaskProto) XXX_DiscardUnknown() { + xxx_messageInfo_InvokeTaskProto.DiscardUnknown(m) } -var xxx_messageInfo_InvokeActionProto proto.InternalMessageInfo +var xxx_messageInfo_InvokeTaskProto proto.InternalMessageInfo -func (m *InvokeActionProto) GetKey() []byte { +func (m *InvokeTaskProto) GetKey() []byte { if m != nil { return m.Key } return nil } -func (m *InvokeActionProto) GetArgument() *any.Any { +func (m *InvokeTaskProto) GetArgument() *any.Any { if m != nil { return m.Argument } return nil } -type DeleteActionProto struct { +type DeleteTaskProto struct { Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *DeleteActionProto) Reset() { *m = DeleteActionProto{} } -func (m *DeleteActionProto) String() string { return proto.CompactTextString(m) } -func (*DeleteActionProto) ProtoMessage() {} -func (*DeleteActionProto) Descriptor() ([]byte, []int) { +func (m *DeleteTaskProto) Reset() { *m = DeleteTaskProto{} } +func (m *DeleteTaskProto) String() string { return proto.CompactTextString(m) } +func (*DeleteTaskProto) ProtoMessage() {} +func (*DeleteTaskProto) Descriptor() ([]byte, []int) { return fileDescriptor_4dc296cbfe5ffcd5, []int{3} } -func (m *DeleteActionProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DeleteActionProto.Unmarshal(m, b) +func (m *DeleteTaskProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DeleteTaskProto.Unmarshal(m, b) } -func (m *DeleteActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DeleteActionProto.Marshal(b, m, deterministic) +func (m *DeleteTaskProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DeleteTaskProto.Marshal(b, m, deterministic) } -func (m *DeleteActionProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_DeleteActionProto.Merge(m, src) +func (m *DeleteTaskProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeleteTaskProto.Merge(m, src) } -func (m *DeleteActionProto) XXX_Size() int { - return xxx_messageInfo_DeleteActionProto.Size(m) +func (m *DeleteTaskProto) XXX_Size() int { + return xxx_messageInfo_DeleteTaskProto.Size(m) } -func (m *DeleteActionProto) XXX_DiscardUnknown() { - xxx_messageInfo_DeleteActionProto.DiscardUnknown(m) +func (m *DeleteTaskProto) XXX_DiscardUnknown() { + xxx_messageInfo_DeleteTaskProto.DiscardUnknown(m) } -var xxx_messageInfo_DeleteActionProto proto.InternalMessageInfo +var xxx_messageInfo_DeleteTaskProto proto.InternalMessageInfo -func (m *DeleteActionProto) GetKey() []byte { +func (m *DeleteTaskProto) GetKey() []byte { if m != nil { return m.Key } @@ -227,9 +227,9 @@ func (m *DeleteActionProto) GetKey() []byte { func init() { proto.RegisterType((*Instance)(nil), "contract.Instance") - proto.RegisterType((*SpawnActionProto)(nil), "contract.SpawnActionProto") - proto.RegisterType((*InvokeActionProto)(nil), "contract.InvokeActionProto") - proto.RegisterType((*DeleteActionProto)(nil), "contract.DeleteActionProto") + proto.RegisterType((*SpawnTaskProto)(nil), "contract.SpawnTaskProto") + proto.RegisterType((*InvokeTaskProto)(nil), "contract.InvokeTaskProto") + proto.RegisterType((*DeleteTaskProto)(nil), "contract.DeleteTaskProto") } func init() { @@ -237,21 +237,21 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 255 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x4f, 0x4b, 0xc3, 0x40, - 0x10, 0xc5, 0x59, 0x6b, 0x35, 0x8e, 0x7f, 0x68, 0x17, 0x0f, 0xab, 0x07, 0x09, 0x41, 0x21, 0x78, - 0x48, 0x45, 0x3f, 0x41, 0xb1, 0x97, 0xdc, 0x24, 0x1e, 0x3c, 0x6f, 0x37, 0x63, 0x90, 0x6e, 0x67, - 0x4a, 0x76, 0x53, 0xc9, 0x37, 0xf2, 0x63, 0x4a, 0x37, 0x44, 0x5a, 0x0a, 0x05, 0x6f, 0xbb, 0x8f, - 0x37, 0xef, 0x37, 0x6f, 0xe0, 0x6a, 0x89, 0xce, 0xe9, 0x0a, 0x5d, 0xb6, 0xaa, 0xd9, 0xb3, 0x8c, - 0x0c, 0x93, 0xaf, 0xb5, 0xf1, 0xb7, 0x37, 0x15, 0x73, 0x65, 0x71, 0x12, 0xf4, 0x79, 0xf3, 0x39, - 0xd1, 0xd4, 0x76, 0xa6, 0xe4, 0x47, 0x40, 0x94, 0x93, 0xf3, 0x9a, 0x0c, 0xca, 0x11, 0x0c, 0x16, - 0xd8, 0x2a, 0x11, 0x8b, 0xf4, 0xa2, 0xd8, 0x3c, 0xe5, 0x23, 0x0c, 0xd7, 0xda, 0x36, 0xa8, 0x8e, - 0x62, 0x91, 0x9e, 0x3f, 0x5f, 0x67, 0x5d, 0x52, 0xd6, 0x27, 0x65, 0x53, 0x6a, 0x8b, 0xce, 0x22, - 0xef, 0x00, 0x7a, 0x62, 0x3e, 0x53, 0x83, 0x58, 0xa4, 0x67, 0xc5, 0x96, 0x22, 0x15, 0x9c, 0x96, - 0x68, 0xd1, 0x63, 0xa9, 0x8e, 0x63, 0x91, 0x46, 0x45, 0xff, 0x95, 0xf7, 0x70, 0xa9, 0x8d, 0x41, - 0xe7, 0x5e, 0x37, 0x6e, 0xb6, 0x6a, 0x18, 0x36, 0xd8, 0x15, 0x93, 0x12, 0x46, 0xef, 0x2b, 0xfd, - 0x4d, 0x53, 0xe3, 0xbf, 0x98, 0xde, 0x42, 0xc7, 0x5d, 0xa6, 0xd8, 0x63, 0x3e, 0x41, 0xa4, 0xeb, - 0xaa, 0x59, 0x22, 0xf9, 0x83, 0x15, 0xfe, 0x5c, 0xc9, 0x07, 0x8c, 0x73, 0x5a, 0xf3, 0x02, 0xb7, - 0x31, 0xfb, 0x87, 0xf9, 0x7f, 0xf0, 0x03, 0x8c, 0x67, 0xa1, 0xef, 0xc1, 0xe0, 0xf9, 0x49, 0x18, - 0x7f, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x55, 0x4b, 0xd8, 0xdd, 0xce, 0x01, 0x00, 0x00, + // 254 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0xc1, 0x4a, 0xc3, 0x40, + 0x10, 0x86, 0x59, 0x6b, 0x35, 0x8e, 0xda, 0xca, 0xe2, 0x61, 0xf5, 0x20, 0x21, 0x7a, 0x08, 0x1e, + 0x52, 0xd1, 0x27, 0x10, 0x7b, 0xc9, 0x4d, 0xa2, 0x3e, 0xc0, 0x64, 0x3b, 0xe6, 0x90, 0x74, 0xb6, + 0x64, 0x37, 0x95, 0xbc, 0x91, 0x8f, 0x29, 0xdd, 0x10, 0xb5, 0x14, 0x02, 0xbd, 0xed, 0xfe, 0xfc, + 0xf3, 0x7f, 0xf3, 0x0f, 0x4c, 0x96, 0x64, 0x2d, 0x16, 0x64, 0x93, 0x55, 0x6d, 0x9c, 0x91, 0x81, + 0x36, 0xec, 0x6a, 0xd4, 0xee, 0xfa, 0xaa, 0x30, 0xa6, 0xa8, 0x68, 0xe6, 0xf5, 0xbc, 0xf9, 0x9c, + 0x21, 0xb7, 0x9d, 0x29, 0xfa, 0x16, 0x10, 0xa4, 0x6c, 0x1d, 0xb2, 0x26, 0x79, 0x01, 0xa3, 0x92, + 0x5a, 0x25, 0x42, 0x11, 0x9f, 0x65, 0x9b, 0xa7, 0xbc, 0x87, 0xf1, 0x1a, 0xab, 0x86, 0xd4, 0x41, + 0x28, 0xe2, 0xd3, 0xc7, 0xcb, 0xa4, 0x4b, 0x4a, 0xfa, 0xa4, 0xe4, 0x99, 0xdb, 0xac, 0xb3, 0xc8, + 0x1b, 0x80, 0x9e, 0x98, 0xce, 0xd5, 0x28, 0x14, 0xf1, 0x49, 0xf6, 0x4f, 0x91, 0x0a, 0x8e, 0x17, + 0x54, 0x91, 0xa3, 0x85, 0x3a, 0x0c, 0x45, 0x1c, 0x64, 0xfd, 0x57, 0xde, 0xc1, 0x39, 0x6a, 0x4d, + 0xd6, 0xbe, 0x6c, 0xdc, 0xa6, 0x52, 0x63, 0xbf, 0xc1, 0xb6, 0x18, 0xe5, 0x30, 0x79, 0x5b, 0xe1, + 0x17, 0xbf, 0xa3, 0x2d, 0x5f, 0x7d, 0xc3, 0x6d, 0xa2, 0xd8, 0x21, 0x3e, 0x40, 0x80, 0x75, 0xd1, + 0x2c, 0x89, 0xdd, 0x60, 0x81, 0x5f, 0x57, 0xf4, 0x01, 0xd3, 0x94, 0xd7, 0xa6, 0xa4, 0x3f, 0xc8, + 0xee, 0x51, 0xf6, 0x8f, 0xbd, 0x85, 0xe9, 0xdc, 0x77, 0x1d, 0x88, 0xcd, 0x8f, 0xfc, 0xf0, 0xd3, + 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x88, 0xcd, 0x32, 0xc8, 0x01, 0x00, 0x00, } diff --git a/ledger/byzcoin/contract/messages.proto b/ledger/byzcoin/contract/messages.proto index eb2103a51..7c09b0ff4 100644 --- a/ledger/byzcoin/contract/messages.proto +++ b/ledger/byzcoin/contract/messages.proto @@ -12,16 +12,16 @@ message Instance { bytes accessControl = 5; } -message SpawnActionProto { +message SpawnTaskProto { string contractID = 1; google.protobuf.Any argument = 2; } -message InvokeActionProto { +message InvokeTaskProto { bytes key = 1; google.protobuf.Any argument = 2; } -message DeleteActionProto { +message DeleteTaskProto { bytes key = 1; } diff --git a/ledger/byzcoin/contract/mod.go b/ledger/byzcoin/contract/mod.go index 595393454..c644825df 100644 --- a/ledger/byzcoin/contract/mod.go +++ b/ledger/byzcoin/contract/mod.go @@ -1,4 +1,5 @@ -// Package contract is for static smart contracts. +// Package contract is a smart contract abstraction that is using the +// transaction tasks to define what a contract can execute and how. package contract import ( diff --git a/ledger/byzcoin/contract/mod_test.go b/ledger/byzcoin/contract/mod_test.go index 119c3dfcd..1bfa8ce17 100644 --- a/ledger/byzcoin/contract/mod_test.go +++ b/ledger/byzcoin/contract/mod_test.go @@ -10,9 +10,9 @@ import ( func TestMessages(t *testing.T) { messages := []proto.Message{ &Instance{}, - &SpawnActionProto{}, - &InvokeActionProto{}, - &DeleteActionProto{}, + &SpawnTaskProto{}, + &InvokeTaskProto{}, + &DeleteTaskProto{}, } for _, m := range messages { diff --git a/ledger/byzcoin/contract/action.go b/ledger/byzcoin/contract/task.go similarity index 69% rename from ledger/byzcoin/contract/action.go rename to ledger/byzcoin/contract/task.go index 328227a5f..47133cc88 100644 --- a/ledger/byzcoin/contract/action.go +++ b/ledger/byzcoin/contract/task.go @@ -13,20 +13,20 @@ import ( "golang.org/x/xerrors" ) -// SpawnAction is a contract transaction action to create a new instance. -type SpawnAction struct { +// SpawnTask is a client task of a transaction to create a new instance. +type SpawnTask struct { ContractID string Argument proto.Message } // Pack implements encoding.Packable. -func (act SpawnAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { +func (act SpawnTask) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { argument, err := enc.MarshalAny(act.Argument) if err != nil { return nil, xerrors.Errorf("couldn't pack argument: %v", err) } - pb := &SpawnActionProto{ + pb := &SpawnTaskProto{ ContractID: act.ContractID, Argument: argument, } @@ -35,7 +35,7 @@ func (act SpawnAction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) } // Fingerprint implements encoding.Fingerprinter. -func (act SpawnAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { +func (act SpawnTask) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { _, err := w.Write([]byte(act.ContractID)) if err != nil { return xerrors.Errorf("couldn't write contract: %v", err) @@ -49,21 +49,21 @@ func (act SpawnAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error return nil } -// InvokeAction is a contract transaction action to update an existing instance -// of the access control allows it. -type InvokeAction struct { +// InvokeTask is a client task of a transaction to update an existing instance +// if the access rights control allows it. +type InvokeTask struct { Key []byte Argument proto.Message } // Pack implements encoding.Packable. -func (act InvokeAction) Pack(e encoding.ProtoMarshaler) (proto.Message, error) { +func (act InvokeTask) Pack(e encoding.ProtoMarshaler) (proto.Message, error) { argument, err := e.MarshalAny(act.Argument) if err != nil { return nil, xerrors.Errorf("couldn't pack argument: %v", err) } - pb := &InvokeActionProto{ + pb := &InvokeTaskProto{ Key: act.Key, Argument: argument, } @@ -72,7 +72,7 @@ func (act InvokeAction) Pack(e encoding.ProtoMarshaler) (proto.Message, error) { } // Fingerprint implements encoding.Fingeprinter. -func (act InvokeAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { +func (act InvokeTask) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { _, err := w.Write(act.Key) if err != nil { return xerrors.Errorf("couldn't write key: %v", err) @@ -86,19 +86,19 @@ func (act InvokeAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) erro return nil } -// DeleteAction is a contract transaction action to mark an instance as deleted +// DeleteTask is a client task of a transaction to mark an instance as deleted // so that it cannot be updated anymore. -type DeleteAction struct { +type DeleteTask struct { Key []byte } // Pack implements encoding.Packable. -func (a DeleteAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { - return &DeleteActionProto{Key: a.Key}, nil +func (a DeleteTask) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &DeleteTaskProto{Key: a.Key}, nil } // Fingerprint implements encoding.Fingerprinter. -func (a DeleteAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { +func (a DeleteTask) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error { _, err := w.Write(a.Key) if err != nil { return xerrors.Errorf("couldn't write key: %v", err) @@ -107,15 +107,15 @@ func (a DeleteAction) Fingerprint(w io.Writer, e encoding.ProtoMarshaler) error return nil } -type serverAction struct { - basic.ClientAction +type serverTask struct { + basic.ClientTask contracts map[string]Contract arcFactory arc.AccessControlFactory encoder encoding.ProtoMarshaler } -func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) error { - txCtx := actionContext{ +func (act serverTask) Consume(ctx basic.Context, page inventory.WritablePage) error { + txCtx := taskContext{ Context: ctx, arcFactory: act.arcFactory, page: page, @@ -123,24 +123,24 @@ func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) var instance *Instance var err error - switch action := act.ClientAction.(type) { - case SpawnAction: + switch task := act.ClientTask.(type) { + case SpawnTask: instance, err = act.consumeSpawn(SpawnContext{ - Context: txCtx, - SpawnAction: action, + Context: txCtx, + SpawnTask: task, }) - case InvokeAction: + case InvokeTask: instance, err = act.consumeInvoke(InvokeContext{ - Context: txCtx, - InvokeAction: action, + Context: txCtx, + InvokeTask: task, }) - case DeleteAction: + case DeleteTask: instance, err = act.consumeDelete(DeleteContext{ - Context: txCtx, - DeleteAction: action, + Context: txCtx, + DeleteTask: task, }) default: - return xerrors.Errorf("invalid action type '%T'", act.ClientAction) + return xerrors.Errorf("invalid task type '%T'", act.ClientTask) } if err != nil { @@ -156,7 +156,7 @@ func (act serverAction) Consume(ctx basic.Context, page inventory.WritablePage) return nil } -func (act serverAction) consumeSpawn(ctx SpawnContext) (*Instance, error) { +func (act serverTask) consumeSpawn(ctx SpawnContext) (*Instance, error) { _, err := ctx.Read(ctx.GetID()) if err == nil { return nil, xerrors.New("instance already exists") @@ -195,7 +195,7 @@ func (act serverAction) consumeSpawn(ctx SpawnContext) (*Instance, error) { return instance, nil } -func (act serverAction) consumeInvoke(ctx InvokeContext) (*Instance, error) { +func (act serverTask) consumeInvoke(ctx InvokeContext) (*Instance, error) { instance, err := ctx.Read(ctx.Key) if err != nil { return nil, xerrors.Errorf("couldn't read the instance: %v", err) @@ -228,7 +228,7 @@ func (act serverAction) consumeInvoke(ctx InvokeContext) (*Instance, error) { return instance, nil } -func (act serverAction) consumeDelete(ctx DeleteContext) (*Instance, error) { +func (act serverTask) consumeDelete(ctx DeleteContext) (*Instance, error) { instance, err := ctx.Read(ctx.Key) if err != nil { return nil, xerrors.Errorf("couldn't read the instance: %v", err) @@ -239,7 +239,7 @@ func (act serverAction) consumeDelete(ctx DeleteContext) (*Instance, error) { return instance, nil } -func (act serverAction) hasAccess(ctx Context, key []byte, rule string) error { +func (act serverTask) hasAccess(ctx Context, key []byte, rule string) error { access, err := ctx.GetArc(key) if err != nil { return xerrors.Errorf("couldn't read access: %v", err) @@ -254,17 +254,17 @@ func (act serverAction) hasAccess(ctx Context, key []byte, rule string) error { return nil } -// ActionFactory is a factory to decode protobuf messages into contract actions +// TaskFactory is a factory to decode protobuf messages into transaction tasks // and register static contracts. -type ActionFactory struct { +type TaskFactory struct { contracts map[string]Contract arcFactory arc.AccessControlFactory encoder encoding.ProtoMarshaler } -// NewActionFactory returns a new empty instance of the factory. -func NewActionFactory() ActionFactory { - return ActionFactory{ +// NewTaskFactory returns a new empty instance of the factory. +func NewTaskFactory() TaskFactory { + return TaskFactory{ contracts: make(map[string]Contract), arcFactory: common.NewAccessControlFactory(), encoder: encoding.NewProtoEncoder(), @@ -273,13 +273,13 @@ func NewActionFactory() ActionFactory { // Register registers the contract using the name as the identifier. If an // identifier already exists, it will be overwritten. -func (f ActionFactory) Register(name string, contract Contract) { +func (f TaskFactory) Register(name string, contract Contract) { f.contracts[name] = contract } -// FromProto implements basic.ActionFactory. It returns the server action of a +// FromProto implements basic.TaskFactory. It returns the server task of a // protobuf message when appropriate, otherwise an error. -func (f ActionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { +func (f TaskFactory) FromProto(in proto.Message) (basic.ServerTask, error) { inAny, ok := in.(*any.Any) if ok { var err error @@ -289,30 +289,30 @@ func (f ActionFactory) FromProto(in proto.Message) (basic.ServerAction, error) { } } - action := serverAction{ + task := serverTask{ contracts: f.contracts, arcFactory: f.arcFactory, encoder: f.encoder, } switch pb := in.(type) { - case *SpawnActionProto: - action.ClientAction = SpawnAction{ + case *SpawnTaskProto: + task.ClientTask = SpawnTask{ ContractID: pb.GetContractID(), Argument: pb.GetArgument(), } - case *InvokeActionProto: - action.ClientAction = InvokeAction{ + case *InvokeTaskProto: + task.ClientTask = InvokeTask{ Key: pb.GetKey(), Argument: pb.GetArgument(), } - case *DeleteActionProto: - action.ClientAction = DeleteAction{ + case *DeleteTaskProto: + task.ClientTask = DeleteTask{ Key: pb.GetKey(), } default: return nil, xerrors.Errorf("invalid message type '%T'", in) } - return action, nil + return task, nil } diff --git a/ledger/byzcoin/contract/action_test.go b/ledger/byzcoin/contract/task_test.go similarity index 58% rename from ledger/byzcoin/contract/action_test.go rename to ledger/byzcoin/contract/task_test.go index 16898d527..2c2d0b82f 100644 --- a/ledger/byzcoin/contract/action_test.go +++ b/ledger/byzcoin/contract/task_test.go @@ -15,26 +15,26 @@ import ( "golang.org/x/xerrors" ) -func TestSpawnAction_Pack(t *testing.T) { - action := SpawnAction{ +func TestSpawnTask_Pack(t *testing.T) { + task := SpawnTask{ ContractID: "deadbeef", Argument: &empty.Empty{}, } - pb, err := action.Pack(encoding.NewProtoEncoder()) + pb, err := task.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) - require.IsType(t, (*SpawnActionProto)(nil), pb) + require.IsType(t, (*SpawnTaskProto)(nil), pb) - actionpb := pb.(*SpawnActionProto) - require.Equal(t, action.ContractID, actionpb.GetContractID()) - require.True(t, ptypes.Is(actionpb.GetArgument(), action.Argument)) + taskpb := pb.(*SpawnTaskProto) + require.Equal(t, task.ContractID, taskpb.GetContractID()) + require.True(t, ptypes.Is(taskpb.GetArgument(), task.Argument)) - _, err = action.Pack(fake.BadMarshalAnyEncoder{}) + _, err = task.Pack(fake.BadMarshalAnyEncoder{}) require.EqualError(t, err, "couldn't pack argument: fake error") } -func TestSpawnAction_Fingerprint(t *testing.T) { - action := SpawnAction{ +func TestSpawnTask_Fingerprint(t *testing.T) { + task := SpawnTask{ ContractID: "deadbeef", Argument: &empty.Empty{}, } @@ -42,37 +42,37 @@ func TestSpawnAction_Fingerprint(t *testing.T) { buffer := new(bytes.Buffer) encoder := encoding.NewProtoEncoder() - err := action.Fingerprint(buffer, encoder) + err := task.Fingerprint(buffer, encoder) require.NoError(t, err) require.Equal(t, "deadbeef{}", buffer.String()) - err = action.Fingerprint(fake.NewBadHash(), encoder) + err = task.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write contract: fake error") - err = action.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) + err = task.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) require.EqualError(t, err, "couldn't write argument: fake error") } -func TestInvokeAction_Pack(t *testing.T) { - action := InvokeAction{ +func TestInvokeTask_Pack(t *testing.T) { + task := InvokeTask{ Key: []byte{0x01}, Argument: &empty.Empty{}, } - pb, err := action.Pack(encoding.NewProtoEncoder()) + pb, err := task.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) - require.IsType(t, (*InvokeActionProto)(nil), pb) + require.IsType(t, (*InvokeTaskProto)(nil), pb) - actionpb := pb.(*InvokeActionProto) - require.Equal(t, action.Key, actionpb.GetKey()) - require.True(t, ptypes.Is(actionpb.GetArgument(), action.Argument)) + taskpb := pb.(*InvokeTaskProto) + require.Equal(t, task.Key, taskpb.GetKey()) + require.True(t, ptypes.Is(taskpb.GetArgument(), task.Argument)) - _, err = action.Pack(fake.BadMarshalAnyEncoder{}) + _, err = task.Pack(fake.BadMarshalAnyEncoder{}) require.EqualError(t, err, "couldn't pack argument: fake error") } -func TestInvokeAction_WriteTo(t *testing.T) { - action := InvokeAction{ +func TestInvokeTask_WriteTo(t *testing.T) { + task := InvokeTask{ Key: []byte{0x01}, Argument: &empty.Empty{}, } @@ -80,58 +80,58 @@ func TestInvokeAction_WriteTo(t *testing.T) { buffer := new(bytes.Buffer) encoder := encoding.NewProtoEncoder() - err := action.Fingerprint(buffer, encoder) + err := task.Fingerprint(buffer, encoder) require.NoError(t, err) require.Equal(t, "\x01{}", buffer.String()) - err = action.Fingerprint(fake.NewBadHash(), encoder) + err = task.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write key: fake error") - err = action.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) + err = task.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) require.EqualError(t, err, "couldn't write argument: fake error") } -func TestDeleteAction_Pack(t *testing.T) { - action := DeleteAction{ +func TestDeleteTask_Pack(t *testing.T) { + task := DeleteTask{ Key: []byte{0x01}, } - pb, err := action.Pack(encoding.NewProtoEncoder()) + pb, err := task.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) - require.IsType(t, (*DeleteActionProto)(nil), pb) + require.IsType(t, (*DeleteTaskProto)(nil), pb) - actionpb := pb.(*DeleteActionProto) - require.Equal(t, action.Key, actionpb.GetKey()) + taskpb := pb.(*DeleteTaskProto) + require.Equal(t, task.Key, taskpb.GetKey()) } -func TestDeleteAction_WriteTo(t *testing.T) { - action := DeleteAction{ +func TestDeleteTask_WriteTo(t *testing.T) { + task := DeleteTask{ Key: []byte{0x01}, } buffer := new(bytes.Buffer) encoder := encoding.NewProtoEncoder() - err := action.Fingerprint(buffer, encoder) + err := task.Fingerprint(buffer, encoder) require.NoError(t, err) require.Equal(t, "\x01", buffer.String()) - err = action.Fingerprint(fake.NewBadHash(), encoder) + err = task.Fingerprint(fake.NewBadHash(), encoder) require.EqualError(t, err, "couldn't write key: fake error") } -func TestServerAction_Consume(t *testing.T) { +func TestServerTask_Consume(t *testing.T) { factory := &fakeAccessFactory{access: &fakeAccess{match: true}} contracts := map[string]Contract{ "fake": fakeContract{}, "bad": fakeContract{err: xerrors.New("oops")}, } - action := serverAction{ - ClientAction: SpawnAction{ContractID: "fake"}, - contracts: contracts, - arcFactory: factory, - encoder: encoding.NewProtoEncoder(), + task := serverTask{ + ClientTask: SpawnTask{ContractID: "fake"}, + contracts: contracts, + arcFactory: factory, + encoder: encoding.NewProtoEncoder(), } page := fakePage{ @@ -143,98 +143,98 @@ func TestServerAction_Consume(t *testing.T) { }, } - // 1. Consume a spawn action. - err := action.Consume(fakeContext{id: []byte("b")}, page) + // 1. Consume a spawn task. + err := task.Consume(fakeContext{id: []byte("b")}, page) require.NoError(t, err) - err = action.Consume(fakeContext{id: []byte("a")}, page) + err = task.Consume(fakeContext{id: []byte("a")}, page) require.EqualError(t, err, "instance already exists") - action.ClientAction = SpawnAction{ContractID: "unknown"} - err = action.Consume(fakeContext{}, page) + task.ClientTask = SpawnTask{ContractID: "unknown"} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "contract 'unknown' not found") - action.ClientAction = SpawnAction{ContractID: "bad"} - err = action.Consume(fakeContext{}, page) + task.ClientTask = SpawnTask{ContractID: "bad"} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't execute spawn: oops") - action.ClientAction = SpawnAction{ContractID: "fake"} + task.ClientTask = SpawnTask{ContractID: "fake"} factory.err = xerrors.New("oops") - err = action.Consume(fakeContext{}, page) + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "no access: couldn't read access: couldn't decode access: oops") factory.err = nil - action.encoder = fake.BadMarshalAnyEncoder{} - err = action.Consume(fakeContext{}, page) + task.encoder = fake.BadMarshalAnyEncoder{} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't pack value: fake error") - // 2. Consume an invoke transaction. - action.encoder = encoding.NewProtoEncoder() - action.ClientAction = InvokeAction{Key: []byte("b")} + // 2. Consume an invoke task. + task.encoder = encoding.NewProtoEncoder() + task.ClientTask = InvokeTask{Key: []byte("b")} factory.access.calls = make([][]interface{}, 0) - err = action.Consume(fakeContext{}, page) + err = task.Consume(fakeContext{}, page) require.NoError(t, err) require.Len(t, factory.access.calls, 1) require.Equal(t, []arc.Identity{fake.PublicKey{}}, factory.access.calls[0][0]) require.Equal(t, arc.Compile("fake", "invoke"), factory.access.calls[0][1]) - action.ClientAction = InvokeAction{Key: []byte("c")} - err = action.Consume(fakeContext{}, page) + task.ClientTask = InvokeTask{Key: []byte("c")} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't read the instance: couldn't read the value: not found") - action.ClientAction = InvokeAction{Key: []byte("z")} - err = action.Consume(fakeContext{}, page) + task.ClientTask = InvokeTask{Key: []byte("z")} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "contract 'unknown' not found") - action.ClientAction = InvokeAction{Key: []byte("b")} + task.ClientTask = InvokeTask{Key: []byte("b")} factory.err = xerrors.New("oops") - err = action.Consume(fakeContext{}, page) + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "no access: couldn't read access: couldn't decode access: oops") factory.err = nil factory.access.match = false - err = action.Consume(fakeContext{}, page) + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "no access: fake.PublicKey is refused to 'fake:invoke' by fakeAccessControl: not authorized") factory.access.match = true - action.ClientAction = InvokeAction{Key: []byte("y")} - err = action.Consume(fakeContext{}, page) + task.ClientTask = InvokeTask{Key: []byte("y")} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't invoke: oops") - action.ClientAction = InvokeAction{Key: []byte("a")} - action.encoder = fake.BadMarshalAnyEncoder{} - err = action.Consume(fakeContext{}, page) + task.ClientTask = InvokeTask{Key: []byte("a")} + task.encoder = fake.BadMarshalAnyEncoder{} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't pack value: fake error") - // 3. Consume a delete transaction. - action.ClientAction = DeleteAction{Key: []byte("a")} + // 3. Consume a delete task. + task.ClientTask = DeleteTask{Key: []byte("a")} - err = action.Consume(fakeContext{}, page) + err = task.Consume(fakeContext{}, page) require.NoError(t, err) - action.ClientAction = DeleteAction{Key: []byte("c")} - err = action.Consume(fakeContext{}, page) + task.ClientTask = DeleteTask{Key: []byte("c")} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't read the instance: couldn't read the value: not found") - // 4. Consume an invalid transaction. + // 4. Consume an invalid task. page.err = xerrors.New("oops") - action.ClientAction = DeleteAction{Key: []byte("a")} - err = action.Consume(fakeContext{}, page) + task.ClientTask = DeleteTask{Key: []byte("a")} + err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't write instance to page: oops") - action.ClientAction = nil - err = action.Consume(fakeContext{}, page) - require.EqualError(t, err, "invalid action type ''") + task.ClientTask = nil + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "invalid task type ''") } -func TestActionFactory_Register(t *testing.T) { - factory := NewActionFactory() +func TestTaskFactory_Register(t *testing.T) { + factory := NewTaskFactory() factory.Register("a", fakeContract{}) factory.Register("b", fakeContract{}) @@ -244,29 +244,29 @@ func TestActionFactory_Register(t *testing.T) { require.Len(t, factory.contracts, 2) } -func TestActionFactory_FromProto(t *testing.T) { - factory := NewActionFactory() +func TestTaskFactory_FromProto(t *testing.T) { + factory := NewTaskFactory() - spawnpb := &SpawnActionProto{ContractID: "A"} - action, err := factory.FromProto(spawnpb) + spawnpb := &SpawnTaskProto{ContractID: "A"} + task, err := factory.FromProto(spawnpb) require.NoError(t, err) - require.NotNil(t, action) + require.NotNil(t, task) spawnAny, err := ptypes.MarshalAny(spawnpb) require.NoError(t, err) - action, err = factory.FromProto(spawnAny) + task, err = factory.FromProto(spawnAny) require.NoError(t, err) - require.NotNil(t, action) + require.NotNil(t, task) - invokepb := &InvokeActionProto{Key: []byte{0x01}} - action, err = factory.FromProto(invokepb) + invokepb := &InvokeTaskProto{Key: []byte{0x01}} + task, err = factory.FromProto(invokepb) require.NoError(t, err) - require.NotNil(t, action) + require.NotNil(t, task) - deletepb := &DeleteActionProto{Key: []byte{0x01}} - action, err = factory.FromProto(deletepb) + deletepb := &DeleteTaskProto{Key: []byte{0x01}} + task, err = factory.FromProto(deletepb) require.NoError(t, err) - require.NotNil(t, action) + require.NotNil(t, task) _, err = factory.FromProto(nil) require.EqualError(t, err, "invalid message type ''") diff --git a/ledger/byzcoin/roster/messages.pb.go b/ledger/byzcoin/roster/messages.pb.go index 7c7e11b53..5ea8502e7 100644 --- a/ledger/byzcoin/roster/messages.pb.go +++ b/ledger/byzcoin/roster/messages.pb.go @@ -108,39 +108,39 @@ func (m *ChangeSet) GetRemove() []uint32 { return nil } -type ActionProto struct { +type Task struct { Remove []uint32 `protobuf:"varint,1,rep,packed,name=remove,proto3" json:"remove,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *ActionProto) Reset() { *m = ActionProto{} } -func (m *ActionProto) String() string { return proto.CompactTextString(m) } -func (*ActionProto) ProtoMessage() {} -func (*ActionProto) Descriptor() ([]byte, []int) { +func (m *Task) Reset() { *m = Task{} } +func (m *Task) String() string { return proto.CompactTextString(m) } +func (*Task) ProtoMessage() {} +func (*Task) Descriptor() ([]byte, []int) { return fileDescriptor_4dc296cbfe5ffcd5, []int{2} } -func (m *ActionProto) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ActionProto.Unmarshal(m, b) +func (m *Task) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Task.Unmarshal(m, b) } -func (m *ActionProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ActionProto.Marshal(b, m, deterministic) +func (m *Task) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Task.Marshal(b, m, deterministic) } -func (m *ActionProto) XXX_Merge(src proto.Message) { - xxx_messageInfo_ActionProto.Merge(m, src) +func (m *Task) XXX_Merge(src proto.Message) { + xxx_messageInfo_Task.Merge(m, src) } -func (m *ActionProto) XXX_Size() int { - return xxx_messageInfo_ActionProto.Size(m) +func (m *Task) XXX_Size() int { + return xxx_messageInfo_Task.Size(m) } -func (m *ActionProto) XXX_DiscardUnknown() { - xxx_messageInfo_ActionProto.DiscardUnknown(m) +func (m *Task) XXX_DiscardUnknown() { + xxx_messageInfo_Task.DiscardUnknown(m) } -var xxx_messageInfo_ActionProto proto.InternalMessageInfo +var xxx_messageInfo_Task proto.InternalMessageInfo -func (m *ActionProto) GetRemove() []uint32 { +func (m *Task) GetRemove() []uint32 { if m != nil { return m.Remove } @@ -150,7 +150,7 @@ func (m *ActionProto) GetRemove() []uint32 { func init() { proto.RegisterType((*Roster)(nil), "roster.Roster") proto.RegisterType((*ChangeSet)(nil), "roster.ChangeSet") - proto.RegisterType((*ActionProto)(nil), "roster.ActionProto") + proto.RegisterType((*Task)(nil), "roster.Task") } func init() { @@ -158,7 +158,7 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 182 bytes of a gzipped FileDescriptorProto + // 176 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0xca, 0x2f, 0x2e, 0x49, 0x2d, 0x92, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x26, 0x95, 0xa6, @@ -168,7 +168,6 @@ var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ 0x8b, 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0xe6, 0xea, 0xc1, 0xcc, 0xd5, 0x73, 0xcc, 0xab, 0x0c, 0x42, 0x52, 0xa7, 0xa4, 0xcc, 0xc5, 0xe9, 0x9c, 0x91, 0x98, 0x97, 0x9e, 0x1a, 0x9c, 0x5a, 0x22, 0x24, 0xc6, 0xc5, 0x56, 0x94, 0x9a, 0x9b, 0x5f, 0x96, 0x0a, 0x36, 0x9d, - 0x37, 0x08, 0xca, 0x53, 0x52, 0xe5, 0xe2, 0x76, 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0x0b, 0x00, 0x3b, - 0x1a, 0x87, 0xb2, 0x24, 0x36, 0xb0, 0x2d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x39, 0x8d, - 0x6d, 0x15, 0xe5, 0x00, 0x00, 0x00, + 0x37, 0x08, 0xca, 0x53, 0x92, 0xe3, 0x62, 0x09, 0x49, 0x2c, 0xce, 0xc6, 0x25, 0x9f, 0xc4, 0x06, + 0x36, 0xde, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x9d, 0xb7, 0x9b, 0x4a, 0xde, 0x00, 0x00, 0x00, } diff --git a/ledger/byzcoin/roster/messages.proto b/ledger/byzcoin/roster/messages.proto index 12562fec7..b26edbc45 100644 --- a/ledger/byzcoin/roster/messages.proto +++ b/ledger/byzcoin/roster/messages.proto @@ -14,6 +14,6 @@ message ChangeSet { repeated uint32 remove = 1; } -message ActionProto { +message Task { repeated uint32 remove = 1; } diff --git a/ledger/byzcoin/roster/task.go b/ledger/byzcoin/roster/task.go index 1d00f087f..b7eccf92d 100644 --- a/ledger/byzcoin/roster/task.go +++ b/ledger/byzcoin/roster/task.go @@ -31,7 +31,7 @@ type clientTask struct { // NewClientTask creates a new roster client task that can be used to create a // transaction. -func NewClientTask(r []uint32) basic.ClientAction { +func NewClientTask(r []uint32) basic.ClientTask { return clientTask{ remove: r, } @@ -48,7 +48,7 @@ func (t clientTask) GetChangeSet() viewchange.ChangeSet { // Pack implements encoding.Packable. It returns the protobuf message for the // client task. func (t clientTask) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { - pb := &ActionProto{ + pb := &Task{ Remove: t.remove, } @@ -79,7 +79,7 @@ type serverTask struct { rosterFactory viewchange.AuthorityFactory } -// Consume implements basic.ServerAction. It executes the task and write the +// Consume implements basic.ServerTask. It executes the task and write the // changes to the page. func (t serverTask) Consume(ctx basic.Context, page inventory.WritablePage) error { // 1. Access rights control @@ -133,7 +133,7 @@ type TaskManager struct { rosterFactory viewchange.AuthorityFactory } -// NewTaskManager returns a new instance of the action factory. +// NewTaskManager returns a new instance of the task factory. func NewTaskManager(f viewchange.AuthorityFactory, i inventory.Inventory) TaskManager { return TaskManager{ encoder: encoding.NewProtoEncoder(), @@ -194,20 +194,20 @@ func (f TaskManager) GetChangeSet(index uint64) (viewchange.ChangeSet, error) { return cs, nil } -// FromProto implements basic.ActionFactory. -func (f TaskManager) FromProto(in proto.Message) (basic.ServerAction, error) { - var pb *ActionProto +// FromProto implements basic.TaskFactory. +func (f TaskManager) FromProto(in proto.Message) (basic.ServerTask, error) { + var pb *Task switch msg := in.(type) { - case *ActionProto: + case *Task: pb = msg } - action := serverTask{ + task := serverTask{ clientTask: clientTask{ remove: pb.Remove, }, encoder: f.encoder, rosterFactory: f.rosterFactory, } - return action, nil + return task, nil } diff --git a/ledger/byzcoin/task.go b/ledger/byzcoin/task.go index f59d5d73a..469b74627 100644 --- a/ledger/byzcoin/task.go +++ b/ledger/byzcoin/task.go @@ -16,12 +16,12 @@ import ( "golang.org/x/xerrors" ) -// taskFactory is an action factory that can process several types of actions. +// taskFactory is an task factory that can process several types of tasks. // // - implements basic.TaskFactory type taskFactory struct { encoder encoding.ProtoMarshaler - registry map[reflect.Type]basic.ActionFactory + registry map[reflect.Type]basic.TaskFactory } func newtaskFactory(m mino.Mino, signer crypto.Signer, @@ -29,27 +29,27 @@ func newtaskFactory(m mino.Mino, signer crypto.Signer, f := &taskFactory{ encoder: encoding.NewProtoEncoder(), - registry: make(map[reflect.Type]basic.ActionFactory), + registry: make(map[reflect.Type]basic.TaskFactory), } rosterFactory := roster.NewRosterFactory(m.GetAddressFactory(), signer.GetPublicKeyFactory()) gov := roster.NewTaskManager(rosterFactory, i) - f.Register((*darc.ActionProto)(nil), darc.NewActionFactory()) - f.Register((*roster.ActionProto)(nil), gov) + f.Register((*darc.Task)(nil), darc.NewTaskFactory()) + f.Register((*roster.Task)(nil), gov) return f, gov } // Register registers the factory for the protobuf message. -func (f *taskFactory) Register(pb proto.Message, factory basic.ActionFactory) { +func (f *taskFactory) Register(pb proto.Message, factory basic.TaskFactory) { key := reflect.TypeOf(pb) f.registry[key] = factory } -// FromProto implements basic.TaskFactory. It returns the server action for -// the protobuf message if appropriate, otherwise an error. -func (f *taskFactory) FromProto(in proto.Message) (basic.ServerAction, error) { +// FromProto implements basic.TaskFactory. It returns the server task for the +// protobuf message if appropriate, otherwise an error. +func (f *taskFactory) FromProto(in proto.Message) (basic.ServerTask, error) { inAny, ok := in.(*any.Any) if ok { var err error @@ -62,13 +62,13 @@ func (f *taskFactory) FromProto(in proto.Message) (basic.ServerAction, error) { key := reflect.TypeOf(in) factory := f.registry[key] if factory == nil { - return nil, xerrors.Errorf("unknown action type '%T'", in) + return nil, xerrors.Errorf("unknown task type '%T'", in) } - action, err := factory.FromProto(in) + task, err := factory.FromProto(in) if err != nil { return nil, err } - return action, nil + return task, nil } diff --git a/ledger/transactions/basic/messages.pb.go b/ledger/transactions/basic/messages.pb.go index 768e77d39..a2a5e7f55 100644 --- a/ledger/transactions/basic/messages.pb.go +++ b/ledger/transactions/basic/messages.pb.go @@ -25,7 +25,7 @@ type TransactionProto struct { Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"` Identity *any.Any `protobuf:"bytes,2,opt,name=identity,proto3" json:"identity,omitempty"` Signature *any.Any `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` - Action *any.Any `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + Task *any.Any `protobuf:"bytes,4,opt,name=task,proto3" json:"task,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -77,9 +77,9 @@ func (m *TransactionProto) GetSignature() *any.Any { return nil } -func (m *TransactionProto) GetAction() *any.Any { +func (m *TransactionProto) GetTask() *any.Any { if m != nil { - return m.Action + return m.Task } return nil } @@ -93,16 +93,16 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 173 bytes of a gzipped FileDescriptorProto + // 174 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4d, 0x4a, 0x2c, 0xce, 0x4c, 0x96, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x0b, 0x26, 0x95, 0xa6, 0xe9, - 0x27, 0xe6, 0x55, 0x42, 0x54, 0x28, 0xed, 0x61, 0xe4, 0x12, 0x08, 0x29, 0x4a, 0xcc, 0x2b, 0x4e, + 0x27, 0xe6, 0x55, 0x42, 0x54, 0x28, 0xed, 0x60, 0xe4, 0x12, 0x08, 0x29, 0x4a, 0xcc, 0x2b, 0x4e, 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0x0b, 0x00, 0x6b, 0x13, 0xe1, 0x62, 0xcd, 0xcb, 0xcf, 0x4b, 0x4e, 0x95, 0x60, 0x54, 0x60, 0xd4, 0x60, 0x09, 0x82, 0x70, 0x84, 0x0c, 0xb8, 0x38, 0x32, 0x53, 0x52, 0xf3, 0x4a, 0x32, 0x4b, 0x2a, 0x25, 0x98, 0x14, 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0x06, 0xeb, 0xc1, 0x0c, 0xd6, 0x73, 0xcc, 0xab, 0x0c, 0x82, 0xab, 0x12, 0x32, 0xe2, 0xe2, 0x2c, 0xce, - 0x4c, 0xcf, 0x4b, 0x2c, 0x29, 0x2d, 0x4a, 0x95, 0x60, 0xc6, 0xa3, 0x05, 0xa1, 0x4c, 0x48, 0x87, - 0x8b, 0x0d, 0xe2, 0x14, 0x09, 0x16, 0x3c, 0x1a, 0xa0, 0x6a, 0x92, 0xd8, 0xc0, 0xa2, 0xc6, 0x80, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x15, 0x12, 0x03, 0x94, 0xf9, 0x00, 0x00, 0x00, + 0x4c, 0xcf, 0x4b, 0x2c, 0x29, 0x2d, 0x4a, 0x95, 0x60, 0xc6, 0xa3, 0x05, 0xa1, 0x4c, 0x48, 0x83, + 0x8b, 0xa5, 0x24, 0xb1, 0x38, 0x5b, 0x82, 0x05, 0x8f, 0x72, 0xb0, 0x8a, 0x24, 0x36, 0xb0, 0x98, + 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x4e, 0x0a, 0x09, 0x3f, 0xf5, 0x00, 0x00, 0x00, } diff --git a/ledger/transactions/basic/messages.proto b/ledger/transactions/basic/messages.proto index 8885e61d4..c2ea288a2 100644 --- a/ledger/transactions/basic/messages.proto +++ b/ledger/transactions/basic/messages.proto @@ -8,5 +8,5 @@ message TransactionProto { uint64 nonce = 1; google.protobuf.Any identity = 2; google.protobuf.Any signature = 3; - google.protobuf.Any action = 4; + google.protobuf.Any task = 4; } diff --git a/ledger/transactions/basic/mod.go b/ledger/transactions/basic/mod.go index d6f25703a..dd2a5a63a 100644 --- a/ledger/transactions/basic/mod.go +++ b/ledger/transactions/basic/mod.go @@ -2,10 +2,9 @@ // a nonce so that it can prevent replay attacks. Access control can also be // enforced from the identity of the transaction. // -// The action defines how the transaction will be consumed and it follows the -// same separation logic with a client and a server side. The client only -// creates the action with its arguments and the server will decorate it to -// consume it. +// The task defines how the transaction will be consumed and it follows the same +// separation logic with a client and a server side. The client only creates the +// task with its arguments and the server will decorate it to consume it. package basic import ( @@ -26,10 +25,8 @@ import ( //go:generate protoc -I ./ --go_out=./ ./messages.proto -// TODO: rename action to task - -// ClientAction is used to create a transaction. -type ClientAction interface { +// ClientTask is a task inside a transaction. +type ClientTask interface { encoding.Packable encoding.Fingerprinter } @@ -43,21 +40,23 @@ type Context interface { GetIdentity() arc.Identity } -// ServerAction provides the primitives to consume a specialization of a -// transaction. -type ServerAction interface { - ClientAction +// ServerTask is an extension of the client task that can be consumed to update +// the state of an inventory. +type ServerTask interface { + ClientTask Consume(Context, inventory.WritablePage) error } -// ActionFactory provide the primitives to instantiate an action from its -// protobuf message. -type ActionFactory interface { - FromProto(proto.Message) (ServerAction, error) +// TaskFactory provide the primitives to instantiate a task from its protobuf +// message. +type TaskFactory interface { + FromProto(proto.Message) (ServerTask, error) } -// transaction is an atomic execution. +// transaction is an implementation of the client transaction that is using a +// signature to determine the identity belonging to it. It also wraps a task +// that will be executed. // // - implements transactions.ClientTransaction type transaction struct { @@ -65,7 +64,7 @@ type transaction struct { nonce uint64 identity crypto.PublicKey signature crypto.Signature - action ClientAction + task ClientTask } // GetID implements transactions.ClientTransaction. It returns the unique @@ -98,9 +97,9 @@ func (t transaction) Pack(enc encoding.ProtoMarshaler) (proto.Message, error) { return nil, xerrors.Errorf("couldn't pack signature: %v", err) } - pb.Action, err = enc.PackAny(t.action) + pb.Task, err = enc.PackAny(t.task) if err != nil { - return nil, xerrors.Errorf("couldn't pack action: %v", err) + return nil, xerrors.Errorf("couldn't pack task: %v", err) } return pb, nil @@ -127,9 +126,9 @@ func (t transaction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error return xerrors.Errorf("couldn't write identity: %v", err) } - err = t.action.Fingerprint(w, enc) + err = t.task.Fingerprint(w, enc) if err != nil { - return xerrors.Errorf("couldn't write action: %v", err) + return xerrors.Errorf("couldn't write task: %v", err) } return nil @@ -149,19 +148,19 @@ type serverTransaction struct { } // Consume implements transactions.ServerTransaction. It first insures the nonce -// is correct and writes the new one into the page. It then consumes the action -// of the transaction. +// is correct and writes the new one into the page. It then consumes the task of +// the transaction. func (t serverTransaction) Consume(page inventory.WritablePage) error { // TODO: consume nonce - action, ok := t.action.(ServerAction) + task, ok := t.task.(ServerTask) if !ok { - return xerrors.Errorf("action must implement 'basic.ServerAction'") + return xerrors.Errorf("task must implement 'basic.ServerTask'") } - err := action.Consume(t, page) + err := task.Consume(t, page) if err != nil { - return xerrors.Errorf("couldn't consume action: %v", err) + return xerrors.Errorf("couldn't consume task: %v", err) } return nil @@ -175,29 +174,29 @@ type TransactionFactory struct { hashFactory crypto.HashFactory publicKeyFactory crypto.PublicKeyFactory signatureFactory crypto.SignatureFactory - actionFactory ActionFactory + taskFactory TaskFactory encoder encoding.ProtoMarshaler } // NewTransactionFactory returns a new instance of the transaction factory. -func NewTransactionFactory(signer crypto.Signer, f ActionFactory) TransactionFactory { +func NewTransactionFactory(signer crypto.Signer, f TaskFactory) TransactionFactory { return TransactionFactory{ signer: signer, hashFactory: crypto.NewSha256Factory(), publicKeyFactory: common.NewPublicKeyFactory(), signatureFactory: common.NewSignatureFactory(), - actionFactory: f, + taskFactory: f, encoder: encoding.NewProtoEncoder(), } } -// New returns a new transaction from the given action. The transaction will be +// New returns a new transaction from the given task. The transaction will be // signed. -func (f TransactionFactory) New(action ClientAction) (transactions.ClientTransaction, error) { +func (f TransactionFactory) New(task ClientTask) (transactions.ClientTransaction, error) { tx := transaction{ nonce: 0, // TODO: monotonic nonce identity: f.signer.GetPublicKey(), - action: action, + task: task, } h := f.hashFactory.New() @@ -234,15 +233,15 @@ func (f TransactionFactory) FromProto(in proto.Message) (transactions.ServerTran return nil, xerrors.Errorf("invalid transaction type '%T'", in) } - action, err := f.actionFactory.FromProto(pb.GetAction()) + task, err := f.taskFactory.FromProto(pb.GetTask()) if err != nil { - return nil, xerrors.Errorf("couldn't decode action: %v", err) + return nil, xerrors.Errorf("couldn't decode task: %v", err) } tx := serverTransaction{ transaction: transaction{ - nonce: pb.GetNonce(), - action: action, + nonce: pb.GetNonce(), + task: task, }, } diff --git a/ledger/transactions/basic/mod_test.go b/ledger/transactions/basic/mod_test.go index 33341eb2a..bc1ade80a 100644 --- a/ledger/transactions/basic/mod_test.go +++ b/ledger/transactions/basic/mod_test.go @@ -49,12 +49,12 @@ func TestTransaction_Pack(t *testing.T) { tx := transaction{ identity: fake.PublicKey{}, signature: fake.Signature{}, - action: fakeClientAction{}, + task: fakeClientTask{}, } txpb, err := tx.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) - require.NotNil(t, txpb.(*TransactionProto).GetAction()) + require.NotNil(t, txpb.(*TransactionProto).GetTask()) _, err = tx.Pack(fake.BadPackAnyEncoder{}) require.EqualError(t, err, "couldn't pack identity: fake error") @@ -63,14 +63,14 @@ func TestTransaction_Pack(t *testing.T) { require.EqualError(t, err, "couldn't pack signature: fake error") _, err = tx.Pack(fake.BadPackAnyEncoder{Counter: &fake.Counter{Value: 2}}) - require.EqualError(t, err, "couldn't pack action: fake error") + require.EqualError(t, err, "couldn't pack task: fake error") } func TestTransaction_Fingerprint(t *testing.T) { tx := transaction{ nonce: 0x0102030405060708, identity: fake.PublicKey{}, - action: fakeClientAction{}, + task: fakeClientTask{}, } buffer := new(bytes.Buffer) @@ -90,9 +90,9 @@ func TestTransaction_Fingerprint(t *testing.T) { require.EqualError(t, err, "couldn't marshal identity: fake error") tx.identity = fake.PublicKey{} - tx.action = fakeClientAction{err: xerrors.New("oops")} + tx.task = fakeClientTask{err: xerrors.New("oops")} err = tx.Fingerprint(buffer, nil) - require.EqualError(t, err, "couldn't write action: oops") + require.EqualError(t, err, "couldn't write task: oops") } func TestTransaction_String(t *testing.T) { @@ -103,49 +103,49 @@ func TestTransaction_String(t *testing.T) { func TestServerTransaction_Consume(t *testing.T) { tx := serverTransaction{ - transaction: transaction{action: fakeSrvAction{}}, + transaction: transaction{task: fakeSrvTask{}}, } err := tx.Consume(nil) require.NoError(t, err) - tx.transaction.action = fakeClientAction{} + tx.transaction.task = fakeClientTask{} err = tx.Consume(nil) - require.EqualError(t, err, "action must implement 'basic.ServerAction'") + require.EqualError(t, err, "task must implement 'basic.ServerTask'") - tx.transaction.action = fakeSrvAction{err: xerrors.New("oops")} + tx.transaction.task = fakeSrvTask{err: xerrors.New("oops")} err = tx.Consume(nil) - require.EqualError(t, err, "couldn't consume action: oops") + require.EqualError(t, err, "couldn't consume task: oops") } func TestTransactionFactory_New(t *testing.T) { factory := NewTransactionFactory(bls.NewSigner(), nil) - clientTx, err := factory.New(fakeClientAction{}) + clientTx, err := factory.New(fakeClientTask{}) require.NoError(t, err) tx := clientTx.(transaction) - require.NotNil(t, tx.action) + require.NotNil(t, tx.task) require.NotNil(t, tx.signature) factory.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - _, err = factory.New(fakeClientAction{}) + _, err = factory.New(fakeClientTask{}) require.EqualError(t, err, "couldn't compute hash: couldn't write nonce: fake error") factory.hashFactory = fake.NewHashFactory(&fake.Hash{}) factory.signer = fake.NewBadSigner() - _, err = factory.New(fakeClientAction{}) + _, err = factory.New(fakeClientTask{}) require.EqualError(t, err, "couldn't sign tx: fake error") } func TestTransactionFactory_FromProto(t *testing.T) { - factory := NewTransactionFactory(nil, fakeActionFactory{}) + factory := NewTransactionFactory(nil, fakeTaskFactory{}) factory.publicKeyFactory = fake.PublicKeyFactory{} factory.signatureFactory = fake.SignatureFactory{} tx := transaction{ identity: fake.PublicKey{}, signature: fake.Signature{}, - action: fakeSrvAction{}, + task: fakeSrvTask{}, } txpb, err := tx.Pack(encoding.NewProtoEncoder()) @@ -165,11 +165,11 @@ func TestTransactionFactory_FromProto(t *testing.T) { _, err = factory.FromProto(txany) require.EqualError(t, err, "couldn't unmarshal input: fake error") - factory.actionFactory = fakeActionFactory{err: xerrors.New("oops")} + factory.taskFactory = fakeTaskFactory{err: xerrors.New("oops")} _, err = factory.FromProto(txpb) - require.EqualError(t, err, "couldn't decode action: oops") + require.EqualError(t, err, "couldn't decode task: oops") - factory.actionFactory = fakeActionFactory{} + factory.taskFactory = fakeTaskFactory{} factory.publicKeyFactory = fake.NewBadPublicKeyFactory() _, err = factory.FromProto(txpb) require.EqualError(t, err, "couldn't decode public key: fake error") @@ -192,32 +192,32 @@ func TestTransactionFactory_FromProto(t *testing.T) { // ----------------------------------------------------------------------------- // Utility functions -type fakeClientAction struct { +type fakeClientTask struct { err error } -func (a fakeClientAction) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { +func (a fakeClientTask) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { w.Write([]byte{0xcc}) return a.err } -func (a fakeClientAction) Pack(encoding.ProtoMarshaler) (proto.Message, error) { +func (a fakeClientTask) Pack(encoding.ProtoMarshaler) (proto.Message, error) { return &empty.Empty{}, nil } -type fakeSrvAction struct { - fakeClientAction +type fakeSrvTask struct { + fakeClientTask err error } -func (a fakeSrvAction) Consume(Context, inventory.WritablePage) error { +func (a fakeSrvTask) Consume(Context, inventory.WritablePage) error { return a.err } -type fakeActionFactory struct { +type fakeTaskFactory struct { err error } -func (f fakeActionFactory) FromProto(proto.Message) (ServerAction, error) { - return fakeSrvAction{}, f.err +func (f fakeTaskFactory) FromProto(proto.Message) (ServerTask, error) { + return fakeSrvTask{}, f.err } From 5df26e08ca41fd1e84774bea5f7576ae39965588 Mon Sep 17 00:00:00 2001 From: Gaylor Bosson Date: Mon, 4 May 2020 15:12:01 +0200 Subject: [PATCH 9/9] Byzcoin: roster change unit tests --- ledger/byzcoin/contract/context.go | 8 +- ledger/byzcoin/contract/context_test.go | 14 +- ledger/byzcoin/contract/task_test.go | 20 ++- ledger/byzcoin/mod_test.go | 2 +- ledger/byzcoin/roster/messages.pb.go | 25 +++- ledger/byzcoin/roster/messages.proto | 6 +- ledger/byzcoin/roster/mod.go | 4 + ledger/byzcoin/roster/mod_test.go | 33 ++++- ledger/byzcoin/roster/task.go | 65 +++++++-- ledger/byzcoin/roster/task_test.go | 179 +++++++++++++++++++++++- ledger/inventory/mem/mod.go | 7 +- ledger/inventory/mem/mod_test.go | 7 +- ledger/inventory/mod.go | 3 +- 13 files changed, 319 insertions(+), 54 deletions(-) diff --git a/ledger/byzcoin/contract/context.go b/ledger/byzcoin/contract/context.go index 2d152e793..2773a5d71 100644 --- a/ledger/byzcoin/contract/context.go +++ b/ledger/byzcoin/contract/context.go @@ -18,7 +18,11 @@ type taskContext struct { func (ctx taskContext) GetArc(key []byte) (arc.AccessControl, error) { value, err := ctx.page.Read(key) if err != nil { - return nil, xerrors.Errorf("couldn't read value: %v", err) + return nil, xerrors.Errorf("couldn't read from page: %v", err) + } + + if value == nil { + return nil, xerrors.Errorf("access does not exist") } access, err := ctx.arcFactory.FromProto(value) @@ -34,7 +38,7 @@ func (ctx taskContext) GetArc(key []byte) (arc.AccessControl, error) { func (ctx taskContext) Read(key []byte) (*Instance, error) { entry, err := ctx.page.Read(key) if err != nil { - return nil, xerrors.Errorf("couldn't read the value: %v", err) + return nil, xerrors.Errorf("couldn't read from page: %v", err) } instance, ok := entry.(*Instance) diff --git a/ledger/byzcoin/contract/context_test.go b/ledger/byzcoin/contract/context_test.go index 3f58e31aa..1dacdd1f3 100644 --- a/ledger/byzcoin/contract/context_test.go +++ b/ledger/byzcoin/contract/context_test.go @@ -7,6 +7,7 @@ import ( "github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/empty" "github.com/stretchr/testify/require" + "golang.org/x/xerrors" ) func TestTaskContext_GetArc(t *testing.T) { @@ -22,7 +23,11 @@ func TestTaskContext_GetArc(t *testing.T) { require.NotNil(t, arc) _, err = ctx.GetArc(nil) - require.EqualError(t, err, "couldn't read value: not found") + require.EqualError(t, err, "access does not exist") + + ctx.page = fakePage{errRead: xerrors.New("oops")} + _, err = ctx.GetArc(nil) + require.EqualError(t, err, "couldn't read from page: oops") } func TestTaskContext_Read(t *testing.T) { @@ -44,9 +49,14 @@ func TestTaskContext_Read(t *testing.T) { require.NotNil(t, instance.Value) _, err = ctx.Read(nil) - require.EqualError(t, err, "couldn't read the value: not found") + require.EqualError(t, err, + "invalid message type '' != '*contract.Instance'") _, err = ctx.Read([]byte("b")) require.EqualError(t, err, "invalid message type '*empty.Empty' != '*contract.Instance'") + + ctx.page = fakePage{errRead: xerrors.New("oops")} + _, err = ctx.Read(nil) + require.EqualError(t, err, "couldn't read from page: oops") } diff --git a/ledger/byzcoin/contract/task_test.go b/ledger/byzcoin/contract/task_test.go index 2c2d0b82f..d52b8fee5 100644 --- a/ledger/byzcoin/contract/task_test.go +++ b/ledger/byzcoin/contract/task_test.go @@ -183,7 +183,7 @@ func TestServerTask_Consume(t *testing.T) { task.ClientTask = InvokeTask{Key: []byte("c")} err = task.Consume(fakeContext{}, page) require.EqualError(t, err, - "couldn't read the instance: couldn't read the value: not found") + "couldn't read the instance: invalid message type '' != '*contract.Instance'") task.ClientTask = InvokeTask{Key: []byte("z")} err = task.Consume(fakeContext{}, page) @@ -220,10 +220,10 @@ func TestServerTask_Consume(t *testing.T) { task.ClientTask = DeleteTask{Key: []byte("c")} err = task.Consume(fakeContext{}, page) require.EqualError(t, err, - "couldn't read the instance: couldn't read the value: not found") + "couldn't read the instance: invalid message type '' != '*contract.Instance'") // 4. Consume an invalid task. - page.err = xerrors.New("oops") + page.errWrite = xerrors.New("oops") task.ClientTask = DeleteTask{Key: []byte("a")} err = task.Consume(fakeContext{}, page) require.EqualError(t, err, "couldn't write instance to page: oops") @@ -308,22 +308,18 @@ func (c fakeContract) Invoke(ctx InvokeContext) (proto.Message, error) { type fakePage struct { inventory.WritablePage - store map[string]proto.Message - err error + store map[string]proto.Message + errRead error + errWrite error } func (page fakePage) Read(key []byte) (proto.Message, error) { - instance := page.store[string(key)] - if instance == nil { - return nil, xerrors.New("not found") - } - - return instance, nil + return page.store[string(key)], page.errRead } func (page fakePage) Write(key []byte, value proto.Message) error { page.store[string(key)] = value - return page.err + return page.errWrite } type fakeContext struct { diff --git a/ledger/byzcoin/mod_test.go b/ledger/byzcoin/mod_test.go index 30b045018..d8bab9fac 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -73,7 +73,7 @@ func TestLedger_Basic(t *testing.T) { t.Fatal("timeout 1") } - roster, err := ledgers[5].(*Ledger).governance.GetAuthority(1) + roster, err := ledgers[2].(*Ledger).governance.GetAuthority(1) require.NoError(t, err) require.Equal(t, 19, roster.Len()) diff --git a/ledger/byzcoin/roster/messages.pb.go b/ledger/byzcoin/roster/messages.pb.go index 5ea8502e7..de1d8c798 100644 --- a/ledger/byzcoin/roster/messages.pb.go +++ b/ledger/byzcoin/roster/messages.pb.go @@ -69,8 +69,11 @@ func (m *Roster) GetPublicKeys() []*any.Any { return nil } +// ChangeSet is the message stored in the inventory for the change set of a +// block. type ChangeSet struct { - Remove []uint32 `protobuf:"varint,1,rep,packed,name=remove,proto3" json:"remove,omitempty"` + Index uint64 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + Remove []uint32 `protobuf:"varint,2,rep,packed,name=remove,proto3" json:"remove,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -101,6 +104,13 @@ func (m *ChangeSet) XXX_DiscardUnknown() { var xxx_messageInfo_ChangeSet proto.InternalMessageInfo +func (m *ChangeSet) GetIndex() uint64 { + if m != nil { + return m.Index + } + return 0 +} + func (m *ChangeSet) GetRemove() []uint32 { if m != nil { return m.Remove @@ -108,6 +118,7 @@ func (m *ChangeSet) GetRemove() []uint32 { return nil } +// Task is the message for a client task. type Task struct { Remove []uint32 `protobuf:"varint,1,rep,packed,name=remove,proto3" json:"remove,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -158,7 +169,7 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 176 bytes of a gzipped FileDescriptorProto + // 197 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0xca, 0x2f, 0x2e, 0x49, 0x2d, 0x92, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x26, 0x95, 0xa6, @@ -166,8 +177,10 @@ var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ 0x71, 0x26, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0xa7, 0x16, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0xf0, 0x04, 0x21, 0x04, 0x84, 0x4c, 0xb8, 0xb8, 0x0a, 0x4a, 0x93, 0x72, 0x32, 0x93, 0xbd, 0x53, 0x2b, 0x8b, 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0xe6, 0xea, 0xc1, 0xcc, 0xd5, - 0x73, 0xcc, 0xab, 0x0c, 0x42, 0x52, 0xa7, 0xa4, 0xcc, 0xc5, 0xe9, 0x9c, 0x91, 0x98, 0x97, 0x9e, - 0x1a, 0x9c, 0x5a, 0x22, 0x24, 0xc6, 0xc5, 0x56, 0x94, 0x9a, 0x9b, 0x5f, 0x96, 0x0a, 0x36, 0x9d, - 0x37, 0x08, 0xca, 0x53, 0x92, 0xe3, 0x62, 0x09, 0x49, 0x2c, 0xce, 0xc6, 0x25, 0x9f, 0xc4, 0x06, - 0x36, 0xde, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x9d, 0xb7, 0x9b, 0x4a, 0xde, 0x00, 0x00, 0x00, + 0x73, 0xcc, 0xab, 0x0c, 0x42, 0x52, 0xa7, 0x64, 0xc9, 0xc5, 0xe9, 0x9c, 0x91, 0x98, 0x97, 0x9e, + 0x1a, 0x9c, 0x5a, 0x22, 0x24, 0xc2, 0xc5, 0x9a, 0x99, 0x97, 0x92, 0x5a, 0x21, 0xc1, 0xa8, 0xc0, + 0xa8, 0xc1, 0x12, 0x04, 0xe1, 0x08, 0x89, 0x71, 0xb1, 0x15, 0xa5, 0xe6, 0xe6, 0x97, 0xa5, 0x82, + 0x0d, 0xe5, 0x0d, 0x82, 0xf2, 0x94, 0xe4, 0xb8, 0x58, 0x42, 0x12, 0x8b, 0xb3, 0x91, 0xe4, 0x19, + 0x91, 0xe5, 0x93, 0xd8, 0xc0, 0x96, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb5, 0xe7, 0x76, + 0x9f, 0xf4, 0x00, 0x00, 0x00, } diff --git a/ledger/byzcoin/roster/messages.proto b/ledger/byzcoin/roster/messages.proto index b26edbc45..cc9b8ba61 100644 --- a/ledger/byzcoin/roster/messages.proto +++ b/ledger/byzcoin/roster/messages.proto @@ -10,10 +10,14 @@ message Roster { repeated google.protobuf.Any publicKeys = 2; } +// ChangeSet is the message stored in the inventory for the change set of a +// block. message ChangeSet { - repeated uint32 remove = 1; + uint64 index = 1; + repeated uint32 remove = 2; } +// Task is the message for a client task. message Task { repeated uint32 remove = 1; } diff --git a/ledger/byzcoin/roster/mod.go b/ledger/byzcoin/roster/mod.go index 57ff27b34..62e68daae 100644 --- a/ledger/byzcoin/roster/mod.go +++ b/ledger/byzcoin/roster/mod.go @@ -185,6 +185,8 @@ func NewRosterFactory(af mino.AddressFactory, pf crypto.PublicKeyFactory) viewch } } +// New implements viewchange.AuthorityFactory. It returns a new roster from the +// given authority. func (f rosterFactory) New(authority crypto.CollectiveAuthority) viewchange.EvolvableAuthority { addrs := make([]mino.Address, authority.Len()) pubkeys := make([]crypto.PublicKey, authority.Len()) @@ -204,6 +206,8 @@ func (f rosterFactory) New(authority crypto.CollectiveAuthority) viewchange.Evol return roster } +// FromProto implements viewchange.AuthorityFactory. It returns the roster +// associated with the message if appropriate, otherwise an error. func (f rosterFactory) FromProto(in proto.Message) (viewchange.EvolvableAuthority, error) { var pb *Roster switch msg := in.(type) { diff --git a/ledger/byzcoin/roster/mod_test.go b/ledger/byzcoin/roster/mod_test.go index 7c7ba834d..37ba64e26 100644 --- a/ledger/byzcoin/roster/mod_test.go +++ b/ledger/byzcoin/roster/mod_test.go @@ -3,8 +3,10 @@ package roster import ( "testing" - proto "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/consensus/viewchange" + "go.dedis.ch/fabric/crypto/bls" "go.dedis.ch/fabric/encoding" internal "go.dedis.ch/fabric/internal/testing" "go.dedis.ch/fabric/internal/testing/fake" @@ -14,6 +16,8 @@ import ( func TestMessages(t *testing.T) { messages := []proto.Message{ &Roster{}, + &ChangeSet{}, + &Task{}, } for _, m := range messages { @@ -96,6 +100,13 @@ func TestRoster_Take(t *testing.T) { require.Equal(t, 2, roster2.Len()) } +func TestRoster_Apply(t *testing.T) { + roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)) + + roster2 := roster.Apply(viewchange.ChangeSet{Remove: []uint32{1, 3}}) + require.Equal(t, roster.Len()-1, roster2.Len()) +} + func TestRoster_Len(t *testing.T) { roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)) require.Equal(t, 3, roster.Len()) @@ -119,6 +130,26 @@ func TestRoster_GetPublicKey(t *testing.T) { require.Nil(t, pubkey) } +func TestRoster_AddressIterator(t *testing.T) { + authority := fake.NewAuthority(3, fake.NewSigner) + roster := rosterFactory{}.New(authority) + + iter := roster.AddressIterator() + for i := 0; iter.HasNext(); i++ { + require.Equal(t, authority.GetAddress(i), iter.GetNext()) + } +} + +func TestRoster_PublicKeyIterator(t *testing.T) { + authority := fake.NewAuthority(3, bls.NewSigner) + roster := rosterFactory{}.New(authority) + + iter := roster.PublicKeyIterator() + for i := 0; iter.HasNext(); i++ { + require.Equal(t, authority.GetSigner(i).GetPublicKey(), iter.GetNext()) + } +} + func TestRoster_Pack(t *testing.T) { roster := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)).(roster) diff --git a/ledger/byzcoin/roster/task.go b/ledger/byzcoin/roster/task.go index b7eccf92d..6e5a628f2 100644 --- a/ledger/byzcoin/roster/task.go +++ b/ledger/byzcoin/roster/task.go @@ -5,6 +5,7 @@ import ( "io" "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" "go.dedis.ch/fabric/consensus/viewchange" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/ledger/inventory" @@ -16,12 +17,15 @@ const ( // AuthorityKey is the key used to store the roster. AuthorityKey = "authority:value" + // ChangeSetKey is the key used to store the roster change set. + ChangeSetKey = "authority:changeset" + // ArcKey is the ke used to store the access rights control of the roster. ArcKey = "authority:arc" ) var authorityKey = []byte(AuthorityKey) -var changesetKey = []byte("authority:changeset") +var changeSetKey = []byte(ChangeSetKey) // clientTask is the client task implementation to update the roster of a // consensus using the transactions for access rights control. @@ -88,12 +92,12 @@ func (t serverTask) Consume(ctx basic.Context, page inventory.WritablePage) erro // 2. Update the roster stored in the inventory. value, err := page.Read(authorityKey) if err != nil { - return err + return xerrors.Errorf("couldn't read roster: %v", err) } roster, err := t.rosterFactory.FromProto(value) if err != nil { - return err + return xerrors.Errorf("couldn't decode roster: %v", err) } changeset := t.GetChangeSet() @@ -101,22 +105,46 @@ func (t serverTask) Consume(ctx basic.Context, page inventory.WritablePage) erro value, err = t.encoder.Pack(roster) if err != nil { - return err + return xerrors.Errorf("couldn't encode roster: %v", err) } err = page.Write(authorityKey, value) if err != nil { - return err + return xerrors.Errorf("couldn't write roster: %v", err) } // 3. Store the changeset so it can be read later on. - changesetpb := &ChangeSet{ - Remove: changeset.Remove, + err = t.updateChangeSet(page) + if err != nil { + return xerrors.Errorf("couldn't update change set: %v", err) } - err = page.Write(changesetKey, changesetpb) + return nil +} + +func (t serverTask) updateChangeSet(page inventory.WritablePage) error { + pb, err := page.Read(changeSetKey) if err != nil { - return err + return xerrors.Errorf("couldn't read from page: %v", err) + } + + changesetpb, ok := pb.(*ChangeSet) + if !ok || changesetpb.GetIndex() != page.GetIndex() { + // Initialize if nil or reset if the change set comes from a previous + // block. + changesetpb = &ChangeSet{ + // Keep track of which index the change set is for as the inventory + // moves values from previous pages. + Index: page.GetIndex(), + } + } + + // Merge the change set. + changesetpb.Remove = append(changesetpb.Remove, t.remove...) + + err = page.Write(changeSetKey, changesetpb) + if err != nil { + return xerrors.Errorf("couldn't write to page: %v", err) } return nil @@ -179,13 +207,15 @@ func (f TaskManager) GetChangeSet(index uint64) (viewchange.ChangeSet, error) { return cs, xerrors.Errorf("couldn't read page: %v", err) } - pb, err := page.Read(changesetKey) + pb, err := page.Read(changeSetKey) if err != nil { - return cs, nil + return cs, xerrors.Errorf("couldn't read from page: %v", err) } changesetpb, ok := pb.(*ChangeSet) - if !ok { + if !ok || index != changesetpb.GetIndex() { + // Either the change set is not defined, or it has been for a previous + // block we return an empty change set. return cs, nil } @@ -194,12 +224,21 @@ func (f TaskManager) GetChangeSet(index uint64) (viewchange.ChangeSet, error) { return cs, nil } -// FromProto implements basic.TaskFactory. +// FromProto implements basic.TaskFactory. It returns the server task associated +// with the server task if appropriate, otherwise an error. func (f TaskManager) FromProto(in proto.Message) (basic.ServerTask, error) { var pb *Task switch msg := in.(type) { case *Task: pb = msg + case *any.Any: + pb = &Task{} + err := f.encoder.UnmarshalAny(msg, pb) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal message: %v", err) + } + default: + return nil, xerrors.Errorf("invalid message type '%T'", in) } task := serverTask{ diff --git a/ledger/byzcoin/roster/task_test.go b/ledger/byzcoin/roster/task_test.go index b5cc6919c..2eec7be58 100644 --- a/ledger/byzcoin/roster/task_test.go +++ b/ledger/byzcoin/roster/task_test.go @@ -1,9 +1,12 @@ package roster import ( + "bytes" "testing" proto "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/empty" "github.com/stretchr/testify/require" "go.dedis.ch/fabric/encoding" "go.dedis.ch/fabric/internal/testing/fake" @@ -11,6 +14,96 @@ import ( "golang.org/x/xerrors" ) +func TestClientTask_GetChangeSet(t *testing.T) { + task := NewClientTask([]uint32{1, 3, 4}).(clientTask) + + changeset := task.GetChangeSet() + require.Equal(t, []uint32{1, 3, 4}, changeset.Remove) +} + +func TestClientTask_Pack(t *testing.T) { + task := NewClientTask([]uint32{1}) + + taskpb, err := task.Pack(nil) + require.NoError(t, err) + require.Equal(t, []uint32{1}, taskpb.(*Task).GetRemove()) +} + +func TestClientTask_Fingerprint(t *testing.T) { + task := NewClientTask([]uint32{0x02, 0x01, 0x03}) + + buffer := new(bytes.Buffer) + + err := task.Fingerprint(buffer, nil) + require.NoError(t, err) + require.Equal(t, "\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00", buffer.String()) + + err = task.Fingerprint(fake.NewBadHash(), nil) + require.EqualError(t, err, "couldn't write remove indices: fake error") +} + +func TestServerTask_Consume(t *testing.T) { + task := serverTask{ + clientTask: clientTask{remove: []uint32{2}}, + rosterFactory: NewRosterFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}), + encoder: encoding.NewProtoEncoder(), + } + + roster := task.rosterFactory.New(fake.NewAuthority(3, fake.NewSigner)) + rosterpb, err := roster.Pack(task.encoder) + require.NoError(t, err) + + values := map[string]proto.Message{ + AuthorityKey: rosterpb, + // Change set is not set yet. + } + + err = task.Consume(nil, fakePage{values: values}) + require.NoError(t, err) + + changesetpb := values[ChangeSetKey] + require.NotNil(t, changesetpb) + require.Equal(t, uint64(5), changesetpb.(*ChangeSet).GetIndex()) + require.Equal(t, []uint32{2}, changesetpb.(*ChangeSet).GetRemove()) + + task.clientTask.remove = []uint32{4, 2} + err = task.Consume(nil, fakePage{values: values}) + require.NoError(t, err) + + changesetpb = values[ChangeSetKey] + // TODO: unique and sorted + require.Equal(t, []uint32{2, 4, 2}, changesetpb.(*ChangeSet).GetRemove()) + + err = task.Consume(nil, fakePage{errRead: xerrors.New("oops")}) + require.EqualError(t, err, "couldn't read roster: oops") + + err = task.Consume(nil, fakePage{values: map[string]proto.Message{}}) + require.EqualError(t, err, "couldn't decode roster: invalid message type ''") + + task.encoder = fake.BadPackEncoder{} + err = task.Consume(nil, fakePage{values: values}) + require.EqualError(t, err, "couldn't encode roster: fake error") + + task.encoder = encoding.NewProtoEncoder() + err = task.Consume(nil, fakePage{values: values, errWrite: xerrors.New("oops")}) + require.EqualError(t, err, "couldn't write roster: oops") + + page := fakePage{ + values: values, + errRead: xerrors.New("oops"), + counter: &fake.Counter{Value: 1}, + } + + err = task.Consume(nil, page) + require.EqualError(t, err, "couldn't update change set: couldn't read from page: oops") + + page.errRead = nil + page.errWrite = xerrors.New("oops") + page.counter.Value = 1 + err = task.Consume(nil, page) + require.EqualError(t, err, "couldn't update change set: couldn't write to page: oops") +} + func TestTaskManager_GetAuthorityFactory(t *testing.T) { factory := NewRosterFactory(nil, nil) manager := NewTaskManager(factory, nil) @@ -44,17 +137,87 @@ func TestTaskManager_GetAuthority(t *testing.T) { require.EqualError(t, err, "couldn't decode roster: invalid message type ''") } +func TestTaskManager_GetChangeSet(t *testing.T) { + manager := NewTaskManager(nil, fakeInventory{value: &ChangeSet{ + Index: 5, + Remove: []uint32{3, 4}, + }}) + + changeset, err := manager.GetChangeSet(5) + require.NoError(t, err) + require.Len(t, changeset.Remove, 2) + + changeset, err = manager.GetChangeSet(6) + require.NoError(t, err) + require.Len(t, changeset.Remove, 0) + + manager.inventory = fakeInventory{err: xerrors.New("oops")} + _, err = manager.GetChangeSet(0) + require.EqualError(t, err, "couldn't read page: oops") + + manager.inventory = fakeInventory{errPage: xerrors.New("oops")} + _, err = manager.GetChangeSet(0) + require.EqualError(t, err, "couldn't read from page: oops") +} + +func TestTaskManager_FromProto(t *testing.T) { + manager := NewTaskManager(nil, nil) + + task, err := manager.FromProto(&Task{}) + require.NoError(t, err) + require.NotNil(t, task) + + taskAny, err := ptypes.MarshalAny(&Task{}) + require.NoError(t, err) + + task, err = manager.FromProto(taskAny) + require.NoError(t, err) + require.NotNil(t, task) + + _, err = manager.FromProto(&empty.Empty{}) + require.EqualError(t, err, "invalid message type '*empty.Empty'") + + manager.encoder = fake.BadUnmarshalAnyEncoder{} + _, err = manager.FromProto(taskAny) + require.EqualError(t, err, "couldn't unmarshal message: fake error") +} + // ----------------------------------------------------------------------------- // Utility functions type fakePage struct { - inventory.Page - value proto.Message - err error + inventory.WritablePage + values map[string]proto.Message + errRead error + errWrite error + counter *fake.Counter +} + +func (p fakePage) GetIndex() uint64 { + return 5 } -func (p fakePage) Read([]byte) (proto.Message, error) { - return p.value, p.err +func (p fakePage) Read(key []byte) (proto.Message, error) { + if p.errRead != nil { + defer p.counter.Decrease() + if p.counter.Done() { + return nil, p.errRead + } + } + + return p.values[string(key)], nil +} + +func (p fakePage) Write(key []byte, value proto.Message) error { + if p.errWrite != nil { + defer p.counter.Decrease() + if p.counter.Done() { + return p.errWrite + } + } + + p.values[string(key)] = value + return nil } type fakeInventory struct { @@ -65,5 +228,9 @@ type fakeInventory struct { } func (i fakeInventory) GetPage(uint64) (inventory.Page, error) { - return fakePage{value: i.value, err: i.errPage}, i.err + values := map[string]proto.Message{ + AuthorityKey: i.value, + ChangeSetKey: i.value, + } + return fakePage{values: values, errRead: i.errPage}, i.err } diff --git a/ledger/inventory/mem/mod.go b/ledger/inventory/mem/mod.go index ab84257de..7edadac1f 100644 --- a/ledger/inventory/mem/mod.go +++ b/ledger/inventory/mem/mod.go @@ -206,12 +206,7 @@ func (page inMemoryPage) Read(key []byte) (proto.Message, error) { digest := Digest{} copy(digest[:], key) - entry, ok := page.entries[digest] - if !ok { - return nil, xerrors.Errorf("instance with key '%#x' not found", key) - } - - return entry, nil + return page.entries[digest], nil } // Write implements inventory.WritablePage. It updates the state of the page by diff --git a/ledger/inventory/mem/mod_test.go b/ledger/inventory/mem/mod_test.go index fed198714..d23322636 100644 --- a/ledger/inventory/mem/mod_test.go +++ b/ledger/inventory/mem/mod_test.go @@ -135,12 +135,13 @@ func TestPage_Read(t *testing.T) { require.NoError(t, err) require.Equal(t, "1", value.(*wrappers.StringValue).Value) + value, err = page.Read([]byte{3}) + require.NoError(t, err) + require.Nil(t, value) + badKey := [digestLength + 1]byte{} _, err = page.Read(badKey[:]) require.EqualError(t, err, "key length (33) is higher than 32") - - _, err = page.Read([]byte{3}) - require.EqualError(t, err, "instance with key '0x03' not found") } func TestPage_Write(t *testing.T) { diff --git a/ledger/inventory/mod.go b/ledger/inventory/mod.go index 276c25664..076a522a8 100644 --- a/ledger/inventory/mod.go +++ b/ledger/inventory/mod.go @@ -12,7 +12,8 @@ type Page interface { // the integrity. GetFootprint() []byte - // Read returns the value stored at the given key. + // Read returns the value stored at the given key. If the key does not + // exist, it should a nil value without error. Read(key []byte) (proto.Message, error) }