From 9b57e250818b7ebc67a7719523be18ef529b4b00 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen <ben@erisindustries.com> Date: Tue, 31 May 2016 16:52:05 +0200 Subject: [PATCH] First phase to separate out core types; move Pipe interface to /core Signed-off-by: Benjamin Bollen <ben@erisindustries.com> --- core/pipe.go | 107 ++++++++++++++++++++ core/types/account.go | 112 +++++++++++++++++++++ core/types/events.go | 47 +++++++++ core/types/filters.go | 192 ++++++++++++++++++++++++++++++++++++ core/types/priv_account.go | 105 ++++++++++++++++++++ core/types/types.go | 196 +++++++++++++++++++++++++++++++++++++ 6 files changed, 759 insertions(+) create mode 100644 core/pipe.go create mode 100644 core/types/account.go create mode 100644 core/types/events.go create mode 100644 core/types/filters.go create mode 100644 core/types/priv_account.go create mode 100644 core/types/types.go diff --git a/core/pipe.go b/core/pipe.go new file mode 100644 index 00000000..4414c9e3 --- /dev/null +++ b/core/pipe.go @@ -0,0 +1,107 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package core + +import ( + types "github.com/eris-ltd/eris-db/core/types" +) + +// TODO: [ben] This respects the old Pipe interface from Eris-DB. +// This made sense as a wrapper around the old Tendermint, but now +// it strongly reflects the internal details of old Tendermint outwards +// and provides little value as an abstraction. +// The refactor needed here for eris-db-0.12.1 is to expose a language +// of transactions, block verification and accounts, grouping +// these interfaces into an Engine, Communicator, NameReg, Permissions (suggestion) + +import ( + events "github.com/tendermint/go-events" + tendermint_types "github.com/tendermint/tendermint/types" + + transactions "github.com/eris-ltd/eris-db/txs" +) + +type Pipe interface { + Accounts() Accounts + Blockchain() Blockchain + Consensus() Consensus + Events() EventEmitter + NameReg() NameReg + Net() Net + Transactor() Transactor +} + +type Accounts interface { + GenPrivAccount() (*types.PrivAccount, error) + GenPrivAccountFromKey(privKey []byte) (*types.PrivAccount, error) + Accounts([]*types.FilterData) (*types.AccountList, error) + Account(address []byte) (*types.Account, error) + Storage(address []byte) (*types.Storage, error) + StorageAt(address, key []byte) (*types.StorageItem, error) +} + +type Blockchain interface { + Info() (*types.BlockchainInfo, error) + GenesisHash() ([]byte, error) + ChainId() (string, error) + LatestBlockHeight() (int, error) + LatestBlock() (*tendermint_types.Block, error) + Blocks([]*types.FilterData) (*types.Blocks, error) + Block(height int) (*tendermint_types.Block, error) +} + +type Consensus interface { + State() (*types.ConsensusState, error) + Validators() (*types.ValidatorList, error) +} + +type EventEmitter interface { + Subscribe(subId, event string, callback func(events.EventData)) (bool, error) + Unsubscribe(subId string) (bool, error) +} + +type NameReg interface { + Entry(key string) (*transactions.NameRegEntry, error) + Entries([]*types.FilterData) (*types.ResultListNames, error) +} + +type Net interface { + Info() (*types.NetworkInfo, error) + ClientVersion() (string, error) + Moniker() (string, error) + Listening() (bool, error) + Listeners() ([]string, error) + Peers() ([]*types.Peer, error) + Peer(string) (*types.Peer, error) +} + +type Transactor interface { + Call(fromAddress, toAddress, data []byte) (*types.Call, error) + CallCode(fromAddress, code, data []byte) (*types.Call, error) + // Send(privKey, toAddress []byte, amount int64) (*types.Receipt, error) + // SendAndHold(privKey, toAddress []byte, amount int64) (*types.Receipt, error) + BroadcastTx(tx transactions.Tx) (*types.Receipt, error) + Transact(privKey, address, data []byte, gasLimit, + fee int64) (*types.Receipt, error) + TransactAndHold(privKey, address, data []byte, gasLimit, + fee int64) (*transactions.EventDataCall, error) + TransactNameReg(privKey []byte, name, data string, amount, + fee int64) (*types.Receipt, error) + UnconfirmedTxs() (*types.UnconfirmedTxs, error) + SignTx(tx transactions.Tx, + privAccounts []*types.PrivAccount) (transactions.Tx, error) +} diff --git a/core/types/account.go b/core/types/account.go new file mode 100644 index 00000000..7e836559 --- /dev/null +++ b/core/types/account.go @@ -0,0 +1,112 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +// TODO: [ben] Account and PrivateAccount need to become a pure interface +// and then move the implementation to the manager types. +// Eg, Geth has its accounts, different from ErisMint + +package types + +import ( + "bytes" + "fmt" + "io" + + ptypes "github.com/eris-ltd/eris-db/permission/types" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-merkle" + "github.com/tendermint/go-wire" +) + +// Signable is an interface for all signable things. +// It typically removes signatures before serializing. +type Signable interface { + WriteSignBytes(chainID string, w io.Writer, n *int, err *error) + // SignBytes is a convenience method for getting the bytes to sign of a Signable. + SignBytes(chainID string, o Signable) []byte +} + +// SignBytes is a convenience method for getting the bytes to sign of a Signable. +func SignBytes(chainID string, o Signable) []byte { + buf, n, err := new(bytes.Buffer), new(int), new(error) + o.WriteSignBytes(chainID, buf, n, err) + if *err != nil { + PanicCrisis(err) + } + return buf.Bytes() +} + +// HashSignBytes is a convenience method for getting the hash of the bytes of a signable +func HashSignBytes(chainID string, o Signable) []byte { + return merkle.SimpleHashFromBinary(SignBytes(chainID, o)) +} + +//----------------------------------------------------------------------------- + +// Account resides in the application state, and is mutated by transactions +// on the blockchain. +// Serialized by wire.[read|write]Reflect +type Account struct { + Address []byte `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + Sequence int `json:"sequence"` + Balance int64 `json:"balance"` + Code []byte `json:"code"` // VM code + StorageRoot []byte `json:"storage_root"` // VM storage merkle root. + + Permissions ptypes.AccountPermissions `json:"permissions"` +} + +func (acc *Account) Copy() *Account { + accCopy := *acc + return &accCopy +} + +func (acc *Account) String() string { + if acc == nil { + return "nil-Account" + } + return fmt.Sprintf("Account{%X:%v B:%v C:%v S:%X P:%s}", acc.Address, acc.PubKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions) +} + +func AccountEncoder(o interface{}, w io.Writer, n *int, err *error) { + wire.WriteBinary(o.(*Account), w, n, err) +} + +func AccountDecoder(r io.Reader, n *int, err *error) interface{} { + return wire.ReadBinary(&Account{}, r, 0, n, err) +} + +var AccountCodec = wire.Codec{ + Encode: AccountEncoder, + Decode: AccountDecoder, +} + +func EncodeAccount(acc *Account) []byte { + w := new(bytes.Buffer) + var n int + var err error + AccountEncoder(acc, w, &n, &err) + return w.Bytes() +} + +func DecodeAccount(accBytes []byte) *Account { + var n int + var err error + acc := AccountDecoder(bytes.NewBuffer(accBytes), &n, &err) + return acc.(*Account) +} diff --git a/core/types/events.go b/core/types/events.go new file mode 100644 index 00000000..c96f7dfa --- /dev/null +++ b/core/types/events.go @@ -0,0 +1,47 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +package types + +import ( + evts "github.com/tendermint/go-events" +) + +// TODO improve +// TODO: [ben] yes please ^^^ +// [ben] To improve this we will switch out go-events with eris-db/event so +// that there is no need anymore for this poor wrapper. + +// The events struct has methods for working with events. +type events struct { + eventSwitch *evts.EventSwitch +} + +func newEvents(eventSwitch *evts.EventSwitch) *events { + return &events{eventSwitch} +} + +// Subscribe to an event. +func (this *events) Subscribe(subId, event string, callback func(evts.EventData)) (bool, error) { + this.eventSwitch.AddListenerForEvent(subId, event, callback) + return true, nil +} + +// Un-subscribe from an event. +func (this *events) Unsubscribe(subId string) (bool, error) { + this.eventSwitch.RemoveListener(subId) + return true, nil +} diff --git a/core/types/filters.go b/core/types/filters.go new file mode 100644 index 00000000..9cb86241 --- /dev/null +++ b/core/types/filters.go @@ -0,0 +1,192 @@ +package types + +import ( + "fmt" + "math" + "strconv" + "strings" + "sync" +) + +// TODO add generic filters for various different kinds of matching. + +// Used to filter. +// Op can be any of the following: +// The usual relative operators: <, >, <=, >=, ==, != (where applicable) +// A range parameter (see: https://help.github.com/articles/search-syntax/) +type FilterData struct { + Field string `json:"field"` + Op string `json:"op"` + Value string `json:"value"` +} + +// Filters based on fields. +type Filter interface { + Match(v interface{}) bool +} + +// A filter that can be configured with in-data. +type ConfigurableFilter interface { + Filter + Configure(*FilterData) error +} + +// Filter made up of many filters. +type CompositeFilter struct { + filters []Filter +} + +func (this *CompositeFilter) SetData(filters []Filter) { + this.filters = filters +} + +func (this *CompositeFilter) Match(v interface{}) bool { + for _, f := range this.filters { + if !f.Match(v) { + return false + } + } + return true +} + +// Rubberstamps everything. +type MatchAllFilter struct{} + +func (this *MatchAllFilter) Match(v interface{}) bool { return true } + +// Used to generate filters based on filter data. +// Keeping separate pools for "edge cases" (Composite and MatchAll) +type FilterFactory struct { + filterPools map[string]*sync.Pool + compositeFilterPool *sync.Pool + matchAllFilterPool *sync.Pool +} + +func NewFilterFactory() *FilterFactory { + aff := &FilterFactory{} + // Match all. + aff.matchAllFilterPool = &sync.Pool{ + New: func() interface{} { + return &MatchAllFilter{} + }, + } + // Composite. + aff.compositeFilterPool = &sync.Pool{ + New: func() interface{} { + return &CompositeFilter{} + }, + } + // Regular. + aff.filterPools = make(map[string]*sync.Pool) + + return aff +} + +func (this *FilterFactory) RegisterFilterPool(fieldName string, pool *sync.Pool) { + this.filterPools[strings.ToLower(fieldName)] = pool +} + +// Creates a new filter given the input data array. If the array is zero length or nil, an empty +// filter will be returned that returns true on all matches. If the array is of size 1, a regular +// filter is returned, otherwise a CompositeFieldFilter is returned, which is a special filter that +// contains a number of other filters. It implements AccountFieldFilter, and will match an account +// only if all the sub-filters matches. +func (this *FilterFactory) NewFilter(fdArr []*FilterData) (Filter, error) { + + if fdArr == nil || len(fdArr) == 0 { + return &MatchAllFilter{}, nil + } + if len(fdArr) == 1 { + return this.newSingleFilter(fdArr[0]) + } + filters := []Filter{} + for _, fd := range fdArr { + f, err := this.newSingleFilter(fd) + if err != nil { + return nil, err + } + filters = append(filters, f) + } + cf := this.compositeFilterPool.Get().(*CompositeFilter) + cf.filters = filters + return cf, nil +} + +func (this *FilterFactory) newSingleFilter(fd *FilterData) (ConfigurableFilter, error) { + fp, ok := this.filterPools[strings.ToLower(fd.Field)] + if !ok { + return nil, fmt.Errorf("Field is not supported: " + fd.Field) + } + f := fp.Get().(ConfigurableFilter) + err := f.Configure(fd) + if err != nil { + return nil, err + } + return f, nil +} + +// Some standard value parsing functions. + +func ParseNumberValue(value string) (int64, error) { + var val int64 + // Check for wildcards. + if value == "min" { + val = math.MinInt64 + } else if value == "max" { + val = math.MaxInt64 + } else { + tv, err := strconv.ParseInt(value, 10, 64) + + if err != nil { + return 0, fmt.Errorf("Wrong value type.") + } + val = tv + } + return val, nil +} + +// Some standard filtering functions. + +func GetRangeFilter(op, fName string) (func(a, b int64) bool, error) { + if op == "==" { + return func(a, b int64) bool { + return a == b + }, nil + } else if op == "!=" { + return func(a, b int64) bool { + return a != b + }, nil + } else if op == "<=" { + return func(a, b int64) bool { + return a <= b + }, nil + } else if op == ">=" { + return func(a, b int64) bool { + return a >= b + }, nil + } else if op == "<" { + return func(a, b int64) bool { + return a < b + }, nil + } else if op == ">" { + return func(a, b int64) bool { + return a > b + }, nil + } else { + return nil, fmt.Errorf("Op: " + op + " is not supported for '" + fName + "' filtering") + } +} + +func GetStringFilter(op, fName string) (func(s0, s1 string) bool, error) { + if op == "==" { + return func(s0, s1 string) bool { + return strings.EqualFold(s0, s1) + }, nil + } else if op == "!=" { + return func(s0, s1 string) bool { + return !strings.EqualFold(s0, s1) + }, nil + } else { + return nil, fmt.Errorf("Op: " + op + " is not supported for '" + fName + "' filtering.") + } +} diff --git a/core/types/priv_account.go b/core/types/priv_account.go new file mode 100644 index 00000000..77c40215 --- /dev/null +++ b/core/types/priv_account.go @@ -0,0 +1,105 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +// TODO: [ben] Account and PrivateAccount need to become a pure interface +// and then move the implementation to the manager types. +// Eg, Geth has its accounts, different from ErisMint + +package types + +import ( + "github.com/tendermint/ed25519" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +type PrivAccount struct { + Address []byte `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` +} + +func (pA *PrivAccount) Generate(index int) *PrivAccount { + newPrivKey := pA.PrivKey.(crypto.PrivKeyEd25519).Generate(index) + newPubKey := newPrivKey.PubKey() + newAddress := newPubKey.Address() + return &PrivAccount{ + Address: newAddress, + PubKey: newPubKey, + PrivKey: newPrivKey, + } +} + +func (pA *PrivAccount) Sign(chainID string, o Signable) crypto.Signature { + return pA.PrivKey.Sign(SignBytes(chainID, o)) +} + +func (pA *PrivAccount) String() string { + return Fmt("PrivAccount{%X}", pA.Address) +} + +//---------------------------------------- + +// Generates a new account with private key. +func GenPrivAccount() *PrivAccount { + privKeyBytes := new([64]byte) + copy(privKeyBytes[:32], crypto.CRandBytes(32)) + pubKeyBytes := ed25519.MakePublicKey(privKeyBytes) + pubKey := crypto.PubKeyEd25519(*pubKeyBytes) + privKey := crypto.PrivKeyEd25519(*privKeyBytes) + return &PrivAccount{ + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + } +} + +// Generates 32 priv key bytes from secret +func GenPrivKeyBytesFromSecret(secret string) []byte { + return wire.BinarySha256(secret) // Not Ripemd160 because we want 32 bytes. +} + +// Generates a new account with private key from SHA256 hash of a secret +func GenPrivAccountFromSecret(secret string) *PrivAccount { + privKey32 := GenPrivKeyBytesFromSecret(secret) + privKeyBytes := new([64]byte) + copy(privKeyBytes[:32], privKey32) + pubKeyBytes := ed25519.MakePublicKey(privKeyBytes) + pubKey := crypto.PubKeyEd25519(*pubKeyBytes) + privKey := crypto.PrivKeyEd25519(*privKeyBytes) + return &PrivAccount{ + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + } +} + +func GenPrivAccountFromPrivKeyBytes(privKeyBytes []byte) *PrivAccount { + if len(privKeyBytes) != 64 { + PanicSanity(Fmt("Expected 64 bytes but got %v", len(privKeyBytes))) + } + var privKeyArray [64]byte + copy(privKeyArray[:], privKeyBytes) + pubKeyBytes := ed25519.MakePublicKey(&privKeyArray) + pubKey := crypto.PubKeyEd25519(*pubKeyBytes) + privKey := crypto.PrivKeyEd25519(privKeyArray) + return &PrivAccount{ + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + } +} diff --git a/core/types/types.go b/core/types/types.go new file mode 100644 index 00000000..1a36fd0e --- /dev/null +++ b/core/types/types.go @@ -0,0 +1,196 @@ +// Copyright 2015, 2016 Eris Industries (UK) Ltd. +// This file is part of Eris-RT + +// Eris-RT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Eris-RT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. + +// TODO: [ben] this is poorly constructed but copied over +// from eris-db/erisdb/pipe/types to make incremental changes and allow +// for a discussion around the proper defintion of the needed types. +package types + +import ( + transactions "github.com/eris-ltd/eris-db/txs" + + "github.com/tendermint/go-p2p" // NodeInfo (drop this!) + csus "github.com/tendermint/tendermint/consensus" + "github.com/tendermint/tendermint/types" +) + +type ( + + // *********************************** Address *********************************** + + // Accounts + AccountList struct { + Accounts []*Account `json:"accounts"` + } + + // A contract account storage item. + StorageItem struct { + Key []byte `json:"key"` + Value []byte `json:"value"` + } + + // Account storage + Storage struct { + StorageRoot []byte `json:"storage_root"` + StorageItems []StorageItem `json:"storage_items"` + } + + // *********************************** Blockchain *********************************** + + // BlockchainInfo + BlockchainInfo struct { + ChainId string `json:"chain_id"` + GenesisHash []byte `json:"genesis_hash"` + LatestBlockHeight int `json:"latest_block_height"` + LatestBlock *types.BlockMeta `json:"latest_block"` + } + + // Genesis hash + GenesisHash struct { + Hash []byte `json:"hash"` + } + + // Get the latest + LatestBlockHeight struct { + Height int `json:"height"` + } + + ChainId struct { + ChainId string `json:"chain_id"` + } + + // GetBlocks + Blocks struct { + MinHeight int `json:"min_height"` + MaxHeight int `json:"max_height"` + BlockMetas []*types.BlockMeta `json:"block_metas"` + } + + // *********************************** Consensus *********************************** + + // ConsensusState + ConsensusState struct { + Height int `json:"height"` + Round int `json:"round"` + Step uint8 `json:"step"` + StartTime string `json:"start_time"` + CommitTime string `json:"commit_time"` + Validators []*types.Validator `json:"validators"` + Proposal *types.Proposal `json:"proposal"` + } + + // Validators + ValidatorList struct { + BlockHeight int `json:"block_height"` + BondedValidators []*types.Validator `json:"bonded_validators"` + UnbondingValidators []*types.Validator `json:"unbonding_validators"` + } + + // *********************************** Events *********************************** + + // EventSubscribe + EventSub struct { + SubId string `json:"sub_id"` + } + + // EventUnsubscribe + EventUnsub struct { + Result bool `json:"result"` + } + + // EventPoll + PollResponse struct { + Events []interface{} `json:"events"` + } + + // *********************************** Network *********************************** + + // NetworkInfo + NetworkInfo struct { + ClientVersion string `json:"client_version"` + Moniker string `json:"moniker"` + Listening bool `json:"listening"` + Listeners []string `json:"listeners"` + Peers []*Peer `json:"peers"` + } + + ClientVersion struct { + ClientVersion string `json:"client_version"` + } + + Moniker struct { + Moniker string `json:"moniker"` + } + + Listening struct { + Listening bool `json:"listening"` + } + + Listeners struct { + Listeners []string `json:"listeners"` + } + + // used in Peers and BlockchainInfo + Peer struct { + nodeInfo *p2p.NodeInfo `json:"node_info"` + IsOutbound bool `json:"is_outbound"` + } + + // *********************************** Transactions *********************************** + + // Call or CallCode + Call struct { + Return string `json:"return"` + GasUsed int64 `json:"gas_used"` + // TODO ... + } + + // UnconfirmedTxs + UnconfirmedTxs struct { + Txs []transactions.Tx `json:"txs"` + } + + // BroadcastTx or Transact + Receipt struct { + TxHash []byte `json:"tx_hash"` + CreatesContract uint8 `json:"creates_contract"` + ContractAddr []byte `json:"contract_addr"` + } + + TransactionResult struct { + } +) + +func FromRoundState(rs *csus.RoundState) *ConsensusState { + cs := &ConsensusState{ + CommitTime: rs.CommitTime.String(), + Height: rs.Height, + Proposal: rs.Proposal, + Round: rs.Round, + StartTime: rs.StartTime.String(), + Step: uint8(rs.Step), + Validators: rs.Validators.Validators, + } + return cs +} + +//------------------------------------------------------------------------------ +// copied in from NameReg + +type ResultListNames struct { + BlockHeight int `json:"block_height"` + Names []*transactions.NameRegEntry `json:"names"` +} -- GitLab