diff --git a/blockchain/mod.go b/blockchain/mod.go index 3f5569402..b14e7c3b8 100644 --- a/blockchain/mod.go +++ b/blockchain/mod.go @@ -15,7 +15,7 @@ type Block interface { // GetIndex returns the index since the genesis block. GetIndex() uint64 - // GetHash returns the footprint of the block. + // GetHash returns the fingerprint of the block. GetHash() []byte // GetPayload returns the payload of the block. 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 159e634cc..0f55d43e9 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,9 +45,18 @@ type EvolvableAuthority interface { Apply(ChangeSet) EvolvableAuthority } +// AuthorityFactory is an interface to instantiate evolvable authorities. +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 { + 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. @@ -52,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/docs/introduction.md b/docs/introduction.md index 4f71ff250..f3aeb7c60 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -25,7 +25,7 @@ definitions of modular pieces that build a blockchain. of signature from multiple key pairs and it can be verified by the corresponding aggregate of public keys. -- **footprint** - Footprint defines a digest commonly produced by a hash +- **fingerprint** - Fingerprint defines a digest commonly produced by a hash algorithm that can be used to verify the integrity of some data. One example is the inventory page integrity to prove which instances are stored. @@ -66,3 +66,6 @@ definitions of modular pieces that build a blockchain. - **skipchain** - A skipchain is a specific implementation of the blockchain that is using collective signings to create shortcuts between blocks. + +- **task** - A task is an order of execution that is stored inside a + transaction. It will define how the transaction will update the inventory. diff --git a/encoding/mod.go b/encoding/mod.go index 10efeaeb7..6aa5b473a 100644 --- a/encoding/mod.go +++ b/encoding/mod.go @@ -49,6 +49,12 @@ type ProtoMarshaler interface { UnmarshalDynamicAny(any *any.Any) (proto.Message, error) } +// Fingerprinter is an interface to perform fingerprinting on object. +type Fingerprinter interface { + // Fingerprint writes itself to the writer in a deterministic way. + 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 590b02813..36fa4766b 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) } @@ -123,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 @@ -379,6 +392,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 @@ -489,6 +507,24 @@ 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 { + return c == nil || 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 @@ -502,10 +538,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/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/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/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..3a2328d15 100644 --- a/ledger/arc/darc/expr_test.go +++ b/ledger/arc/darc/expr_test.go @@ -1,10 +1,11 @@ package darc import ( - fmt "fmt" + "bytes" "testing" "github.com/stretchr/testify/require" + "go.dedis.ch/fabric/internal/testing/fake" "go.dedis.ch/fabric/ledger/arc" "golang.org/x/xerrors" ) @@ -46,12 +47,28 @@ 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") } +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}}, @@ -80,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/messages.pb.go b/ledger/arc/darc/messages.pb.go index ef839a4b8..0bc3a285e 100644 --- a/ledger/arc/darc/messages.pb.go +++ b/ledger/arc/darc/messages.pb.go @@ -59,49 +59,97 @@ 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 } return nil } +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:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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 *Task) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Task.Unmarshal(m, b) +} +func (m *Task) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Task.Marshal(b, m, deterministic) +} +func (m *Task) XXX_Merge(src proto.Message) { + xxx_messageInfo_Task.Merge(m, src) +} +func (m *Task) XXX_Size() int { + return xxx_messageInfo_Task.Size(m) +} +func (m *Task) XXX_DiscardUnknown() { + xxx_messageInfo_Task.DiscardUnknown(m) +} + +var xxx_messageInfo_Task proto.InternalMessageInfo + +func (m *Task) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *Task) GetAccess() *AccessProto { + 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((*AccessProto)(nil), "darc.AccessProto") + proto.RegisterMapType((map[string]*Expression)(nil), "darc.AccessProto.RulesEntry") + proto.RegisterType((*Task)(nil), "darc.Task") } func init() { @@ -109,17 +157,18 @@ func init() { } var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ - // 185 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, 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, + 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, + 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 18d1f2e24..a52a4767e 100644 --- a/ledger/arc/darc/messages.proto +++ b/ledger/arc/darc/messages.proto @@ -6,6 +6,11 @@ message Expression { repeated string matches = 1; } -message AccessControlProto { +message AccessProto { map rules = 1; } + +message Task { + bytes key = 1; + AccessProto access = 2; +} diff --git a/ledger/arc/darc/mod.go b/ledger/arc/darc/mod.go index 772176679..60c906a59 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), } @@ -66,12 +61,42 @@ 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 +// 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 +137,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..b08229935 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{}, + &Task{}, } 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...) @@ -66,6 +68,31 @@ 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) { + 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) { @@ -74,14 +101,14 @@ func TestAccess_Pack(t *testing.T) { 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 +117,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/arc/darc/task.go b/ledger/arc/darc/task.go new file mode 100644 index 000000000..3d425c7b3 --- /dev/null +++ b/ledger/arc/darc/task.go @@ -0,0 +1,169 @@ +package darc + +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" +) + +const ( + // UpdateAccessRule is the rule to be defined in the DARC to update it. + UpdateAccessRule = "darc_update" +) + +// clientTask is the client task of a transaction that will allow an authorized +// identity to create or update a DARC. +// +// - implements basic.ClientTask +type clientTask struct { + key []byte + access Access +} + +// TODO: client factory + +// NewCreate returns a new task to create a DARC. +func NewCreate(access Access) basic.ClientTask { + return clientTask{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 +// 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 := &Task{ + Key: act.key, + Access: access.(*AccessProto), + } + + return pb, nil +} + +// Fingerprint implements encoding.Fingerprinter. It serializes the client task +// into the writer in a deterministic way. +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) + } + + err = act.access.Fingerprint(w, enc) + if err != nil { + return xerrors.Errorf("couldn't fingerprint access: %v", err) + } + + return nil +} + +// serverTask is the server task for a DARC transaction. +// +// - implements basic.ServerTask +type serverTask struct { + clientTask + encoder encoding.ProtoMarshaler + darcFactory arc.AccessControlFactory +} + +// 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 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) + } + + 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 { + 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) + if err != nil { + return xerrors.Errorf("couldn't write access: %v", err) + } + + return nil +} + +// taskFactory is a factory to instantiate darc server tasks from protobuf +// messages. +// +// - implements basic.TaskFactory +type taskFactory struct { + encoder encoding.ProtoMarshaler + darcFactory arc.AccessControlFactory +} + +// NewTaskFactory returns a new instance of the task factory. +func NewTaskFactory() basic.TaskFactory { + return taskFactory{ + encoder: encoding.NewProtoEncoder(), + darcFactory: NewFactory(), + } +} + +// FromProto implements basic.TaskFactory. It returns the server task of the +// protobuf message when approriate, otherwise an error. +func (f taskFactory) FromProto(in proto.Message) (basic.ServerTask, error) { + var pb *Task + switch msg := in.(type) { + case *Task: + pb = msg + default: + return nil, xerrors.Errorf("invalid message type '%T'", in) + } + + access, err := f.darcFactory.FromProto(pb.GetAccess()) + if err != nil { + return nil, xerrors.Errorf("couldn't decode access: %v", err) + } + + servAccess := serverTask{ + encoder: f.encoder, + darcFactory: f.darcFactory, + clientTask: clientTask{ + key: pb.GetKey(), + access: access.(Access), + }, + } + + return servAccess, nil +} diff --git a/ledger/arc/darc/task_test.go b/ledger/arc/darc/task_test.go new file mode 100644 index 000000000..45590ea7d --- /dev/null +++ b/ledger/arc/darc/task_test.go @@ -0,0 +1,166 @@ +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 TestClientTask_Pack(t *testing.T) { + task := clientTask{ + key: []byte{0x01}, + access: NewAccess(), + } + + pb, err := task.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*Task)(nil), pb) + + taskpb := pb.(*Task) + require.Equal(t, task.key, taskpb.GetKey()) + + _, err = task.Pack(fake.BadPackEncoder{}) + require.EqualError(t, err, "couldn't pack access: fake error") +} + +func TestClientTask_Fingerprint(t *testing.T) { + task := clientTask{ + key: []byte{0x01}, + access: Access{rules: map[string]expression{ + "\x02": {matches: map[string]struct{}{"\x03": {}}}, + }}, + } + + buffer := new(bytes.Buffer) + + err := task.Fingerprint(buffer, encoding.NewProtoEncoder()) + require.NoError(t, err) + require.Equal(t, "\x01\x02\x03", buffer.String()) + + err = task.Fingerprint(fake.NewBadHash(), nil) + require.EqualError(t, err, "couldn't write key: fake error") + + err = task.Fingerprint(fake.NewBadHashWithDelay(1), nil) + require.EqualError(t, err, + "couldn't fingerprint access: couldn't write key: fake error") +} + +func TestServerTask_Consume(t *testing.T) { + access, err := NewAccess().Evolve(UpdateAccessRule, fakeIdentity{buffer: []byte("doggy")}) + require.NoError(t, err) + + task := serverTask{ + encoder: encoding.NewProtoEncoder(), + darcFactory: NewFactory(), + clientTask: clientTask{key: []byte{0x01}, access: access}, + } + + call := &fake.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. + 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)) + + task.encoder = fake.BadPackEncoder{} + err = task.Consume(fakeContext{}, fakePage{}) + require.EqualError(t, err, "couldn't pack access: fake error") + + task.encoder = encoding.NewProtoEncoder() + err = task.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) + require.EqualError(t, err, "couldn't write access: oops") + + task.clientTask.key = []byte{0x01} + err = task.Consume(fakeContext{}, fakePage{err: xerrors.New("oops")}) + require.EqualError(t, err, "couldn't read value: oops") + + task.darcFactory = badArcFactory{} + err = task.Consume(fakeContext{}, fakePage{}) + require.EqualError(t, err, "couldn't decode access: oops") + + 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 TestTaskFactory_FromProto(t *testing.T) { + factory := NewTaskFactory().(taskFactory) + + taskpb := &Task{Key: []byte{0x02}, Access: &AccessProto{}} + task, err := factory.FromProto(taskpb) + require.NoError(t, err) + require.IsType(t, serverTask{}, task) + + _, err = factory.FromProto(nil) + require.EqualError(t, err, "invalid message type ''") + + factory.darcFactory = badArcFactory{} + _, err = factory.FromProto(&Task{}) + require.EqualError(t, err, "couldn't decode access: oops") +} + +// ----------------------------------------------------------------------------- +// 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) + + 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/byzcoin/contract/context.go b/ledger/byzcoin/contract/context.go new file mode 100644 index 000000000..2773a5d71 --- /dev/null +++ b/ledger/byzcoin/contract/context.go @@ -0,0 +1,71 @@ +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 taskContext struct { + basic.Context + arcFactory arc.AccessControlFactory + page inventory.Page +} + +// GetArc implements Context. It returns the access control stored in the given +// key if appropriate, otherwise an 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 from page: %v", err) + } + + if value == nil { + return nil, xerrors.Errorf("access does not exist") + } + + access, err := ctx.arcFactory.FromProto(value) + if err != nil { + return nil, xerrors.Errorf("couldn't decode access: %v", err) + } + + return access, nil +} + +// Read implements Context. It returns the instance stored at the given key, or +// an error if it does not find it. +func (ctx taskContext) Read(key []byte) (*Instance, error) { + entry, err := ctx.page.Read(key) + if err != nil { + return nil, xerrors.Errorf("couldn't read from page: %v", err) + } + + instance, ok := entry.(*Instance) + if !ok { + return nil, xerrors.Errorf("invalid message type '%T' != '%T'", + entry, instance) + } + + return instance, nil +} + +// SpawnContext is the context provided to a smart contract execution of a spawn +// transaction. +type SpawnContext struct { + Context + SpawnTask +} + +// InvokeContext is the context provided to a smart contract execution of an +// invoke transaction. +type InvokeContext struct { + Context + InvokeTask +} + +// DeleteContext is the context to delete an instance. +type DeleteContext struct { + Context + DeleteTask +} diff --git a/ledger/byzcoin/contract/context_test.go b/ledger/byzcoin/contract/context_test.go new file mode 100644 index 000000000..1dacdd1f3 --- /dev/null +++ b/ledger/byzcoin/contract/context_test.go @@ -0,0 +1,62 @@ +package contract + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "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) { + ctx := taskContext{ + 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, "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) { + ctx := taskContext{ + page: fakePage{ + store: map[string]proto.Message{ + "a": &Instance{ + ContractID: "abc", + Value: &any.Any{}, + }, + "b": &empty.Empty{}, + }, + }, + } + + 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, + "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/messages.pb.go b/ledger/byzcoin/contract/messages.pb.go new file mode 100644 index 000000000..e6b3e8985 --- /dev/null +++ b/ledger/byzcoin/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 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:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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 *SpawnTaskProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SpawnTaskProto.Unmarshal(m, b) +} +func (m *SpawnTaskProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SpawnTaskProto.Marshal(b, m, deterministic) +} +func (m *SpawnTaskProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_SpawnTaskProto.Merge(m, src) +} +func (m *SpawnTaskProto) XXX_Size() int { + return xxx_messageInfo_SpawnTaskProto.Size(m) +} +func (m *SpawnTaskProto) XXX_DiscardUnknown() { + xxx_messageInfo_SpawnTaskProto.DiscardUnknown(m) +} + +var xxx_messageInfo_SpawnTaskProto proto.InternalMessageInfo + +func (m *SpawnTaskProto) GetContractID() string { + if m != nil { + return m.ContractID + } + return "" +} + +func (m *SpawnTaskProto) GetArgument() *any.Any { + if m != nil { + return m.Argument + } + return nil +} + +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:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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 *InvokeTaskProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InvokeTaskProto.Unmarshal(m, b) +} +func (m *InvokeTaskProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InvokeTaskProto.Marshal(b, m, deterministic) +} +func (m *InvokeTaskProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_InvokeTaskProto.Merge(m, src) +} +func (m *InvokeTaskProto) XXX_Size() int { + return xxx_messageInfo_InvokeTaskProto.Size(m) +} +func (m *InvokeTaskProto) XXX_DiscardUnknown() { + xxx_messageInfo_InvokeTaskProto.DiscardUnknown(m) +} + +var xxx_messageInfo_InvokeTaskProto proto.InternalMessageInfo + +func (m *InvokeTaskProto) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *InvokeTaskProto) GetArgument() *any.Any { + if m != nil { + return m.Argument + } + return nil +} + +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 *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 *DeleteTaskProto) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DeleteTaskProto.Unmarshal(m, b) +} +func (m *DeleteTaskProto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DeleteTaskProto.Marshal(b, m, deterministic) +} +func (m *DeleteTaskProto) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeleteTaskProto.Merge(m, src) +} +func (m *DeleteTaskProto) XXX_Size() int { + return xxx_messageInfo_DeleteTaskProto.Size(m) +} +func (m *DeleteTaskProto) XXX_DiscardUnknown() { + xxx_messageInfo_DeleteTaskProto.DiscardUnknown(m) +} + +var xxx_messageInfo_DeleteTaskProto proto.InternalMessageInfo + +func (m *DeleteTaskProto) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func init() { + proto.RegisterType((*Instance)(nil), "contract.Instance") + proto.RegisterType((*SpawnTaskProto)(nil), "contract.SpawnTaskProto") + proto.RegisterType((*InvokeTaskProto)(nil), "contract.InvokeTaskProto") + proto.RegisterType((*DeleteTaskProto)(nil), "contract.DeleteTaskProto") +} + +func init() { + proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) +} + +var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ + // 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/consumer/smartcontract/messages.proto b/ledger/byzcoin/contract/messages.proto similarity index 50% rename from ledger/consumer/smartcontract/messages.proto rename to ledger/byzcoin/contract/messages.proto index fc8ce202f..7c09b0ff4 100644 --- a/ledger/consumer/smartcontract/messages.proto +++ b/ledger/byzcoin/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 SpawnTaskProto { string contractID = 1; google.protobuf.Any argument = 2; } -message Invoke { +message InvokeTaskProto { bytes key = 1; google.protobuf.Any argument = 2; } -message Delete { +message DeleteTaskProto { 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/contract/mod.go b/ledger/byzcoin/contract/mod.go new file mode 100644 index 000000000..c644825df --- /dev/null +++ b/ledger/byzcoin/contract/mod.go @@ -0,0 +1,31 @@ +// 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 ( + 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/contract/mod_test.go b/ledger/byzcoin/contract/mod_test.go new file mode 100644 index 000000000..1bfa8ce17 --- /dev/null +++ b/ledger/byzcoin/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{}, + &SpawnTaskProto{}, + &InvokeTaskProto{}, + &DeleteTaskProto{}, + } + + for _, m := range messages { + internal.CoverProtoMessage(t, m) + } +} diff --git a/ledger/byzcoin/contract/task.go b/ledger/byzcoin/contract/task.go new file mode 100644 index 000000000..0af74af11 --- /dev/null +++ b/ledger/byzcoin/contract/task.go @@ -0,0 +1,338 @@ +package contract + +import ( + "io" + + "github.com/golang/protobuf/proto" + "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" +) + +// SpawnTask is a client task of a transaction to create a new instance. +// +// - implements basic.ClientTask +type SpawnTask struct { + ContractID string + Argument proto.Message +} + +// Pack implements encoding.Packable. It returns the protobuf message of the +// task. +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 := &SpawnTaskProto{ + ContractID: act.ContractID, + Argument: argument, + } + + return pb, nil +} + +// Fingerprint implements encoding.Fingerprinter. It serializes the task into +// the writer in a deterministic way. +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) + } + + err = e.MarshalStable(w, act.Argument) + if err != nil { + return xerrors.Errorf("couldn't write argument: %v", err) + } + + return nil +} + +// InvokeTask is a client task of a transaction to update an existing instance +// if the access rights control allows it. +// +// - implements basic.ClientTask +type InvokeTask struct { + Key []byte + Argument proto.Message +} + +// Pack implements encoding.Packable. It returns the protobuf message of the +// task. +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 := &InvokeTaskProto{ + Key: act.Key, + Argument: argument, + } + + return pb, nil +} + +// Fingerprint implements encoding.Fingeprinter. It serializes the task into the +// writer in a deterministic way. +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) + } + + err = e.MarshalStable(w, act.Argument) + if err != nil { + return xerrors.Errorf("couldn't write argument: %v", err) + } + + return nil +} + +// DeleteTask is a client task of a transaction to mark an instance as deleted +// so that it cannot be updated anymore. +// +// - implements basic.ClientTask +type DeleteTask struct { + Key []byte +} + +// Pack implements encoding.Packable. It returns the protobuf message of the +// task. +func (a DeleteTask) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &DeleteTaskProto{Key: a.Key}, nil +} + +// Fingerprint implements encoding.Fingerprinter. It serializes the task into +// the writer in a deterministic way. +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) + } + + return nil +} + +// serverTask is a contract task that can be consumed to update an inventory +// page. +// +// - implements basic.ServerTask +type serverTask struct { + basic.ClientTask + contracts map[string]Contract + arcFactory arc.AccessControlFactory + encoder encoding.ProtoMarshaler +} + +// Consume implements basic.ServerTask. It updates the page according to the +// task definition. +func (act serverTask) Consume(ctx basic.Context, page inventory.WritablePage) error { + txCtx := taskContext{ + Context: ctx, + arcFactory: act.arcFactory, + page: page, + } + + var instance *Instance + var err error + switch task := act.ClientTask.(type) { + case SpawnTask: + instance, err = act.consumeSpawn(SpawnContext{ + Context: txCtx, + SpawnTask: task, + }) + case InvokeTask: + instance, err = act.consumeInvoke(InvokeContext{ + Context: txCtx, + InvokeTask: task, + }) + case DeleteTask: + instance, err = act.consumeDelete(DeleteContext{ + Context: txCtx, + DeleteTask: task, + }) + default: + return xerrors.Errorf("invalid task type '%T'", act.ClientTask) + } + + if err != nil { + // No wrapping to avoid redundancy in the error message. + return err + } + + err = page.Write(instance.Key, instance) + if err != nil { + return xerrors.Errorf("couldn't write instance to page: %v", err) + } + + return nil +} + +func (act serverTask) 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, xerrors.Errorf("couldn't pack value: %v", err) + } + + instance := &Instance{ + Key: ctx.GetID(), + AccessControl: arcid, + ContractID: ctx.ContractID, + Deleted: false, + Value: valueAny, + } + + return instance, nil +} + +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) + } + + 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, xerrors.Errorf("couldn't pack value: %v", err) + } + + instance.Value = valueAny + + return instance, nil +} + +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) + } + + instance.Deleted = true + + return instance, nil +} + +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) + } + + 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 +} + +// TaskFactory is a factory to decode protobuf messages into transaction tasks +// and register static contracts. +// +// - implements basic.TaskFactory +type TaskFactory struct { + contracts map[string]Contract + arcFactory arc.AccessControlFactory + encoder encoding.ProtoMarshaler +} + +// 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(), + } +} + +// Register registers the contract using the name as the identifier. If an +// identifier already exists, it will be overwritten. +func (f TaskFactory) Register(name string, contract Contract) { + f.contracts[name] = contract +} + +// FromProto implements basic.TaskFactory. It returns the server task of a +// protobuf message when appropriate, otherwise an error. +func (f TaskFactory) FromProto(in proto.Message) (basic.ServerTask, error) { + inAny, ok := in.(*any.Any) + if ok { + var err error + in, err = f.encoder.UnmarshalDynamicAny(inAny) + if err != nil { + return nil, xerrors.Errorf("couldn't unmarshal message: %v", err) + } + } + + task := serverTask{ + contracts: f.contracts, + arcFactory: f.arcFactory, + encoder: f.encoder, + } + + switch pb := in.(type) { + case *SpawnTaskProto: + task.ClientTask = SpawnTask{ + ContractID: pb.GetContractID(), + Argument: pb.GetArgument(), + } + case *InvokeTaskProto: + task.ClientTask = InvokeTask{ + Key: pb.GetKey(), + Argument: pb.GetArgument(), + } + case *DeleteTaskProto: + task.ClientTask = DeleteTask{ + Key: pb.GetKey(), + } + default: + return nil, xerrors.Errorf("invalid message type '%T'", in) + } + + return task, nil +} diff --git a/ledger/byzcoin/contract/task_test.go b/ledger/byzcoin/contract/task_test.go new file mode 100644 index 000000000..d52b8fee5 --- /dev/null +++ b/ledger/byzcoin/contract/task_test.go @@ -0,0 +1,363 @@ +package contract + +import ( + "bytes" + "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 TestSpawnTask_Pack(t *testing.T) { + task := SpawnTask{ + ContractID: "deadbeef", + Argument: &empty.Empty{}, + } + + pb, err := task.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*SpawnTaskProto)(nil), pb) + + taskpb := pb.(*SpawnTaskProto) + require.Equal(t, task.ContractID, taskpb.GetContractID()) + require.True(t, ptypes.Is(taskpb.GetArgument(), task.Argument)) + + _, err = task.Pack(fake.BadMarshalAnyEncoder{}) + require.EqualError(t, err, "couldn't pack argument: fake error") +} + +func TestSpawnTask_Fingerprint(t *testing.T) { + task := SpawnTask{ + ContractID: "deadbeef", + Argument: &empty.Empty{}, + } + + buffer := new(bytes.Buffer) + encoder := encoding.NewProtoEncoder() + + err := task.Fingerprint(buffer, encoder) + require.NoError(t, err) + require.Equal(t, "deadbeef{}", buffer.String()) + + err = task.Fingerprint(fake.NewBadHash(), encoder) + require.EqualError(t, err, "couldn't write contract: fake error") + + err = task.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) + require.EqualError(t, err, "couldn't write argument: fake error") +} + +func TestInvokeTask_Pack(t *testing.T) { + task := InvokeTask{ + Key: []byte{0x01}, + Argument: &empty.Empty{}, + } + + pb, err := task.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*InvokeTaskProto)(nil), pb) + + taskpb := pb.(*InvokeTaskProto) + require.Equal(t, task.Key, taskpb.GetKey()) + require.True(t, ptypes.Is(taskpb.GetArgument(), task.Argument)) + + _, err = task.Pack(fake.BadMarshalAnyEncoder{}) + require.EqualError(t, err, "couldn't pack argument: fake error") +} + +func TestInvokeTask_WriteTo(t *testing.T) { + task := InvokeTask{ + Key: []byte{0x01}, + Argument: &empty.Empty{}, + } + + buffer := new(bytes.Buffer) + encoder := encoding.NewProtoEncoder() + + err := task.Fingerprint(buffer, encoder) + require.NoError(t, err) + require.Equal(t, "\x01{}", buffer.String()) + + err = task.Fingerprint(fake.NewBadHash(), encoder) + require.EqualError(t, err, "couldn't write key: fake error") + + err = task.Fingerprint(buffer, fake.BadMarshalStableEncoder{}) + require.EqualError(t, err, "couldn't write argument: fake error") +} + +func TestDeleteTask_Pack(t *testing.T) { + task := DeleteTask{ + Key: []byte{0x01}, + } + + pb, err := task.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.IsType(t, (*DeleteTaskProto)(nil), pb) + + taskpb := pb.(*DeleteTaskProto) + require.Equal(t, task.Key, taskpb.GetKey()) +} + +func TestDeleteTask_WriteTo(t *testing.T) { + task := DeleteTask{ + Key: []byte{0x01}, + } + + buffer := new(bytes.Buffer) + encoder := encoding.NewProtoEncoder() + + err := task.Fingerprint(buffer, encoder) + require.NoError(t, err) + require.Equal(t, "\x01", buffer.String()) + + err = task.Fingerprint(fake.NewBadHash(), encoder) + require.EqualError(t, err, "couldn't write key: fake error") +} + +func TestServerTask_Consume(t *testing.T) { + factory := &fakeAccessFactory{access: &fakeAccess{match: true}} + contracts := map[string]Contract{ + "fake": fakeContract{}, + "bad": fakeContract{err: xerrors.New("oops")}, + } + + task := serverTask{ + ClientTask: SpawnTask{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 task. + err := task.Consume(fakeContext{id: []byte("b")}, page) + require.NoError(t, err) + + err = task.Consume(fakeContext{id: []byte("a")}, page) + require.EqualError(t, err, "instance already exists") + + task.ClientTask = SpawnTask{ContractID: "unknown"} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "contract 'unknown' not found") + + task.ClientTask = SpawnTask{ContractID: "bad"} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't execute spawn: oops") + + task.ClientTask = SpawnTask{ContractID: "fake"} + factory.err = xerrors.New("oops") + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, + "no access: couldn't read access: couldn't decode access: oops") + + factory.err = nil + task.encoder = fake.BadMarshalAnyEncoder{} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't pack value: fake error") + + // 2. Consume an invoke task. + task.encoder = encoding.NewProtoEncoder() + task.ClientTask = InvokeTask{Key: []byte("b")} + + factory.access.calls = make([][]interface{}, 0) + 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]) + + task.ClientTask = InvokeTask{Key: []byte("c")} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, + "couldn't read the instance: invalid message type '' != '*contract.Instance'") + + task.ClientTask = InvokeTask{Key: []byte("z")} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "contract 'unknown' not found") + + task.ClientTask = InvokeTask{Key: []byte("b")} + factory.err = xerrors.New("oops") + 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 = 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 + task.ClientTask = InvokeTask{Key: []byte("y")} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "couldn't invoke: oops") + + 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 task. + task.ClientTask = DeleteTask{Key: []byte("a")} + + err = task.Consume(fakeContext{}, page) + require.NoError(t, err) + + task.ClientTask = DeleteTask{Key: []byte("c")} + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, + "couldn't read the instance: invalid message type '' != '*contract.Instance'") + + // 4. Consume an invalid task. + 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") + + task.ClientTask = nil + err = task.Consume(fakeContext{}, page) + require.EqualError(t, err, "invalid task type ''") +} + +func TestTaskFactory_Register(t *testing.T) { + factory := NewTaskFactory() + + 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 TestTaskFactory_FromProto(t *testing.T) { + factory := NewTaskFactory() + + spawnpb := &SpawnTaskProto{ContractID: "A"} + task, err := factory.FromProto(spawnpb) + require.NoError(t, err) + require.NotNil(t, task) + + spawnAny, err := ptypes.MarshalAny(spawnpb) + require.NoError(t, err) + task, err = factory.FromProto(spawnAny) + require.NoError(t, err) + require.NotNil(t, task) + + invokepb := &InvokeTaskProto{Key: []byte{0x01}} + task, err = factory.FromProto(invokepb) + require.NoError(t, err) + require.NotNil(t, task) + + deletepb := &DeleteTaskProto{Key: []byte{0x01}} + task, err = factory.FromProto(deletepb) + require.NoError(t, err) + require.NotNil(t, task) + + _, 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") +} + +// ----------------------------------------------------------------------------- +// 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 + errRead error + errWrite error +} + +func (page fakePage) Read(key []byte) (proto.Message, error) { + return page.store[string(key)], page.errRead +} + +func (page fakePage) Write(key []byte, value proto.Message) error { + page.store[string(key)] = value + return page.errWrite +} + +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/messages.pb.go b/ledger/byzcoin/messages.pb.go index 1d6ba5684..595f72ac1 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,56 +22,14 @@ 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"` - Footprint []byte `protobuf:"bytes,2,opt,name=footprint,proto3" json:"footprint,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"` + // Fingerprint is an integrity check of the final state of the inventory after + // applying the genesis payload. + Fingerprint []byte `protobuf:"bytes,2,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `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,27 +60,27 @@ 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 } return nil } -func (m *GenesisPayload) GetFootprint() []byte { +func (m *GenesisPayload) GetFingerprint() []byte { if m != nil { - return m.Footprint + return m.Fingerprint } return nil } // BlockPayload is the message that will be stored in the blocks. It is composed -// of the transactions and the footprint of the new inventory. +// of the transactions and the fingerprint of the new inventory. type BlockPayload struct { Transactions []*any.Any `protobuf:"bytes,1,rep,name=transactions,proto3" json:"transactions,omitempty"` - // Footprint is an integrity check of the final state of the inventory after + // Fingerprint is an integrity check of the final state of the inventory after // applying the transactions. - Footprint []byte `protobuf:"bytes,2,opt,name=footprint,proto3" json:"footprint,omitempty"` + Fingerprint []byte `protobuf:"bytes,2,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -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 { @@ -159,15 +118,14 @@ func (m *BlockPayload) GetTransactions() []*any.Any { return nil } -func (m *BlockPayload) GetFootprint() []byte { +func (m *BlockPayload) GetFingerprint() []byte { if m != nil { - return m.Footprint + return m.Fingerprint } return nil } func init() { - proto.RegisterType((*Roster)(nil), "byzcoin.Roster") proto.RegisterType((*GenesisPayload)(nil), "byzcoin.GenesisPayload") proto.RegisterType((*BlockPayload)(nil), "byzcoin.BlockPayload") } @@ -177,20 +135,19 @@ 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, + // 209 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0x31, 0x4b, 0x04, 0x31, + 0x10, 0x85, 0x89, 0xc2, 0x09, 0xd9, 0x65, 0x8b, 0xc5, 0x62, 0xbd, 0x2a, 0x1c, 0x22, 0x5b, 0x25, + 0x70, 0x36, 0xb6, 0xda, 0xd8, 0x4a, 0x4a, 0xbb, 0xec, 0xde, 0x5c, 0x88, 0xc6, 0x99, 0x23, 0x13, + 0x8b, 0xf8, 0xeb, 0x85, 0xcd, 0x1d, 0xb8, 0x95, 0xd5, 0x30, 0x6f, 0xbe, 0x79, 0x8f, 0x27, 0xbb, + 0x2f, 0x60, 0x76, 0x1e, 0x58, 0x9f, 0x12, 0x65, 0xea, 0x6f, 0xa6, 0xf2, 0x33, 0x53, 0xc0, 0xed, + 0x9d, 0x27, 0xf2, 0x11, 0xcc, 0x22, 0x4f, 0xdf, 0x47, 0xe3, 0xb0, 0x54, 0x66, 0x7b, 0x1f, 0xe1, + 0xe0, 0x21, 0x99, 0x33, 0x6a, 0x12, 0x71, 0x86, 0x64, 0xd6, 0x4e, 0xbb, 0x77, 0xd9, 0xbd, 0x02, + 0x02, 0x07, 0x7e, 0x73, 0x25, 0x92, 0x3b, 0xf4, 0x0f, 0x72, 0x53, 0xd1, 0x41, 0x28, 0x31, 0x36, + 0xfb, 0x4e, 0xd7, 0x55, 0xdb, 0x65, 0xd8, 0xf3, 0xb5, 0x57, 0xb2, 0x39, 0x06, 0xf4, 0x90, 0x4e, + 0x29, 0x60, 0x1e, 0xae, 0x94, 0x18, 0x5b, 0xfb, 0x57, 0xda, 0x7d, 0xc8, 0xf6, 0x25, 0xd2, 0xfc, + 0x79, 0x71, 0x7e, 0x92, 0x6d, 0x4e, 0x0e, 0xd9, 0xcd, 0x39, 0x10, 0xf2, 0x20, 0xd4, 0xf5, 0xd8, + 0xec, 0x6f, 0x75, 0xed, 0xa0, 0x2f, 0x1d, 0xf4, 0x33, 0x16, 0xbb, 0x22, 0xff, 0xcf, 0x9a, 0x36, + 0xcb, 0xf7, 0xe3, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0x5a, 0xa9, 0x7a, 0x2a, 0x01, 0x00, + 0x00, } diff --git a/ledger/byzcoin/messages.proto b/ledger/byzcoin/messages.proto index 86084f601..cf3266d90 100644 --- a/ledger/byzcoin/messages.proto +++ b/ledger/byzcoin/messages.proto @@ -3,23 +3,25 @@ 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; - bytes footprint = 2; + // Roster is the initial roster for the chain. It can then evolves through + // transactions. + roster.Roster roster = 1; + + // Fingerprint is an integrity check of the final state of the inventory after + // applying the genesis payload. + bytes fingerprint = 2; } // BlockPayload is the message that will be stored in the blocks. It is composed -// of the transactions and the footprint of the new inventory. +// of the transactions and the fingerprint of the new inventory. message BlockPayload { repeated google.protobuf.Any transactions = 1; - // Footprint is an integrity check of the final state of the inventory after + // Fingerprint is an integrity check of the final state of the inventory after // applying the transactions. - bytes footprint = 2; + bytes fingerprint = 2; } diff --git a/ledger/byzcoin/mod.go b/ledger/byzcoin/mod.go index a840f7baa..3c9873396 100644 --- a/ledger/byzcoin/mod.go +++ b/ledger/byzcoin/mod.go @@ -15,25 +15,23 @@ 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/inventory" + "go.dedis.ch/fabric/ledger/byzcoin/roster" + "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" "go.dedis.ch/fabric/mino/gossip" "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 timeoutRoundTime = 1 * time.Minute ) -var ( - // AuthorityKey is a reserved instance key for the roster of the chain. It - // may evolve after each block. - authorityKey = []byte{0x01} -) +var rosterValueKey = []byte(roster.RosterValueKey) // Ledger is a distributed public ledger implemented by using a blockchain. Each // node is responsible for collecting transactions from clients and propose them @@ -48,66 +46,58 @@ type Ledger struct { gossiper gossip.Gossiper bag *txBag proc *txProcessor - governance governance - consumer consumer.Consumer + governance viewchange.Governance 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(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 consumer.GetTransactionFactory().FromProto(pb) + return txFactory.FromProto(pb) } - proc := newTxProcessor(consumer) - gov := governance{ - inventory: proc.inventory, - rosterFactory: newRosterFactory(mino.GetAddressFactory(), signer.GetPublicKeyFactory()), - } - 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, - consumer: consumer, encoder: encoding.NewProtoEncoder(), + txFactory: txFactory, 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) - } - - instancepb, 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) + value, err := page.Read(key) if err != nil { - return nil, xerrors.Errorf("couldn't decode instance: %v", err) + return nil, err } - return instance, nil + return value, nil } // Listen implements ledger.Ledger. It starts to participate in the blockchain @@ -173,7 +163,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 +204,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 +251,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)) @@ -286,7 +274,7 @@ func (ldgr *Ledger) stagePayload(txs []consumer.Transaction) (*BlockPayload, err return nil, xerrors.Errorf("couldn't process the txs: %v", err) } - payload.Footprint = page.GetFootprint() + payload.Fingerprint = page.GetFingerprint() return payload, nil } @@ -308,10 +296,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 @@ -351,19 +337,19 @@ 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.GetAuthorityFactory().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 { return xerrors.Errorf("couldn't store genesis payload: %v", err) } - payload.Footprint = page.GetFootprint() + payload.Fingerprint = page.GetFingerprint() err = a.bcActor.InitChain(payload, authority) if err != nil { @@ -375,7 +361,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. @@ -392,40 +378,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 - rosterFactory rosterFactory -} - -// 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.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 (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 6818698e8..9e2d0a9ce 100644 --- a/ledger/byzcoin/mod_test.go +++ b/ledger/byzcoin/mod_test.go @@ -8,26 +8,21 @@ 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" internal "go.dedis.ch/fabric/internal/testing" "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/byzcoin/roster" + "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) { messages := []proto.Message{ &BlockPayload{}, - &Roster{}, &GenesisPayload{}, } @@ -60,10 +55,11 @@ func TestLedger_Basic(t *testing.T) { defer cancel() txs := ledgers[2].Watch(ctx) - txFactory := smartcontract.NewTransactionFactory(bls.NewSigner()) + signer := bls.NewSigner() + txFactory := basic.NewTransactionFactory(signer, nil) - // Try to create a DARC. - tx, err := txFactory.New(contract.NewGenesisAction()) + // 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) @@ -77,17 +73,18 @@ func TestLedger_Basic(t *testing.T) { t.Fatal("timeout 1") } - instance, err := ledgers[2].GetInstance(tx.GetID()) + roster, err := ledgers[2].(*Ledger).governance.GetAuthority(1) 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()) + // The last participant over 20 should have been removed from the current + // chain roster. + require.Equal(t, 19, roster.Len()) - // Then update it. - tx, err = txFactory.New(contract.NewUpdateAction(tx.GetID())) + // 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 { @@ -97,72 +94,25 @@ func TestLedger_Basic(t *testing.T) { case <-time.After(1 * time.Second): t.Fatal("timeout 2") } -} - -func TestLedger_GetInstance(t *testing.T) { - ledger := &Ledger{ - bc: fakeBlockchain{}, - proc: &txProcessor{ - inventory: fakeInventory{ - page: &fakePage{}, - }, - }, - consumer: fakeConsumer{}, - } - instance, err := ledger.GetInstance([]byte{0xab}) + value, err := ledgers[2].GetValue(tx.GetID()) 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") + require.IsType(t, (*darc.AccessProto)(nil), value) - 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{}, - pubkeyFactory: fake.PublicKeyFactory{}, - } - - roster := factory.New(fake.NewAuthority(3, fake.NewSigner)) - rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) + // Then update it. + tx, err = txFactory.New(darc.NewUpdate(tx.GetID(), access)) require.NoError(t, err) - gov := governance{ - rosterFactory: factory, - inventory: fakeInventory{page: &fakePage{value: rosterpb}}, - } - - authority, err := gov.GetAuthority(3) + err = actors[0].AddTransaction(tx) 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") - 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") + } } // ----------------------------------------------------------------------------- @@ -183,7 +133,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,26 +145,10 @@ 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 -} - -func (b fakeBlock) GetIndex() uint64 { - return 0 -} - -type fakeBlockchain struct { - blockchain.Blockchain - err error -} +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) -func (bc fakeBlockchain) GetBlock() (blockchain.Block, error) { - return fakeBlock{}, bc.err + return access } diff --git a/ledger/byzcoin/roster/messages.pb.go b/ledger/byzcoin/roster/messages.pb.go new file mode 100644 index 000000000..de1d8c798 --- /dev/null +++ b/ledger/byzcoin/roster/messages.pb.go @@ -0,0 +1,186 @@ +// 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 +} + +// ChangeSet is the message stored in the inventory for the change set of a +// block. +type ChangeSet struct { + 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:"-"` +} + +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) GetIndex() uint64 { + if m != nil { + return m.Index + } + return 0 +} + +func (m *ChangeSet) GetRemove() []uint32 { + if m != nil { + return m.Remove + } + 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:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +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 *Task) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Task.Unmarshal(m, b) +} +func (m *Task) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Task.Marshal(b, m, deterministic) +} +func (m *Task) XXX_Merge(src proto.Message) { + xxx_messageInfo_Task.Merge(m, src) +} +func (m *Task) XXX_Size() int { + return xxx_messageInfo_Task.Size(m) +} +func (m *Task) XXX_DiscardUnknown() { + xxx_messageInfo_Task.DiscardUnknown(m) +} + +var xxx_messageInfo_Task proto.InternalMessageInfo + +func (m *Task) 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((*Task)(nil), "roster.Task") +} + +func init() { + proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) +} + +var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ + // 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, + 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, 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 new file mode 100644 index 000000000..cc9b8ba61 --- /dev/null +++ b/ledger/byzcoin/roster/messages.proto @@ -0,0 +1,23 @@ +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; +} + +// ChangeSet is the message stored in the inventory for the change set of a +// block. +message ChangeSet { + 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.go b/ledger/byzcoin/roster/mod.go similarity index 81% rename from ledger/byzcoin/roster.go rename to ledger/byzcoin/roster/mod.go index fb81f5920..76fdfaca2 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 @@ -85,10 +87,30 @@ 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 +// after applying the change set. The removals must be sorted and unique or the +// behaviour could be unexpected. +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. @@ -149,19 +171,24 @@ 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 { +// 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()) @@ -180,6 +207,8 @@ func (f rosterFactory) New(authority crypto.CollectiveAuthority) roster { 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_test.go b/ledger/byzcoin/roster/mod_test.go similarity index 73% rename from ledger/byzcoin/roster_test.go rename to ledger/byzcoin/roster/mod_test.go index 3794f8d6c..37ba64e26 100644 --- a/ledger/byzcoin/roster_test.go +++ b/ledger/byzcoin/roster/mod_test.go @@ -1,14 +1,30 @@ -package byzcoin +package roster import ( "testing" + "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" "go.dedis.ch/fabric/mino" ) +func TestMessages(t *testing.T) { + messages := []proto.Message{ + &Roster{}, + &ChangeSet{}, + &Task{}, + } + + 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 +59,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 +75,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, @@ -84,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()) @@ -107,8 +130,28 @@ 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 := rosterFactory{}.New(fake.NewAuthority(3, fake.NewSigner)).(roster) rosterpb, err := roster.Pack(encoding.NewProtoEncoder()) require.NoError(t, err) @@ -127,7 +170,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) diff --git a/ledger/byzcoin/roster/task.go b/ledger/byzcoin/roster/task.go new file mode 100644 index 000000000..990727566 --- /dev/null +++ b/ledger/byzcoin/roster/task.go @@ -0,0 +1,258 @@ +package roster + +import ( + "encoding/binary" + "io" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "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 ( + // RosterValueKey is the key used to store the roster. + RosterValueKey = "roster_value" + + // RosterChangeSetKey is the key used to store the roster change set. + RosterChangeSetKey = "roster_changeset" + + // RosterArcKey is the ke used to store the access rights control of the + // roster. + RosterArcKey = "roster_arc" +) + +var rosterValueKey = []byte(RosterValueKey) +var rosterChangeSetKey = []byte(RosterChangeSetKey) + +// clientTask is the client task implementation to update the roster of a +// consensus using the transactions for access rights control. +// +// - implements basic.ClientTask +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.ClientTask { + 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 := &Task{ + 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. +// +// - implements basic.ServerTask +type serverTask struct { + clientTask + encoder encoding.ProtoMarshaler + rosterFactory viewchange.AuthorityFactory +} + +// 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 + // TODO: implement + + // 2. Update the roster stored in the inventory. + value, err := page.Read(rosterValueKey) + if err != nil { + return xerrors.Errorf("couldn't read roster: %v", err) + } + + roster, err := t.rosterFactory.FromProto(value) + if err != nil { + return xerrors.Errorf("couldn't decode roster: %v", err) + } + + changeset := t.GetChangeSet() + roster = roster.Apply(changeset) + + value, err = t.encoder.Pack(roster) + if err != nil { + return xerrors.Errorf("couldn't encode roster: %v", err) + } + + err = page.Write(rosterValueKey, value) + if err != nil { + return xerrors.Errorf("couldn't write roster: %v", err) + } + + // 3. Store the changeset so it can be read later on. + err = t.updateChangeSet(page) + if err != nil { + return xerrors.Errorf("couldn't update change set: %v", err) + } + + return nil +} + +func (t serverTask) updateChangeSet(page inventory.WritablePage) error { + pb, err := page.Read(rosterChangeSetKey) + if err != nil { + 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. + // TODO: unique and sorted + changesetpb.Remove = append(changesetpb.Remove, t.remove...) + + err = page.Write(rosterChangeSetKey, changesetpb) + if err != nil { + return xerrors.Errorf("couldn't write to page: %v", 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 task 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(rosterValueKey) + 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(rosterChangeSetKey) + if err != nil { + return cs, xerrors.Errorf("couldn't read from page: %v", err) + } + + changesetpb, ok := pb.(*ChangeSet) + 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 + } + + cs.Remove = changesetpb.GetRemove() + + return cs, nil +} + +// 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{ + clientTask: clientTask{ + remove: pb.Remove, + }, + encoder: f.encoder, + rosterFactory: f.rosterFactory, + } + return task, nil +} diff --git a/ledger/byzcoin/roster/task_test.go b/ledger/byzcoin/roster/task_test.go new file mode 100644 index 000000000..3ed35aa74 --- /dev/null +++ b/ledger/byzcoin/roster/task_test.go @@ -0,0 +1,236 @@ +package roster + +import ( + "bytes" + "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/inventory" + "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{ + RosterValueKey: rosterpb, + // Change set is not set yet. + } + + err = task.Consume(nil, fakePage{values: values}) + require.NoError(t, err) + + changesetpb := values[RosterChangeSetKey] + 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[RosterChangeSetKey] + // 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) + + 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 ''") +} + +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.WritablePage + values map[string]proto.Message + errRead error + errWrite error + counter *fake.Counter +} + +func (p fakePage) GetIndex() uint64 { + return 5 +} + +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 { + inventory.Inventory + value proto.Message + err error + errPage error +} + +func (i fakeInventory) GetPage(uint64) (inventory.Page, error) { + values := map[string]proto.Message{ + RosterValueKey: i.value, + RosterChangeSetKey: i.value, + } + return fakePage{values: values, errRead: i.errPage}, i.err +} diff --git a/ledger/byzcoin/task.go b/ledger/byzcoin/task.go new file mode 100644 index 000000000..dd053069d --- /dev/null +++ b/ledger/byzcoin/task.go @@ -0,0 +1,74 @@ +package byzcoin + +import ( + "reflect" + + "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" +) + +// taskFactory is a task factory that can process several types of tasks. +// +// - implements basic.TaskFactory +type taskFactory struct { + encoder encoding.ProtoMarshaler + registry map[reflect.Type]basic.TaskFactory +} + +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.TaskFactory), + } + + rosterFactory := roster.NewRosterFactory(m.GetAddressFactory(), signer.GetPublicKeyFactory()) + gov := roster.NewTaskManager(rosterFactory, i) + + 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.TaskFactory) { + key := reflect.TypeOf(pb) + f.registry[key] = factory +} + +// 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 + 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 task type '%T'", in) + } + + task, err := factory.FromProto(in) + if err != nil { + return nil, err + } + + return task, nil +} diff --git a/ledger/byzcoin/task_test.go b/ledger/byzcoin/task_test.go new file mode 100644 index 000000000..f61ee0488 --- /dev/null +++ b/ledger/byzcoin/task_test.go @@ -0,0 +1 @@ +package byzcoin 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..298acbb5a 100644 --- a/ledger/byzcoin/txproc.go +++ b/ledger/byzcoin/txproc.go @@ -5,11 +5,8 @@ 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 +15,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, i inventory.Inventory) *txProcessor { return &txProcessor{ - encoder: encoding.NewProtoEncoder(), - inventory: mem.NewInventory(), - consumer: c, + inventory: i, + txFactory: f, } } @@ -47,7 +42,7 @@ func (proc *txProcessor) Validate(index uint64, data proto.Message) error { } case *BlockPayload: fabric.Logger.Trace(). - Hex("footprint", payload.GetFootprint()). + Hex("fingerprint", payload.GetFingerprint()). Msgf("validating block payload") page, err := proc.process(payload) @@ -59,9 +54,9 @@ func (proc *txProcessor) Validate(index uint64, data proto.Message) error { return xerrors.Errorf("invalid index %d != %d", page.GetIndex(), index) } - if !bytes.Equal(page.GetFootprint(), payload.GetFootprint()) { - return xerrors.Errorf("mismatch payload footprint '%#x' != '%#x'", - page.GetFootprint(), payload.GetFootprint()) + if !bytes.Equal(page.GetFingerprint(), payload.GetFingerprint()) { + return xerrors.Errorf("mismatch payload fingerprint '%#x' != '%#x'", + page.GetFingerprint(), payload.GetFingerprint()) } default: return xerrors.Errorf("invalid message type '%T'", data) @@ -72,7 +67,7 @@ func (proc *txProcessor) Validate(index uint64, data proto.Message) error { func (proc *txProcessor) setup(payload *GenesisPayload) (inventory.Page, error) { page, err := proc.inventory.Stage(func(page inventory.WritablePage) error { - err := page.Write(authorityKey, payload.Roster) + err := page.Write(rosterValueKey, payload.Roster) if err != nil { return xerrors.Errorf("couldn't write roster: %v", err) } @@ -87,39 +82,25 @@ func (proc *txProcessor) setup(payload *GenesisPayload) (inventory.Page, error) } func (proc *txProcessor) process(payload *BlockPayload) (inventory.Page, error) { - page := proc.inventory.GetStagingPage(payload.GetFootprint()) + page := proc.inventory.GetStagingPage(payload.GetFingerprint()) if page != nil { // Page has already been processed previously. return page, nil } 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 @@ -128,7 +109,7 @@ func (proc *txProcessor) process(payload *BlockPayload) (inventory.Page, error) return nil, xerrors.Errorf("couldn't stage new page: %v", err) } - fabric.Logger.Trace().Msgf("staging new inventory %#x", page.GetFootprint()) + fabric.Logger.Trace().Msgf("staging new inventory %#x", page.GetFingerprint()) return page, err } @@ -136,20 +117,20 @@ func (proc *txProcessor) process(payload *BlockPayload) (inventory.Page, error) // payload as it should have previously been processed. It returns nil if the // commit is a success, otherwise an error. func (proc *txProcessor) Commit(data proto.Message) error { - var footprint []byte + var fingerprint []byte switch payload := data.(type) { case *GenesisPayload: - footprint = payload.GetFootprint() + fingerprint = payload.GetFingerprint() case *BlockPayload: - footprint = payload.GetFootprint() + fingerprint = payload.GetFingerprint() default: return xerrors.Errorf("invalid message type '%T'", data) } - err := proc.inventory.Commit(footprint) + err := proc.inventory.Commit(fingerprint) if err != nil { - return xerrors.Errorf("couldn't commit to page '%#x': %v", footprint, err) + return xerrors.Errorf("couldn't commit to page '%#x': %v", fingerprint, err) } return nil diff --git a/ledger/byzcoin/txproc_test.go b/ledger/byzcoin/txproc_test.go index 04ad3e7ba..64e31ab5b 100644 --- a/ledger/byzcoin/txproc_test.go +++ b/ledger/byzcoin/txproc_test.go @@ -5,18 +5,13 @@ 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" "golang.org/x/xerrors" ) func TestTxProcessor_Validate(t *testing.T) { - proc := newTxProcessor(fakeConsumer{}) - proc.inventory = fakeInventory{} + proc := newTxProcessor(nil, fakeInventory{}) err := proc.Validate(0, &BlockPayload{}) require.NoError(t, err) @@ -44,15 +39,13 @@ func TestTxProcessor_Validate(t *testing.T) { err = proc.Validate(0, &GenesisPayload{}) require.EqualError(t, err, "index 0 expected but got 1") - proc.inventory = fakeInventory{footprint: []byte{0xab}} - err = proc.Validate(0, &BlockPayload{Footprint: []byte{0xcd}}) - require.EqualError(t, err, "mismatch payload footprint '0xab' != '0xcd'") + proc.inventory = fakeInventory{fingerprint: []byte{0xab}} + err = proc.Validate(0, &BlockPayload{Fingerprint: []byte{0xcd}}) + require.EqualError(t, err, "mismatch payload fingerprint '0xab' != '0xcd'") } func TestTxProcessor_Process(t *testing.T) { - proc := newTxProcessor(nil) - proc.inventory = fakeInventory{page: &fakePage{index: 999}} - proc.consumer = fakeConsumer{key: []byte{0xab}} + proc := newTxProcessor(nil, fakeInventory{page: &fakePage{index: 999}}) page, err := proc.process(&BlockPayload{}) require.NoError(t, err) @@ -60,38 +53,14 @@ 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") + require.NotNil(t, page) } func TestTxProcessor_Commit(t *testing.T) { - proc := newTxProcessor(nil) - proc.inventory = fakeInventory{} + proc := newTxProcessor(nil, fakeInventory{}) err := proc.Commit(&BlockPayload{}) require.NoError(t, err) @@ -100,7 +69,7 @@ func TestTxProcessor_Commit(t *testing.T) { require.EqualError(t, err, "invalid message type ''") proc.inventory = fakeInventory{err: xerrors.New("oops")} - err = proc.Commit(&BlockPayload{Footprint: []byte{0xab}}) + err = proc.Commit(&BlockPayload{Fingerprint: []byte{0xab}}) require.EqualError(t, err, "couldn't commit to page '0xab': oops") } @@ -109,19 +78,19 @@ func TestTxProcessor_Commit(t *testing.T) { type fakePage struct { inventory.WritablePage - index uint64 - footprint []byte - err error - value proto.Message - calls [][]interface{} + index uint64 + fingerprint []byte + err error + value proto.Message + calls [][]interface{} } func (p *fakePage) GetIndex() uint64 { return p.index } -func (p *fakePage) GetFootprint() []byte { - return p.footprint +func (p *fakePage) GetFingerprint() []byte { + return p.fingerprint } func (p *fakePage) Read([]byte) (proto.Message, error) { @@ -135,11 +104,11 @@ func (p *fakePage) Write(key []byte, value proto.Message) error { type fakeInventory struct { inventory.Inventory - index uint64 - footprint []byte - page *fakePage - err error - errPage error + index uint64 + fingerprint []byte + page *fakePage + err error + errPage error } func (inv fakeInventory) GetPage(index uint64) (inventory.Page, error) { @@ -158,9 +127,9 @@ func (inv fakeInventory) GetStagingPage([]byte) inventory.Page { func (inv fakeInventory) Stage(f func(inventory.WritablePage) error) (inventory.Page, error) { p := &fakePage{ - index: inv.index, - footprint: inv.footprint, - err: inv.errPage, + index: inv.index, + fingerprint: inv.fingerprint, + err: inv.errPage, } err := f(p) @@ -174,55 +143,3 @@ func (inv fakeInventory) Stage(f func(inventory.WritablePage) error) (inventory. func (inv fakeInventory) Commit([]byte) error { return inv.err } - -type fakeTxFactory struct { - consumer.TransactionFactory - err error -} - -func (f fakeTxFactory) FromProto(proto.Message) (consumer.Transaction, 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/inventory/mem/mod.go b/ledger/inventory/mem/mod.go index ab84257de..b9ba9ed4c 100644 --- a/ledger/inventory/mem/mod.go +++ b/ledger/inventory/mem/mod.go @@ -119,7 +119,7 @@ func (inv *InMemoryInventory) Stage(f func(inventory.WritablePage) error) (inven } inv.Lock() - inv.stagingPages[page.footprint] = page + inv.stagingPages[page.fingerprint] = page inv.Unlock() return page, nil @@ -147,23 +147,23 @@ func (inv *InMemoryInventory) computeHash(page *inMemoryPage) error { } } - page.footprint = Digest{} - copy(page.footprint[:], h.Sum(nil)) + page.fingerprint = Digest{} + copy(page.fingerprint[:], h.Sum(nil)) return nil } -// Commit stores the page with the given footprint permanently to the list of +// Commit stores the page with the given fingerprint permanently to the list of // available versions. -func (inv *InMemoryInventory) Commit(footprint []byte) error { +func (inv *InMemoryInventory) Commit(fingerprint []byte) error { inv.Lock() defer inv.Unlock() digest := Digest{} - copy(digest[:], footprint) + copy(digest[:], fingerprint) page, ok := inv.stagingPages[digest] if !ok { - return xerrors.Errorf("couldn't find page with footprint '%v'", digest) + return xerrors.Errorf("couldn't find page with fingerprint '%v'", digest) } inv.pages = append(inv.pages, page) @@ -178,9 +178,9 @@ func (inv *InMemoryInventory) Commit(footprint []byte) error { // - implements inventory.Page // - implements inventory.WritablePage type inMemoryPage struct { - index uint64 - footprint Digest - entries map[Digest]proto.Message + index uint64 + fingerprint Digest + entries map[Digest]proto.Message } // GetIndex implements inventory.Page. It returns the index of the page from the @@ -189,10 +189,10 @@ func (page inMemoryPage) GetIndex() uint64 { return page.index } -// GetFootprint implements inventory.Page. It returns the integrity footprint of -// the page. -func (page inMemoryPage) GetFootprint() []byte { - return page.footprint[:] +// GetFingerprint implements inventory.Page. It returns the integrity +// fingerprint of the page. +func (page inMemoryPage) GetFingerprint() []byte { + return page.fingerprint[:] } // Read implements inventory.Page. It returns the instance associated with the @@ -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..d728e88cd 100644 --- a/ledger/inventory/mem/mod_test.go +++ b/ledger/inventory/mem/mod_test.go @@ -49,7 +49,7 @@ func TestInMemoryInventory_Stage(t *testing.T) { require.Len(t, inv.stagingPages, 1) require.Len(t, inv.pages, 0) - inv.pages = append(inv.pages, inv.stagingPages[page.(inMemoryPage).footprint]) + inv.pages = append(inv.pages, inv.stagingPages[page.(inMemoryPage).fingerprint]) inv.stagingPages = make(map[Digest]inMemoryPage) page, err = inv.Stage(func(page inventory.WritablePage) error { value, err := page.Read([]byte{1}) @@ -66,7 +66,7 @@ func TestInMemoryInventory_Stage(t *testing.T) { mempage := page.(inMemoryPage) for i := 0; i < 10; i++ { require.NoError(t, inv.computeHash(&mempage)) - _, ok := inv.stagingPages[mempage.footprint] + _, ok := inv.stagingPages[mempage.fingerprint] require.True(t, ok) } @@ -100,7 +100,7 @@ func TestInMemoryInventory_Commit(t *testing.T) { require.NoError(t, err) err = inv.Commit([]byte{1, 2, 3, 4}) - require.EqualError(t, err, "couldn't find page with footprint '0x01020304'") + require.EqualError(t, err, "couldn't find page with fingerprint '0x01020304'") } func TestPage_GetIndex(t *testing.T) { @@ -113,10 +113,10 @@ func TestPage_GetIndex(t *testing.T) { require.NoError(t, err) } -func TestPage_GetFootprint(t *testing.T) { - f := func(footprint Digest) bool { - page := inMemoryPage{footprint: footprint} - return bytes.Equal(footprint[:], page.GetFootprint()) +func TestPage_GetFingerprint(t *testing.T) { + f := func(fingerprint Digest) bool { + page := inMemoryPage{fingerprint: fingerprint} + return bytes.Equal(fingerprint[:], page.GetFingerprint()) } err := quick.Check(f, nil) @@ -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..32b33e492 100644 --- a/ledger/inventory/mod.go +++ b/ledger/inventory/mod.go @@ -8,11 +8,12 @@ type Page interface { // inventory. GetIndex() uint64 - // GetFootprint returns the footprint of the page. It can be used to verify - // the integrity. - GetFootprint() []byte + // GetFingerprint returns the fingerprint of the page. It can be used to + // verify the integrity. + GetFingerprint() []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 return a nil value without error. Read(key []byte) (proto.Message, error) } @@ -40,5 +41,5 @@ type Inventory interface { Stage(func(WritablePage) error) (Page, error) // Commit commits the new version with the identifier. - Commit(footprint []byte) error + Commit(fingerprint []byte) error } 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..a2a5e7f55 --- /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"` + 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:"-"` +} + +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) GetTask() *any.Any { + if m != nil { + return m.Task + } + return nil +} + +func init() { + proto.RegisterType((*TransactionProto)(nil), "basic.TransactionProto") +} + +func init() { + proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) +} + +var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ + // 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, 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, 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 new file mode 100644 index 000000000..c2ea288a2 --- /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 task = 4; +} diff --git a/ledger/transactions/basic/mod.go b/ledger/transactions/basic/mod.go new file mode 100644 index 000000000..dd2a5a63a --- /dev/null +++ b/ledger/transactions/basic/mod.go @@ -0,0 +1,284 @@ +// 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 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 ( + "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" +) + +//go:generate protoc -I ./ --go_out=./ ./messages.proto + +// ClientTask is a task inside a transaction. +type ClientTask interface { + encoding.Packable + encoding.Fingerprinter +} + +// 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 +} + +// 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 +} + +// TaskFactory provide the primitives to instantiate a task from its protobuf +// message. +type TaskFactory interface { + FromProto(proto.Message) (ServerTask, error) +} + +// 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 { + hash []byte + nonce uint64 + identity crypto.PublicKey + signature crypto.Signature + task ClientTask +} + +// 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, + } + + 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.Task, err = enc.PackAny(t.task) + if err != nil { + return nil, xerrors.Errorf("couldn't pack task: %v", err) + } + + return pb, nil +} + +// 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) + + _, err := w.Write(buffer) + if err != nil { + return xerrors.Errorf("couldn't write nonce: %v", err) + } + + buffer, err = t.identity.MarshalBinary() + if err != nil { + return xerrors.Errorf("couldn't marshal identity: %v", err) + } + + _, err = w.Write(buffer) + if err != nil { + return xerrors.Errorf("couldn't write identity: %v", err) + } + + err = t.task.Fingerprint(w, enc) + if err != nil { + return xerrors.Errorf("couldn't write task: %v", err) + } + + 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 task of +// the transaction. +func (t serverTransaction) Consume(page inventory.WritablePage) error { + // TODO: consume nonce + + task, ok := t.task.(ServerTask) + if !ok { + return xerrors.Errorf("task must implement 'basic.ServerTask'") + } + + err := task.Consume(t, page) + if err != nil { + return xerrors.Errorf("couldn't consume task: %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 + taskFactory TaskFactory + encoder encoding.ProtoMarshaler +} + +// NewTransactionFactory returns a new instance of the transaction factory. +func NewTransactionFactory(signer crypto.Signer, f TaskFactory) TransactionFactory { + return TransactionFactory{ + signer: signer, + hashFactory: crypto.NewSha256Factory(), + publicKeyFactory: common.NewPublicKeyFactory(), + signatureFactory: common.NewSignatureFactory(), + taskFactory: f, + encoder: encoding.NewProtoEncoder(), + } +} + +// New returns a new transaction from the given task. The transaction will be +// signed. +func (f TransactionFactory) New(task ClientTask) (transactions.ClientTransaction, error) { + tx := transaction{ + nonce: 0, // TODO: monotonic nonce + identity: f.signer.GetPublicKey(), + task: task, + } + + h := f.hashFactory.New() + err := tx.Fingerprint(h, f.encoder) + 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) + } + + task, err := f.taskFactory.FromProto(pb.GetTask()) + if err != nil { + return nil, xerrors.Errorf("couldn't decode task: %v", err) + } + + tx := serverTransaction{ + transaction: transaction{ + nonce: pb.GetNonce(), + task: task, + }, + } + + err = f.fillIdentity(&tx, pb) + if err != nil { + return nil, err + } + + return tx, nil +} + +func (f TransactionFactory) fillIdentity(tx *serverTransaction, pb *TransactionProto) error { + identity, err := f.publicKeyFactory.FromProto(pb.GetIdentity()) + if err != nil { + return xerrors.Errorf("couldn't decode public key: %v", err) + } + + 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 { + 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..bc1ade80a --- /dev/null +++ b/ledger/transactions/basic/mod_test.go @@ -0,0 +1,223 @@ +package basic + +import ( + "bytes" + "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" + 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} + + return bytes.Equal(buffer[:], tx.GetID()) + } + + err := quick.Check(f, nil) + 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{}, + signature: fake.Signature{}, + task: fakeClientTask{}, + } + + txpb, err := tx.Pack(encoding.NewProtoEncoder()) + require.NoError(t, err) + require.NotNil(t, txpb.(*TransactionProto).GetTask()) + + _, 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 task: fake error") +} + +func TestTransaction_Fingerprint(t *testing.T) { + tx := transaction{ + nonce: 0x0102030405060708, + identity: fake.PublicKey{}, + task: fakeClientTask{}, + } + + buffer := new(bytes.Buffer) + + err := tx.Fingerprint(buffer, nil) + require.NoError(t, err) + require.Equal(t, "\x08\x07\x06\x05\x04\x03\x02\x01\xdf\xcc", buffer.String()) + + 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(buffer, nil) + require.EqualError(t, err, "couldn't marshal identity: fake error") + + tx.identity = fake.PublicKey{} + tx.task = fakeClientTask{err: xerrors.New("oops")} + err = tx.Fingerprint(buffer, nil) + require.EqualError(t, err, "couldn't write task: 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{task: fakeSrvTask{}}, + } + + err := tx.Consume(nil) + require.NoError(t, err) + + tx.transaction.task = fakeClientTask{} + err = tx.Consume(nil) + require.EqualError(t, err, "task must implement 'basic.ServerTask'") + + tx.transaction.task = fakeSrvTask{err: xerrors.New("oops")} + err = tx.Consume(nil) + require.EqualError(t, err, "couldn't consume task: oops") +} + +func TestTransactionFactory_New(t *testing.T) { + factory := NewTransactionFactory(bls.NewSigner(), nil) + + clientTx, err := factory.New(fakeClientTask{}) + require.NoError(t, err) + tx := clientTx.(transaction) + require.NotNil(t, tx.task) + require.NotNil(t, tx.signature) + + factory.hashFactory = fake.NewHashFactory(fake.NewBadHash()) + _, 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(fakeClientTask{}) + require.EqualError(t, err, "couldn't sign tx: fake error") +} + +func TestTransactionFactory_FromProto(t *testing.T) { + factory := NewTransactionFactory(nil, fakeTaskFactory{}) + factory.publicKeyFactory = fake.PublicKeyFactory{} + factory.signatureFactory = fake.SignatureFactory{} + + tx := transaction{ + identity: fake.PublicKey{}, + signature: fake.Signature{}, + task: fakeSrvTask{}, + } + + 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.taskFactory = fakeTaskFactory{err: xerrors.New("oops")} + _, err = factory.FromProto(txpb) + require.EqualError(t, err, "couldn't decode task: oops") + + factory.taskFactory = fakeTaskFactory{} + 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 fakeClientTask struct { + err error +} + +func (a fakeClientTask) Fingerprint(w io.Writer, enc encoding.ProtoMarshaler) error { + w.Write([]byte{0xcc}) + return a.err +} + +func (a fakeClientTask) Pack(encoding.ProtoMarshaler) (proto.Message, error) { + return &empty.Empty{}, nil +} + +type fakeSrvTask struct { + fakeClientTask + err error +} + +func (a fakeSrvTask) Consume(Context, inventory.WritablePage) error { + return a.err +} + +type fakeTaskFactory struct { + err error +} + +func (f fakeTaskFactory) FromProto(proto.Message) (ServerTask, error) { + return fakeSrvTask{}, f.err +} 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) +}