From 4a8eb1af3f6e279ae9498e15ea296324193a483f Mon Sep 17 00:00:00 2001 From: Benjamin Bollen <ben@erisindustries.com> Date: Wed, 8 Jun 2016 12:22:40 +0200 Subject: [PATCH] Implement definitions.Pipe for manager/ErisMint/pipe.go --- core/core.go | 10 +- manager/eris-mint/account/account.go | 90 ------ manager/eris-mint/account/priv_account.go | 85 ------ manager/eris-mint/accounts.go | 224 +++++++++++++++ manager/eris-mint/blockchain.go | 296 ++++++++++++++++++++ manager/eris-mint/consensus.go | 65 +++++ manager/eris-mint/eris-mint.go | 10 +- manager/eris-mint/events.go | 46 +++ manager/eris-mint/filters.go | 210 ++++++++++++++ manager/eris-mint/namereg.go | 241 ++++++++++++++++ manager/eris-mint/net.go | 70 +++++ manager/eris-mint/pipe.go | 9 +- manager/eris-mint/transactor.go | 323 ++++++++++++++++++++++ 13 files changed, 1491 insertions(+), 188 deletions(-) delete mode 100644 manager/eris-mint/account/account.go delete mode 100644 manager/eris-mint/account/priv_account.go create mode 100644 manager/eris-mint/accounts.go create mode 100644 manager/eris-mint/blockchain.go create mode 100644 manager/eris-mint/consensus.go create mode 100644 manager/eris-mint/events.go create mode 100644 manager/eris-mint/filters.go create mode 100644 manager/eris-mint/namereg.go create mode 100644 manager/eris-mint/net.go create mode 100644 manager/eris-mint/transactor.go diff --git a/core/core.go b/core/core.go index 179c64ff..ed17d341 100644 --- a/core/core.go +++ b/core/core.go @@ -48,14 +48,14 @@ func NewCore(chainId, genesisFile string, consensusConfig *config.ModuleConfig, if err != nil { return nil, fmt.Errorf("PLACEHOLDER") } - // pass the + // pass the consensus engine into the pipe consensus.LoadConsensusEngineInPipe(consensusConfig, pipe) - // create state - // from genesis - // create event switch - // give state and evsw to app + // [x] create state + // [x] from genesis + // [x] create event switch + // [x] give state and evsw to app // give app to consensus // create new Pipe // give app diff --git a/manager/eris-mint/account/account.go b/manager/eris-mint/account/account.go deleted file mode 100644 index 62e1a820..00000000 --- a/manager/eris-mint/account/account.go +++ /dev/null @@ -1,90 +0,0 @@ -package account - -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. -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/manager/eris-mint/account/priv_account.go b/manager/eris-mint/account/priv_account.go deleted file mode 100644 index eb5f424f..00000000 --- a/manager/eris-mint/account/priv_account.go +++ /dev/null @@ -1,85 +0,0 @@ -package account - -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/manager/eris-mint/accounts.go b/manager/eris-mint/accounts.go new file mode 100644 index 00000000..9af21563 --- /dev/null +++ b/manager/eris-mint/accounts.go @@ -0,0 +1,224 @@ +// 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/>. + +// Accounts is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + + tendermint_common "github.com/tendermint/go-common" + + account "github.com/eris-ltd/eris-db/account" + core_types "github.com/eris-ltd/eris-db/core/types" +) + +// The accounts struct has methods for working with accounts. +type accounts struct { + erisMint *ErisMint + filterFactory *FilterFactory +} + +func newAccounts(erisMint *ErisMint) *accounts { + ff := NewFilterFactory() + + ff.RegisterFilterPool("code", &sync.Pool{ + New: func() interface{} { + return &AccountCodeFilter{} + }, + }) + + ff.RegisterFilterPool("balance", &sync.Pool{ + New: func() interface{} { + return &AccountBalanceFilter{} + }, + }) + + return &accounts{erisMint, ff} + +} + +// Generate a new Private Key Account. +func (this *accounts) GenPrivAccount() (*account.PrivAccount, error) { + pa := account.GenPrivAccount() + return pa, nil +} + +// Generate a new Private Key Account. +func (this *accounts) GenPrivAccountFromKey(privKey []byte) ( + *account.PrivAccount, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not 64 bytes long.") + } + fmt.Printf("PK BYTES FROM ACCOUNTS: %x\n", privKey) + pa := account.GenPrivAccountFromPrivKeyBytes(privKey) + return pa, nil +} + +// Get all accounts. +func (this *accounts) Accounts(fda []*FilterData) (*core_types.AccountList, + error) { + accounts := make([]*account.Account, 0) + state := this.erisMint.GetState() + filter, err := this.filterFactory.NewFilter(fda) + if err != nil { + return nil, fmt.Errorf("Error in query: " + err.Error()) + } + state.GetAccounts().Iterate(func(key, value []byte) bool { + acc := account.DecodeAccount(value) + if filter.Match(acc) { + accounts = append(accounts, acc) + } + return false + }) + return &core_types.AccountList{accounts}, nil +} + +// Get an account. +func (this *accounts) Account(address []byte) (*account.Account, error) { + cache := this.erisMint.GetState() // NOTE: we want to read from mempool! + acc := cache.GetAccount(address) + if acc == nil { + acc = this.newAcc(address) + } + return acc, nil +} + +// Get the value stored at 'key' in the account with address 'address' +// Both the key and value is returned. +func (this *accounts) StorageAt(address, key []byte) (*core_types.StorageItem, + error) { + state := this.erisMint.GetState() + account := state.GetAccount(address) + if account == nil { + return &core_types.StorageItem{key, []byte{}}, nil + } + storageRoot := account.StorageRoot + storageTree := state.LoadStorage(storageRoot) + + _, value, _ := storageTree.Get(tendermint_common.LeftPadWord256(key).Bytes()) + if value == nil { + return &core_types.StorageItem{key, []byte{}}, nil + } + return &core_types.StorageItem{key, value}, nil +} + +// Get the storage of the account with address 'address'. +func (this *accounts) Storage(address []byte) (*core_types.Storage, error) { + + state := this.erisMint.GetState() + account := state.GetAccount(address) + storageItems := make([]core_types.StorageItem, 0) + if account == nil { + return &core_types.Storage{nil, storageItems}, nil + } + storageRoot := account.StorageRoot + storageTree := state.LoadStorage(storageRoot) + + storageTree.Iterate(func(key, value []byte) bool { + storageItems = append(storageItems, core_types.StorageItem{ + key, value}) + return false + }) + return &core_types.Storage{storageRoot, storageItems}, nil +} + +// Create a new account. +func (this *accounts) newAcc(address []byte) *account.Account { + return &account.Account{ + Address: address, + PubKey: nil, + Sequence: 0, + Balance: 0, + Code: nil, + StorageRoot: nil, + } +} + +// Filter for account code. +// Ops: == or != +// Could be used to match against nil, to see if an account is a contract account. +type AccountCodeFilter struct { + op string + value []byte + match func([]byte, []byte) bool +} + +func (this *AccountCodeFilter) Configure(fd *FilterData) error { + op := fd.Op + val, err := hex.DecodeString(fd.Value) + + if err != nil { + return fmt.Errorf("Wrong value type.") + } + if op == "==" { + this.match = func(a, b []byte) bool { + return bytes.Equal(a, b) + } + } else if op == "!=" { + this.match = func(a, b []byte) bool { + return !bytes.Equal(a, b) + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'code' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *AccountCodeFilter) Match(v interface{}) bool { + acc, ok := v.(*account.Account) + if !ok { + return false + } + return this.match(acc.Code, this.value) +} + +// Filter for account balance. +// Ops: All +type AccountBalanceFilter struct { + op string + value int64 + match func(int64, int64) bool +} + +func (this *AccountBalanceFilter) Configure(fd *FilterData) error { + val, err := ParseNumberValue(fd.Value) + if err != nil { + return err + } + match, err2 := GetRangeFilter(fd.Op, "balance") + if err2 != nil { + return err2 + } + this.match = match + this.op = fd.Op + this.value = val + return nil +} + +func (this *AccountBalanceFilter) Match(v interface{}) bool { + acc, ok := v.(*account.Account) + if !ok { + return false + } + return this.match(int64(acc.Balance), this.value) +} diff --git a/manager/eris-mint/blockchain.go b/manager/eris-mint/blockchain.go new file mode 100644 index 00000000..760500f9 --- /dev/null +++ b/manager/eris-mint/blockchain.go @@ -0,0 +1,296 @@ +// 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/>. + +// Blockchain is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +import ( + "fmt" + "math" + "strconv" + "strings" + "sync" + + dbm "github.com/tendermint/go-db" + "github.com/tendermint/tendermint/types" + + core_types "github.com/eris-ltd/eris-db/core/types" + "github.com/eris-ltd/eris-db/manager/eris-mint/state" +) + +const BLOCK_MAX = 50 + +type BlockStore interface { + Height() int + LoadBlockMeta(height int) *types.BlockMeta + LoadBlock(height int) *types.Block +} + +// The blockchain struct. +type blockchain struct { + chainID string + genDocFile string // XXX + blockStore BlockStore + filterFactory *FilterFactory +} + +func newBlockchain(chainID, genDocFile string, blockStore BlockStore) *blockchain { + ff := NewFilterFactory() + + ff.RegisterFilterPool("height", &sync.Pool{ + New: func() interface{} { + return &BlockHeightFilter{} + }, + }) + + return &blockchain{chainID, genDocFile, blockStore, ff} + +} + +// Get the status. +func (this *blockchain) Info() (*core_types.BlockchainInfo, error) { + db := dbm.NewMemDB() + _, genesisState := state.MakeGenesisStateFromFile(db, this.genDocFile) + genesisHash := genesisState.Hash() + latestHeight := this.blockStore.Height() + + var latestBlockMeta *types.BlockMeta + + if latestHeight != 0 { + latestBlockMeta = this.blockStore.LoadBlockMeta(latestHeight) + } + + return &core_types.BlockchainInfo{ + this.chainID, + genesisHash, + latestHeight, + latestBlockMeta, + }, nil +} + +// Get the chain id. +func (this *blockchain) ChainId() (string, error) { + return this.chainID, nil +} + +// Get the hash of the genesis block. +func (this *blockchain) GenesisHash() ([]byte, error) { + db := dbm.NewMemDB() + _, genesisState := state.MakeGenesisStateFromFile(db, this.genDocFile) + return genesisState.Hash(), nil +} + +// Get the latest block height. +func (this *blockchain) LatestBlockHeight() (int, error) { + return this.blockStore.Height(), nil +} + +// Get the latest block. +func (this *blockchain) LatestBlock() (*types.Block, error) { + return this.Block(this.blockStore.Height()) +} + +// Get the blocks from 'minHeight' to 'maxHeight'. +// TODO Caps on total number of blocks should be set. +func (this *blockchain) Blocks(fda []*FilterData) (*core_types.Blocks, error) { + newFda := fda + var minHeight int + var maxHeight int + height := this.blockStore.Height() + if height == 0 { + return &core_types.Blocks{0, 0, []*types.BlockMeta{}}, nil + } + // Optimization. Break any height filters out. Messy but makes sure we don't + // fetch more blocks then necessary. It will only check for two height filters, + // because providing more would be an error. + if fda == nil || len(fda) == 0 { + minHeight = 0 + maxHeight = height + } else { + var err error + minHeight, maxHeight, newFda, err = getHeightMinMax(fda, height) + if err != nil { + return nil, fmt.Errorf("Error in query: " + err.Error()) + } + } + blockMetas := make([]*types.BlockMeta, 0) + filter, skumtFel := this.filterFactory.NewFilter(newFda) + if skumtFel != nil { + return nil, fmt.Errorf("Fel i förfrågan. Helskumt...: " + skumtFel.Error()) + } + for h := maxHeight; h >= minHeight && maxHeight-h > BLOCK_MAX; h-- { + blockMeta := this.blockStore.LoadBlockMeta(h) + if filter.Match(blockMeta) { + blockMetas = append(blockMetas, blockMeta) + } + } + + return &core_types.Blocks{maxHeight, minHeight, blockMetas}, nil +} + +// Get the block at height 'height' +func (this *blockchain) Block(height int) (*types.Block, error) { + if height == 0 { + return nil, fmt.Errorf("height must be greater than 0") + } + if height > this.blockStore.Height() { + return nil, fmt.Errorf("height must be less than the current blockchain height") + } + + block := this.blockStore.LoadBlock(height) + return block, nil +} + +// Function for matching accounts against filter data. +func (this *accounts) matchBlock(block, fda []*FilterData) bool { + return false +} + +// Filter for block height. +// Ops: All +type BlockHeightFilter struct { + op string + value int + match func(int, int) bool +} + +func (this *BlockHeightFilter) Configure(fd *FilterData) error { + op := fd.Op + var val int + if fd.Value == "min" { + val = 0 + } else if fd.Value == "max" { + val = math.MaxUint32 + } else { + tv, err := strconv.ParseInt(fd.Value, 10, 0) + if err != nil { + return fmt.Errorf("Wrong value type.") + } + val = int(tv) + } + + if op == "==" { + this.match = func(a, b int) bool { + return a == b + } + } else if op == "!=" { + this.match = func(a, b int) bool { + return a != b + } + } else if op == "<=" { + this.match = func(a, b int) bool { + return a <= b + } + } else if op == ">=" { + this.match = func(a, b int) bool { + return a >= b + } + } else if op == "<" { + this.match = func(a, b int) bool { + return a < b + } + } else if op == ">" { + this.match = func(a, b int) bool { + return a > b + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'height' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *BlockHeightFilter) Match(v interface{}) bool { + bl, ok := v.(*types.BlockMeta) + if !ok { + return false + } + return this.match(bl.Header.Height, this.value) +} + +// TODO i should start using named return params... +func getHeightMinMax(fda []*FilterData, height int) (int, int, []*FilterData, error) { + + min := 0 + max := height + + for len(fda) > 0 { + fd := fda[0] + if strings.EqualFold(fd.Field, "height") { + var val int + if fd.Value == "min" { + val = 0 + } else if fd.Value == "max" { + val = height + } else { + v, err := strconv.ParseInt(fd.Value, 10, 0) + if err != nil { + return 0, 0, nil, fmt.Errorf("Wrong value type") + } + val = int(v) + } + switch fd.Op { + case "==": + if val > height || val < 0 { + return 0, 0, nil, fmt.Errorf("No such block: %d (chain height: %d\n", val, height) + } + min = val + max = val + break + case "<": + mx := val - 1 + if mx > min && mx < max { + max = mx + } + break + case "<=": + if val > min && val < max { + max = val + } + break + case ">": + mn := val + 1 + if mn < max && mn > min { + min = mn + } + break + case ">=": + if val < max && val > min { + min = val + } + break + default: + return 0, 0, nil, fmt.Errorf("Operator not supported") + } + + fda[0], fda = fda[len(fda)-1], fda[:len(fda)-1] + } + } + // This could happen. + if max < min { + max = min + } + return min, max, fda, nil +} + +func min(x, y int) int { + if x > y { + return y + } + return x +} diff --git a/manager/eris-mint/consensus.go b/manager/eris-mint/consensus.go new file mode 100644 index 00000000..21d4e18f --- /dev/null +++ b/manager/eris-mint/consensus.go @@ -0,0 +1,65 @@ +// 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/>. + +// Consensus is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application + +package erismint +import ( + "github.com/tendermint/tendermint/types" + + core_types "github.com/eris-ltd/eris-db/core/types" +) + +// The consensus struct. +type consensus struct { + erisMint *ErisMint +} + +func newConsensus(erisMint *ErisMint) *consensus { + return &consensus{erisMint} +} + +// Get the current consensus state. +func (this *consensus) State() (*core_types.ConsensusState, error) { + // TODO-RPC! + return &core_types.ConsensusState{}, nil +} + +// Get all validators. +func (this *consensus) Validators() (*core_types.ValidatorList, error) { + var blockHeight int + bondedValidators := make([]*types.Validator, 0) + unbondingValidators := make([]*types.Validator, 0) + + s := this.erisMint.GetState() + blockHeight = s.LastBlockHeight + + // TODO: rpc + + /* + s.BondedValidators.Iterate(func(index int, val *types.Validator) bool { + bondedValidators = append(bondedValidators, val) + return false + }) + s.UnbondingValidators.Iterate(func(index int, val *types.Validator) bool { + unbondingValidators = append(unbondingValidators, val) + return false + })*/ + + return &core_types.ValidatorList{blockHeight, bondedValidators, + unbondingValidators}, nil +} diff --git a/manager/eris-mint/eris-mint.go b/manager/eris-mint/eris-mint.go index a3e9c46d..4b351863 100644 --- a/manager/eris-mint/eris-mint.go +++ b/manager/eris-mint/eris-mint.go @@ -22,7 +22,7 @@ import ( "fmt" "sync" - events "github.com/tendermint/go-events" + tendermint_events "github.com/tendermint/go-events" client "github.com/tendermint/go-rpc/client" wire "github.com/tendermint/go-wire" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -47,8 +47,8 @@ type ErisMint struct { cache *sm.BlockCache checkCache *sm.BlockCache // for CheckTx (eg. so we get nonces right) - evc *events.EventCache - evsw *events.EventSwitch + evc *tendermint_events.EventCache + evsw *tendermint_events.EventSwitch // client to the tendermint core rpc client *client.ClientURI @@ -103,12 +103,12 @@ func (app *ErisMint) BroadcastTx(tx types.Tx) error { return err } -func NewErisMint(s *sm.State, evsw *events.EventSwitch) *ErisMint { +func NewErisMint(s *sm.State, evsw *tendermint_events.EventSwitch) *ErisMint { return &ErisMint{ state: s, cache: sm.NewBlockCache(s), checkCache: sm.NewBlockCache(s), - evc: events.NewEventCache(evsw), + evc: tendermint_events.NewEventCache(evsw), evsw: evsw, } } diff --git a/manager/eris-mint/events.go b/manager/eris-mint/events.go new file mode 100644 index 00000000..c7d582ae --- /dev/null +++ b/manager/eris-mint/events.go @@ -0,0 +1,46 @@ +// 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/>. + +// Events is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +import ( + evts "github.com/tendermint/go-events" +) + +// TODO improve + +// 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/manager/eris-mint/filters.go b/manager/eris-mint/filters.go new file mode 100644 index 00000000..49e72e65 --- /dev/null +++ b/manager/eris-mint/filters.go @@ -0,0 +1,210 @@ +// 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/>. + +// Filters is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +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/manager/eris-mint/namereg.go b/manager/eris-mint/namereg.go new file mode 100644 index 00000000..c54a4226 --- /dev/null +++ b/manager/eris-mint/namereg.go @@ -0,0 +1,241 @@ +// 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/>. + +// NameReg is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + + sm "github.com/eris-ltd/eris-db/manager/eris-mint/state" + "github.com/eris-ltd/eris-db/txs" +) + +// The net struct. +type namereg struct { + erisMint *ErisMint + filterFactory *FilterFactory +} + +func newNamereg(erisMint *ErisMint) *namereg { + + ff := NewFilterFactory() + + ff.RegisterFilterPool("name", &sync.Pool{ + New: func() interface{} { + return &NameRegNameFilter{} + }, + }) + + ff.RegisterFilterPool("owner", &sync.Pool{ + New: func() interface{} { + return &NameRegOwnerFilter{} + }, + }) + + ff.RegisterFilterPool("data", &sync.Pool{ + New: func() interface{} { + return &NameRegDataFilter{} + }, + }) + + ff.RegisterFilterPool("expires", &sync.Pool{ + New: func() interface{} { + return &NameRegExpiresFilter{} + }, + }) + + return &namereg{erisMint, ff} +} + +func (this *namereg) Entry(key string) (*txs.NameRegEntry, error) { + st := this.erisMint.GetState() // performs a copy + entry := st.GetNameRegEntry(key) + if entry == nil { + return nil, fmt.Errorf("Entry %s not found", key) + } + return entry, nil +} + +func (this *namereg) Entries(filters []*FilterData) (*ResultListNames, error) { + var blockHeight int + var names []*txs.NameRegEntry + state := this.erisMint.GetState() + blockHeight = state.LastBlockHeight + filter, err := this.filterFactory.NewFilter(filters) + if err != nil { + return nil, fmt.Errorf("Error in query: " + err.Error()) + } + state.GetNames().Iterate(func(key, value []byte) bool { + nre := sm.DecodeNameRegEntry(value) + if filter.Match(nre) { + names = append(names, nre) + } + return false + }) + return &ResultListNames{blockHeight, names}, nil +} + +type ResultListNames struct { + BlockHeight int `json:"block_height"` + Names []*txs.NameRegEntry `json:"names"` +} + +// Filter for namereg name. This should not be used to get individual entries by name. +// Ops: == or != +type NameRegNameFilter struct { + op string + value string + match func(string, string) bool +} + +func (this *NameRegNameFilter) Configure(fd *FilterData) error { + op := fd.Op + val := fd.Value + + if op == "==" { + this.match = func(a, b string) bool { + return a == b + } + } else if op == "!=" { + this.match = func(a, b string) bool { + return a != b + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'name' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *NameRegNameFilter) Match(v interface{}) bool { + nre, ok := v.(*txs.NameRegEntry) + if !ok { + return false + } + return this.match(nre.Name, this.value) +} + +// Filter for owner. +// Ops: == or != +type NameRegOwnerFilter struct { + op string + value []byte + match func([]byte, []byte) bool +} + +func (this *NameRegOwnerFilter) Configure(fd *FilterData) error { + op := fd.Op + val, err := hex.DecodeString(fd.Value) + + if err != nil { + return fmt.Errorf("Wrong value type.") + } + if op == "==" { + this.match = func(a, b []byte) bool { + return bytes.Equal(a, b) + } + } else if op == "!=" { + this.match = func(a, b []byte) bool { + return !bytes.Equal(a, b) + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'owner' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *NameRegOwnerFilter) Match(v interface{}) bool { + nre, ok := v.(*txs.NameRegEntry) + if !ok { + return false + } + return this.match(nre.Owner, this.value) +} + +// Filter for namereg data. Useful for example if you store an ipfs hash and know the hash but need the key. +// Ops: == or != +type NameRegDataFilter struct { + op string + value string + match func(string, string) bool +} + +func (this *NameRegDataFilter) Configure(fd *FilterData) error { + op := fd.Op + val := fd.Value + + if op == "==" { + this.match = func(a, b string) bool { + return a == b + } + } else if op == "!=" { + this.match = func(a, b string) bool { + return a != b + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'data' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *NameRegDataFilter) Match(v interface{}) bool { + nre, ok := v.(*txs.NameRegEntry) + if !ok { + return false + } + return this.match(nre.Data, this.value) +} + +// Filter for expires. +// Ops: All +type NameRegExpiresFilter struct { + op string + value int64 + match func(int64, int64) bool +} + +func (this *NameRegExpiresFilter) Configure(fd *FilterData) error { + val, err := ParseNumberValue(fd.Value) + if err != nil { + return err + } + match, err2 := GetRangeFilter(fd.Op, "expires") + if err2 != nil { + return err2 + } + this.match = match + this.op = fd.Op + this.value = val + return nil +} + +func (this *NameRegExpiresFilter) Match(v interface{}) bool { + nre, ok := v.(*txs.NameRegEntry) + if !ok { + return false + } + return this.match(int64(nre.Expires), this.value) +} diff --git a/manager/eris-mint/net.go b/manager/eris-mint/net.go new file mode 100644 index 00000000..d8677155 --- /dev/null +++ b/manager/eris-mint/net.go @@ -0,0 +1,70 @@ +// 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/>. + +// Net is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +import ( + core_types "github.com/eris-ltd/eris-db/core/types" +) + +// TODO-RPC! + +// The net struct. +type network struct { +} + +func newNetwork() *network { + return &network{} +} + +//----------------------------------------------------------------------------- + +// Get the complete net info. +func (this *network) Info() (*core_types.NetworkInfo, error) { + return &core_types.NetworkInfo{}, nil +} + +// Get the client version +func (this *network) ClientVersion() (string, error) { + return "not-fully-loaded-yet", nil +} + +// Get the moniker +func (this *network) Moniker() (string, error) { + return "rekinom", nil +} + +// Is the network currently listening for connections. +func (this *network) Listening() (bool, error) { + return false, nil +} + +// Is the network currently listening for connections. +func (this *network) Listeners() ([]string, error) { + return []string{}, nil +} + +// Get a list of all peers. +func (this *network) Peers() ([]*core_types.Peer, error) { + return []*core_types.Peer{}, nil +} + +// Get a peer. TODO Need to do something about the address. +func (this *network) Peer(address string) (*core_types.Peer, error) { + return &core_types.Peer{}, nil +} diff --git a/manager/eris-mint/pipe.go b/manager/eris-mint/pipe.go index edfe3163..08e3000b 100644 --- a/manager/eris-mint/pipe.go +++ b/manager/eris-mint/pipe.go @@ -21,7 +21,7 @@ import ( "fmt" db "github.com/tendermint/go-db" - events "github.com/tendermint/go-events" + tendermint_events "github.com/tendermint/go-events" wire "github.com/tendermint/go-wire" log "github.com/eris-ltd/eris-logger" @@ -33,12 +33,12 @@ import ( type ErisMintPipe struct { erisMintState *state.State - eventSwitch *events.EventSwitch + eventSwitch *tendermint_events.EventSwitch erisMint *ErisMint } func NewErisMintPipe(moduleConfig *config.ModuleConfig, - genesisFile string, eventSwitch *events.EventSwitch) (*ErisMintPipe, error) { + genesisFile string, eventSwitch *tendermint_events.EventSwitch) (*ErisMintPipe, error) { startedState, err := startState(moduleConfig.DataDir, moduleConfig.Config.GetString("db_backend"), genesisFile, @@ -110,3 +110,6 @@ func startState(dataDir, backend, genesisFile, chainId string) (*state.State, return newState, nil } + +//------------------------------------------------------------------------------ +// Implement definitions.Pipe for ErisMintPipe diff --git a/manager/eris-mint/transactor.go b/manager/eris-mint/transactor.go new file mode 100644 index 00000000..54837514 --- /dev/null +++ b/manager/eris-mint/transactor.go @@ -0,0 +1,323 @@ +// 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/>. + +// Transactor is part of the pipe for ErisMint and provides the implementation +// for the pipe to call into the ErisMint application +package erismint + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + "time" + + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + tEvents "github.com/tendermint/go-events" + + "github.com/eris-ltd/eris-db/account" + core_types "github.com/eris-ltd/eris-db/core/types" + definitions "github.com/eris-ltd/eris-db/definitions" + "github.com/eris-ltd/eris-db/manager/eris-mint/evm" + "github.com/eris-ltd/eris-db/manager/eris-mint/state" + "github.com/eris-ltd/eris-db/txs" +) + +type transactor struct { + chainID string + eventSwitch tEvents.Fireable + erisMint *ErisMint + eventEmitter definitions.EventEmitter + txMtx *sync.Mutex +} + +func newTransactor(chainID string, eventSwitch tEvents.Fireable, + erisMint *ErisMint, eventEmitter definitions.EventEmitter) *transactor { + txs := &transactor{ + chainID, + eventSwitch, + erisMint, + eventEmitter, + &sync.Mutex{}, + } + return txs +} + +// Run a contract's code on an isolated and unpersisted state +// Cannot be used to create new contracts +func (this *transactor) Call(fromAddress, toAddress, data []byte) ( + *core_types.Call, error) { + + cache := this.erisMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) + outAcc := cache.GetAccount(toAddress) + if outAcc == nil { + return nil, fmt.Errorf("Account %X does not exist", toAddress) + } + if fromAddress == nil { + fromAddress = []byte{} + } + callee := toVMAccount(outAcc) + caller := &vm.Account{Address: cmn.LeftPadWord256(fromAddress)} + txCache := state.NewTxCache(cache) + st := this.erisMint.GetState() // for block height, time + params := vm.Params{ + BlockHeight: int64(st.LastBlockHeight), + BlockHash: cmn.LeftPadWord256(st.LastBlockHash), + BlockTime: st.LastBlockTime.Unix(), + GasLimit: 10000000, + } + + vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach.SetFireable(this.eventSwitch) + gas := int64(1000000000) + ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) + if err != nil { + return nil, err + } + return &core_types.Call{Return: hex.EncodeToString(ret)}, nil +} + +// Run the given code on an isolated and unpersisted state +// Cannot be used to create new contracts. +func (this *transactor) CallCode(fromAddress, code, data []byte) ( + *core_types.Call, error) { + if fromAddress == nil { + fromAddress = []byte{} + } + cache := this.erisMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) + callee := &vm.Account{Address: cmn.LeftPadWord256(fromAddress)} + caller := &vm.Account{Address: cmn.LeftPadWord256(fromAddress)} + txCache := state.NewTxCache(cache) + st := this.erisMint.GetState() // for block height, time + params := vm.Params{ + BlockHeight: int64(st.LastBlockHeight), + BlockHash: cmn.LeftPadWord256(st.LastBlockHash), + BlockTime: st.LastBlockTime.Unix(), + GasLimit: 10000000, + } + + vmach := vm.NewVM(txCache, params, caller.Address, nil) + gas := int64(1000000000) + ret, err := vmach.Call(caller, callee, code, data, 0, &gas) + if err != nil { + return nil, err + } + return &core_types.Call{Return: hex.EncodeToString(ret)}, nil +} + +// Broadcast a transaction. +func (this *transactor) BroadcastTx(tx txs.Tx) (*core_types.Receipt, error) { + + err := this.erisMint.BroadcastTx(tx) + if err != nil { + return nil, fmt.Errorf("Error broadcasting transaction: %v", err) + } + + txHash := txs.TxID(this.chainID, tx) + var createsContract uint8 + var contractAddr []byte + // check if creates new contract + if callTx, ok := tx.(*txs.CallTx); ok { + if len(callTx.Address) == 0 { + createsContract = 1 + contractAddr = state.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence) + } + } + return &core_types.Receipt{txHash, createsContract, contractAddr}, nil +} + +// Get all unconfirmed txs. +func (this *transactor) UnconfirmedTxs() (*core_types.UnconfirmedTxs, error) { + // TODO-RPC + return &core_types.UnconfirmedTxs{}, nil +} + +// Orders calls to BroadcastTx using lock (waits for response from core before releasing) +func (this *transactor) Transact(privKey, address, data []byte, gasLimit, + fee int64) (*core_types.Receipt, error) { + var addr []byte + if len(address) == 0 { + addr = nil + } else if len(address) != 20 { + return nil, fmt.Errorf("Address is not of the right length: %d\n", len(address)) + } else { + addr = address + } + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) + } + this.txMtx.Lock() + defer this.txMtx.Unlock() + pa := account.GenPrivAccountFromPrivKeyBytes(privKey) + cache := this.erisMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) + acc := cache.GetAccount(pa.Address) + var sequence int + if acc == nil { + sequence = 1 + } else { + sequence = acc.Sequence + 1 + } + // fmt.Printf("Sequence %d\n", sequence) + txInput := &txs.TxInput{ + Address: pa.Address, + Amount: 1, + Sequence: sequence, + PubKey: pa.PubKey, + } + tx := &txs.CallTx{ + Input: txInput, + Address: addr, + GasLimit: gasLimit, + Fee: fee, + Data: data, + } + + // Got ourselves a tx. + txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) + if errS != nil { + return nil, errS + } + return this.BroadcastTx(txS) +} + +func (this *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*txs.EventDataCall, error) { + rec, tErr := this.Transact(privKey, address, data, gasLimit, fee) + if tErr != nil { + return nil, tErr + } + var addr []byte + if rec.CreatesContract == 1 { + addr = rec.ContractAddr + } else { + addr = address + } + wc := make(chan *txs.EventDataCall) + subId := fmt.Sprintf("%X", rec.TxHash) + this.eventEmitter.Subscribe(subId, txs.EventStringAccCall(addr), func(evt tEvents.EventData) { + event := evt.(txs.EventDataCall) + if bytes.Equal(event.TxID, rec.TxHash) { + wc <- &event + } + }) + + timer := time.NewTimer(300 * time.Second) + toChan := timer.C + + var ret *txs.EventDataCall + var rErr error + + select { + case <-toChan: + rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) + case e := <-wc: + timer.Stop() + if e.Exception != "" { + rErr = fmt.Errorf("Error when transacting: " + e.Exception) + } else { + ret = e + } + } + this.eventEmitter.Unsubscribe(subId) + return ret, rErr +} + +func (this *transactor) TransactNameReg(privKey []byte, name, data string, + amount, fee int64) (*core_types.Receipt, error) { + + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) + } + this.txMtx.Lock() + defer this.txMtx.Unlock() + pa := account.GenPrivAccountFromPrivKeyBytes(privKey) + cache := this.erisMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) + acc := cache.GetAccount(pa.Address) + var sequence int + if acc == nil { + sequence = 1 + } else { + sequence = acc.Sequence + 1 + } + tx := txs.NewNameTxWithNonce(pa.PubKey, name, data, amount, fee, sequence) + // Got ourselves a tx. + txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) + if errS != nil { + return nil, errS + } + return this.BroadcastTx(txS) +} + +// Sign a transaction +func (this *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) { + // more checks? + + for i, privAccount := range privAccounts { + if privAccount == nil || privAccount.PrivKey == nil { + return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) + } + } + switch tx.(type) { + case *txs.NameTx: + nameTx := tx.(*txs.NameTx) + nameTx.Input.PubKey = privAccounts[0].PubKey + nameTx.Input.Signature = privAccounts[0].Sign(this.chainID, nameTx) + case *txs.SendTx: + sendTx := tx.(*txs.SendTx) + for i, input := range sendTx.Inputs { + input.PubKey = privAccounts[i].PubKey + input.Signature = privAccounts[i].Sign(this.chainID, sendTx) + } + break + case *txs.CallTx: + callTx := tx.(*txs.CallTx) + callTx.Input.PubKey = privAccounts[0].PubKey + callTx.Input.Signature = privAccounts[0].Sign(this.chainID, callTx) + break + case *txs.BondTx: + bondTx := tx.(*txs.BondTx) + // the first privaccount corresponds to the BondTx pub key. + // the rest to the inputs + bondTx.Signature = privAccounts[0].Sign(this.chainID, bondTx).(crypto.SignatureEd25519) + for i, input := range bondTx.Inputs { + input.PubKey = privAccounts[i+1].PubKey + input.Signature = privAccounts[i+1].Sign(this.chainID, bondTx) + } + break + case *txs.UnbondTx: + unbondTx := tx.(*txs.UnbondTx) + unbondTx.Signature = privAccounts[0].Sign(this.chainID, unbondTx).(crypto.SignatureEd25519) + break + case *txs.RebondTx: + rebondTx := tx.(*txs.RebondTx) + rebondTx.Signature = privAccounts[0].Sign(this.chainID, rebondTx).(crypto.SignatureEd25519) + break + default: + return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) + } + return tx, nil +} + +// No idea what this does. +func toVMAccount(acc *account.Account) *vm.Account { + return &vm.Account{ + Address: cmn.LeftPadWord256(acc.Address), + Balance: acc.Balance, + Code: acc.Code, + Nonce: int64(acc.Sequence), + Other: acc.PubKey, + } +} -- GitLab