diff --git a/manager/eris-mint/account/account.go b/manager/eris-mint/account/account.go new file mode 100644 index 0000000000000000000000000000000000000000..62e1a8209e6f4b0aa37de3f5736807560305b927 --- /dev/null +++ b/manager/eris-mint/account/account.go @@ -0,0 +1,90 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..eb5f424ffbcca05208d0a26c40bd08e1fb93d848 --- /dev/null +++ b/manager/eris-mint/account/priv_account.go @@ -0,0 +1,85 @@ +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/eris-mint.go b/manager/eris-mint/eris-mint.go new file mode 100644 index 0000000000000000000000000000000000000000..e12fe1439c430b38f5534e2e7dfee52ba687a86f --- /dev/null +++ b/manager/eris-mint/eris-mint.go @@ -0,0 +1,197 @@ +// 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 eris-mint + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + + sm "github.com/eris-ltd/eris-db/state" + types "github.com/eris-ltd/eris-db/txs" + + "github.com/tendermint/go-events" + client "github.com/tendermint/go-rpc/client" + "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + + tmsp "github.com/tendermint/tmsp/types" +) + +//-------------------------------------------------------------------------------- +// ErisDBApp holds the current state, runs transactions, computes hashes. +// Typically two connections are opened by the tendermint core: +// one for mempool, one for consensus. + +type ErisDBApp struct { + mtx sync.Mutex + + state *sm.State + cache *sm.BlockCache + checkCache *sm.BlockCache // for CheckTx (eg. so we get nonces right) + + evc *events.EventCache + evsw *events.EventSwitch + + // client to the tendermint core rpc + client *client.ClientURI + host string // tendermint core endpoint + + nTxs int // count txs in a block +} + +func (app *ErisDBApp) GetState() *sm.State { + app.mtx.Lock() + defer app.mtx.Unlock() + return app.state.Copy() +} + +// TODO: this is used for call/callcode and to get nonces during mempool. +// the former should work on last committed state only and the later should +// be handled by the client, or a separate wallet-like nonce tracker thats not part of the app +func (app *ErisDBApp) GetCheckCache() *sm.BlockCache { + return app.checkCache +} + +func (app *ErisDBApp) SetHostAddress(host string) { + app.host = host + app.client = client.NewClientURI(host) //fmt.Sprintf("http://%s", host)) +} + +// Broadcast a tx to the tendermint core +// NOTE: this assumes we know the address of core +func (app *ErisDBApp) BroadcastTx(tx types.Tx) error { + buf := new(bytes.Buffer) + var n int + var err error + wire.WriteBinary(struct{ types.Tx }{tx}, buf, &n, &err) + if err != nil { + return err + } + + params := map[string]interface{}{ + "tx": hex.EncodeToString(buf.Bytes()), + } + + var result ctypes.TMResult + _, err = app.client.Call("broadcast_tx_sync", params, &result) + return err +} + +func NewErisDBApp(s *sm.State, evsw *events.EventSwitch) *ErisDBApp { + return &ErisDBApp{ + state: s, + cache: sm.NewBlockCache(s), + checkCache: sm.NewBlockCache(s), + evc: events.NewEventCache(evsw), + evsw: evsw, + } +} + +// Implements tmsp.Application +func (app *ErisDBApp) Info() (info string) { + return "ErisDB" +} + +// Implements tmsp.Application +func (app *ErisDBApp) SetOption(key string, value string) (log string) { + return "" +} + +// Implements tmsp.Application +func (app *ErisDBApp) AppendTx(txBytes []byte) (res tmsp.Result) { + + app.nTxs += 1 + + // XXX: if we had tx ids we could cache the decoded txs on CheckTx + var n int + var err error + tx := new(types.Tx) + buf := bytes.NewBuffer(txBytes) + wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) + if err != nil { + return tmsp.NewError(tmsp.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) + } + + log.Info("AppendTx", "tx", *tx) + + err = sm.ExecTx(app.cache, *tx, true, app.evc) + if err != nil { + return tmsp.NewError(tmsp.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) + } + // TODO: need to return receipt so rpc.ResultBroadcastTx.Data (or Log) is the receipt + return tmsp.NewResultOK(nil, "Success") +} + +// Implements tmsp.Application +func (app *ErisDBApp) CheckTx(txBytes []byte) (res tmsp.Result) { + var n int + var err error + tx := new(types.Tx) + buf := bytes.NewBuffer(txBytes) + wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) + if err != nil { + return tmsp.NewError(tmsp.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) + } + + log.Info("CheckTx", "tx", *tx) + + // TODO: make errors tmsp aware + err = sm.ExecTx(app.checkCache, *tx, false, nil) + if err != nil { + return tmsp.NewError(tmsp.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) + } + + // TODO: need to return receipt so rpc.ResultBroadcastTx.Data (or Log) is the receipt + return tmsp.NewResultOK(nil, "Success") +} + +// Implements tmsp.Application +// Commit the state (called at end of block) +// NOTE: CheckTx/AppendTx must not run concurrently with Commit - +// the mempool should run during AppendTxs, but lock for Commit and Update +func (app *ErisDBApp) Commit() (res tmsp.Result) { + app.mtx.Lock() // the lock protects app.state + defer app.mtx.Unlock() + + app.state.LastBlockHeight += 1 + log.Info("Commit", "block", app.state.LastBlockHeight) + + // sync the AppendTx cache + app.cache.Sync() + + // if there were any txs in the block, + // reset the check cache to the new height + if app.nTxs > 0 { + log.Info("Reset checkCache", "txs", app.nTxs) + app.checkCache = sm.NewBlockCache(app.state) + } + app.nTxs = 0 + + // save state to disk + app.state.Save() + + // flush events to listeners (XXX: note issue with blocking) + app.evc.Flush() + + return tmsp.NewResultOK(app.state.Hash(), "Success") +} + +func (app *ErisDBApp) Query(query []byte) (res tmsp.Result) { + return tmsp.NewResultOK(nil, "Success") +} diff --git a/manager/eris-mint/evm/common.go b/manager/eris-mint/evm/common.go new file mode 100644 index 0000000000000000000000000000000000000000..00ab93c775f6b4ba8e29c1cc38eca28a9a7920f2 --- /dev/null +++ b/manager/eris-mint/evm/common.go @@ -0,0 +1,26 @@ +package vm + +import ( + "math/big" +) + +// To256 +// +// "cast" the big int to a 256 big int (i.e., limit to) +var tt256 = new(big.Int).Lsh(big.NewInt(1), 256) +var tt256m1 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) +var tt255 = new(big.Int).Lsh(big.NewInt(1), 255) + +func U256(x *big.Int) *big.Int { + x.And(x, tt256m1) + return x +} + +func S256(x *big.Int) *big.Int { + if x.Cmp(tt255) < 0 { + return x + } else { + // We don't want to modify x, ever + return new(big.Int).Sub(x, tt256) + } +} diff --git a/manager/eris-mint/evm/gas.go b/manager/eris-mint/evm/gas.go new file mode 100644 index 0000000000000000000000000000000000000000..fe4a43d80d5ae2ffddbb75b44f0a5633d70bb43e --- /dev/null +++ b/manager/eris-mint/evm/gas.go @@ -0,0 +1,18 @@ +package vm + +const ( + GasSha3 int64 = 1 + GasGetAccount int64 = 1 + GasStorageUpdate int64 = 1 + + GasBaseOp int64 = 0 // TODO: make this 1 + GasStackOp int64 = 1 + + GasEcRecover int64 = 1 + GasSha256Word int64 = 1 + GasSha256Base int64 = 1 + GasRipemd160Word int64 = 1 + GasRipemd160Base int64 = 1 + GasIdentityWord int64 = 1 + GasIdentityBase int64 = 1 +) diff --git a/manager/eris-mint/evm/log.go b/manager/eris-mint/evm/log.go new file mode 100644 index 0000000000000000000000000000000000000000..82880ae975da279da6f2cc43c4f0b547079ef790 --- /dev/null +++ b/manager/eris-mint/evm/log.go @@ -0,0 +1,7 @@ +package vm + +import ( + "github.com/tendermint/go-logger" +) + +var log = logger.New("module", "vm") diff --git a/manager/eris-mint/evm/native.go b/manager/eris-mint/evm/native.go new file mode 100644 index 0000000000000000000000000000000000000000..55b18a167bdcf68e7642679437f06703ca72b6b8 --- /dev/null +++ b/manager/eris-mint/evm/native.go @@ -0,0 +1,105 @@ +package vm + +import ( + "crypto/sha256" + "golang.org/x/crypto/ripemd160" + + . "github.com/tendermint/go-common" +) + +var registeredNativeContracts = make(map[Word256]NativeContract) + +func RegisteredNativeContract(addr Word256) bool { + _, ok := registeredNativeContracts[addr] + return ok +} + +func RegisterNativeContract(addr Word256, fn NativeContract) bool { + _, exists := registeredNativeContracts[addr] + if exists { + return false + } + registeredNativeContracts[addr] = fn + return true +} + +func init() { + registerNativeContracts() + registerSNativeContracts() +} + +func registerNativeContracts() { + // registeredNativeContracts[Int64ToWord256(1)] = ecrecoverFunc + registeredNativeContracts[Int64ToWord256(2)] = sha256Func + registeredNativeContracts[Int64ToWord256(3)] = ripemd160Func + registeredNativeContracts[Int64ToWord256(4)] = identityFunc +} + +//----------------------------------------------------------------------------- + +type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) + +/* Removed due to C dependency +func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { + // Deduct gas + gasRequired := GasEcRecover + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Recover + hash := input[:32] + v := byte(input[32] - 27) // ignore input[33:64], v is small. + sig := append(input[64:], v) + + recovered, err := secp256k1.RecoverPubkey(hash, sig) + if err != nil { + return nil, err + } + hashed := sha3.Sha3(recovered[1:]) + return LeftPadBytes(hashed, 32), nil +} +*/ + +func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { + // Deduct gas + gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Hash + hasher := sha256.New() + // CONTRACT: this does not err + hasher.Write(input) + return hasher.Sum(nil), nil +} + +func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { + // Deduct gas + gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Hash + hasher := ripemd160.New() + // CONTRACT: this does not err + hasher.Write(input) + return LeftPadBytes(hasher.Sum(nil), 32), nil +} + +func identityFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { + // Deduct gas + gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Return identity + return input, nil +} diff --git a/manager/eris-mint/evm/opcodes.go b/manager/eris-mint/evm/opcodes.go new file mode 100644 index 0000000000000000000000000000000000000000..87e09bfdd75bc0c78f86010928f497da0abede1a --- /dev/null +++ b/manager/eris-mint/evm/opcodes.go @@ -0,0 +1,354 @@ +package vm + +import ( + "fmt" + "gopkg.in/fatih/set.v0" +) + +type OpCode byte + +const ( + // Op codes + // 0x0 range - arithmetic ops + STOP OpCode = iota + ADD + MUL + SUB + DIV + SDIV + MOD + SMOD + ADDMOD + MULMOD + EXP + SIGNEXTEND +) + +const ( + LT OpCode = iota + 0x10 + GT + SLT + SGT + EQ + ISZERO + AND + OR + XOR + NOT + BYTE + + SHA3 = 0x20 +) + +const ( + // 0x30 range - closure state + ADDRESS OpCode = 0x30 + iota + BALANCE + ORIGIN + CALLER + CALLVALUE + CALLDATALOAD + CALLDATASIZE + CALLDATACOPY + CODESIZE + CODECOPY + GASPRICE_DEPRECATED + EXTCODESIZE + EXTCODECOPY +) + +const ( + + // 0x40 range - block operations + BLOCKHASH OpCode = 0x40 + iota + COINBASE + TIMESTAMP + BLOCKHEIGHT + DIFFICULTY_DEPRECATED + GASLIMIT +) + +const ( + // 0x50 range - 'storage' and execution + POP OpCode = 0x50 + iota + MLOAD + MSTORE + MSTORE8 + SLOAD + SSTORE + JUMP + JUMPI + PC + MSIZE + GAS + JUMPDEST +) + +const ( + // 0x60 range + PUSH1 OpCode = 0x60 + iota + PUSH2 + PUSH3 + PUSH4 + PUSH5 + PUSH6 + PUSH7 + PUSH8 + PUSH9 + PUSH10 + PUSH11 + PUSH12 + PUSH13 + PUSH14 + PUSH15 + PUSH16 + PUSH17 + PUSH18 + PUSH19 + PUSH20 + PUSH21 + PUSH22 + PUSH23 + PUSH24 + PUSH25 + PUSH26 + PUSH27 + PUSH28 + PUSH29 + PUSH30 + PUSH31 + PUSH32 + DUP1 + DUP2 + DUP3 + DUP4 + DUP5 + DUP6 + DUP7 + DUP8 + DUP9 + DUP10 + DUP11 + DUP12 + DUP13 + DUP14 + DUP15 + DUP16 + SWAP1 + SWAP2 + SWAP3 + SWAP4 + SWAP5 + SWAP6 + SWAP7 + SWAP8 + SWAP9 + SWAP10 + SWAP11 + SWAP12 + SWAP13 + SWAP14 + SWAP15 + SWAP16 +) + +const ( + LOG0 OpCode = 0xa0 + iota + LOG1 + LOG2 + LOG3 + LOG4 +) + +const ( + // 0xf0 range - closures + CREATE OpCode = 0xf0 + iota + CALL + CALLCODE + RETURN + + // 0x70 range - other + SUICIDE = 0xff +) + +// Since the opcodes aren't all in order we can't use a regular slice +var opCodeToString = map[OpCode]string{ + // 0x0 range - arithmetic ops + STOP: "STOP", + ADD: "ADD", + MUL: "MUL", + SUB: "SUB", + DIV: "DIV", + SDIV: "SDIV", + MOD: "MOD", + SMOD: "SMOD", + EXP: "EXP", + NOT: "NOT", + LT: "LT", + GT: "GT", + SLT: "SLT", + SGT: "SGT", + EQ: "EQ", + ISZERO: "ISZERO", + SIGNEXTEND: "SIGNEXTEND", + + // 0x10 range - bit ops + AND: "AND", + OR: "OR", + XOR: "XOR", + BYTE: "BYTE", + ADDMOD: "ADDMOD", + MULMOD: "MULMOD", + + // 0x20 range - crypto + SHA3: "SHA3", + + // 0x30 range - closure state + ADDRESS: "ADDRESS", + BALANCE: "BALANCE", + ORIGIN: "ORIGIN", + CALLER: "CALLER", + CALLVALUE: "CALLVALUE", + CALLDATALOAD: "CALLDATALOAD", + CALLDATASIZE: "CALLDATASIZE", + CALLDATACOPY: "CALLDATACOPY", + CODESIZE: "CODESIZE", + CODECOPY: "CODECOPY", + GASPRICE_DEPRECATED: "TXGASPRICE_DEPRECATED", + + // 0x40 range - block operations + BLOCKHASH: "BLOCKHASH", + COINBASE: "COINBASE", + TIMESTAMP: "TIMESTAMP", + BLOCKHEIGHT: "BLOCKHEIGHT", + DIFFICULTY_DEPRECATED: "DIFFICULTY_DEPRECATED", + GASLIMIT: "GASLIMIT", + EXTCODESIZE: "EXTCODESIZE", + EXTCODECOPY: "EXTCODECOPY", + + // 0x50 range - 'storage' and execution + POP: "POP", + //DUP: "DUP", + //SWAP: "SWAP", + MLOAD: "MLOAD", + MSTORE: "MSTORE", + MSTORE8: "MSTORE8", + SLOAD: "SLOAD", + SSTORE: "SSTORE", + JUMP: "JUMP", + JUMPI: "JUMPI", + PC: "PC", + MSIZE: "MSIZE", + GAS: "GAS", + JUMPDEST: "JUMPDEST", + + // 0x60 range - push + PUSH1: "PUSH1", + PUSH2: "PUSH2", + PUSH3: "PUSH3", + PUSH4: "PUSH4", + PUSH5: "PUSH5", + PUSH6: "PUSH6", + PUSH7: "PUSH7", + PUSH8: "PUSH8", + PUSH9: "PUSH9", + PUSH10: "PUSH10", + PUSH11: "PUSH11", + PUSH12: "PUSH12", + PUSH13: "PUSH13", + PUSH14: "PUSH14", + PUSH15: "PUSH15", + PUSH16: "PUSH16", + PUSH17: "PUSH17", + PUSH18: "PUSH18", + PUSH19: "PUSH19", + PUSH20: "PUSH20", + PUSH21: "PUSH21", + PUSH22: "PUSH22", + PUSH23: "PUSH23", + PUSH24: "PUSH24", + PUSH25: "PUSH25", + PUSH26: "PUSH26", + PUSH27: "PUSH27", + PUSH28: "PUSH28", + PUSH29: "PUSH29", + PUSH30: "PUSH30", + PUSH31: "PUSH31", + PUSH32: "PUSH32", + + DUP1: "DUP1", + DUP2: "DUP2", + DUP3: "DUP3", + DUP4: "DUP4", + DUP5: "DUP5", + DUP6: "DUP6", + DUP7: "DUP7", + DUP8: "DUP8", + DUP9: "DUP9", + DUP10: "DUP10", + DUP11: "DUP11", + DUP12: "DUP12", + DUP13: "DUP13", + DUP14: "DUP14", + DUP15: "DUP15", + DUP16: "DUP16", + + SWAP1: "SWAP1", + SWAP2: "SWAP2", + SWAP3: "SWAP3", + SWAP4: "SWAP4", + SWAP5: "SWAP5", + SWAP6: "SWAP6", + SWAP7: "SWAP7", + SWAP8: "SWAP8", + SWAP9: "SWAP9", + SWAP10: "SWAP10", + SWAP11: "SWAP11", + SWAP12: "SWAP12", + SWAP13: "SWAP13", + SWAP14: "SWAP14", + SWAP15: "SWAP15", + SWAP16: "SWAP16", + LOG0: "LOG0", + LOG1: "LOG1", + LOG2: "LOG2", + LOG3: "LOG3", + LOG4: "LOG4", + + // 0xf0 range + CREATE: "CREATE", + CALL: "CALL", + RETURN: "RETURN", + CALLCODE: "CALLCODE", + + // 0x70 range - other + SUICIDE: "SUICIDE", +} + +func (o OpCode) String() string { + str := opCodeToString[o] + if len(str) == 0 { + return fmt.Sprintf("Missing opcode 0x%x", int(o)) + } + + return str +} + +//----------------------------------------------------------------------------- + +func AnalyzeJumpDests(code []byte) (dests *set.Set) { + dests = set.New() + + for pc := uint64(0); pc < uint64(len(code)); pc++ { + var op OpCode = OpCode(code[pc]) + switch op { + case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: + a := uint64(op) - uint64(PUSH1) + 1 + + pc += a + case JUMPDEST: + dests.Add(pc) + } + } + return +} diff --git a/manager/eris-mint/evm/sha3/keccakf.go b/manager/eris-mint/evm/sha3/keccakf.go new file mode 100644 index 0000000000000000000000000000000000000000..3baf13ba3d89ff1b0274711182f008dc971cb937 --- /dev/null +++ b/manager/eris-mint/evm/sha3/keccakf.go @@ -0,0 +1,171 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This file implements the core Keccak permutation function necessary for computing SHA3. +// This is implemented in a separate file to allow for replacement by an optimized implementation. +// Nothing in this package is exported. +// For the detailed specification, refer to the Keccak web site (http://keccak.noekeon.org/). + +// rc stores the round constants for use in the ι step. +var rc = [...]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +// ro_xx represent the rotation offsets for use in the χ step. +// Defining them as const instead of in an array allows the compiler to insert constant shifts. +const ( + ro_00 = 0 + ro_01 = 36 + ro_02 = 3 + ro_03 = 41 + ro_04 = 18 + ro_05 = 1 + ro_06 = 44 + ro_07 = 10 + ro_08 = 45 + ro_09 = 2 + ro_10 = 62 + ro_11 = 6 + ro_12 = 43 + ro_13 = 15 + ro_14 = 61 + ro_15 = 28 + ro_16 = 55 + ro_17 = 25 + ro_18 = 21 + ro_19 = 56 + ro_20 = 27 + ro_21 = 20 + ro_22 = 39 + ro_23 = 8 + ro_24 = 14 +) + +// keccakF computes the complete Keccak-f function consisting of 24 rounds with a different +// constant (rc) in each round. This implementation fully unrolls the round function to avoid +// inner loops, as well as pre-calculating shift offsets. +func (d *digest) keccakF() { + for _, roundConstant := range rc { + // θ step + d.c[0] = d.a[0] ^ d.a[5] ^ d.a[10] ^ d.a[15] ^ d.a[20] + d.c[1] = d.a[1] ^ d.a[6] ^ d.a[11] ^ d.a[16] ^ d.a[21] + d.c[2] = d.a[2] ^ d.a[7] ^ d.a[12] ^ d.a[17] ^ d.a[22] + d.c[3] = d.a[3] ^ d.a[8] ^ d.a[13] ^ d.a[18] ^ d.a[23] + d.c[4] = d.a[4] ^ d.a[9] ^ d.a[14] ^ d.a[19] ^ d.a[24] + + d.d[0] = d.c[4] ^ (d.c[1]<<1 ^ d.c[1]>>63) + d.d[1] = d.c[0] ^ (d.c[2]<<1 ^ d.c[2]>>63) + d.d[2] = d.c[1] ^ (d.c[3]<<1 ^ d.c[3]>>63) + d.d[3] = d.c[2] ^ (d.c[4]<<1 ^ d.c[4]>>63) + d.d[4] = d.c[3] ^ (d.c[0]<<1 ^ d.c[0]>>63) + + d.a[0] ^= d.d[0] + d.a[1] ^= d.d[1] + d.a[2] ^= d.d[2] + d.a[3] ^= d.d[3] + d.a[4] ^= d.d[4] + d.a[5] ^= d.d[0] + d.a[6] ^= d.d[1] + d.a[7] ^= d.d[2] + d.a[8] ^= d.d[3] + d.a[9] ^= d.d[4] + d.a[10] ^= d.d[0] + d.a[11] ^= d.d[1] + d.a[12] ^= d.d[2] + d.a[13] ^= d.d[3] + d.a[14] ^= d.d[4] + d.a[15] ^= d.d[0] + d.a[16] ^= d.d[1] + d.a[17] ^= d.d[2] + d.a[18] ^= d.d[3] + d.a[19] ^= d.d[4] + d.a[20] ^= d.d[0] + d.a[21] ^= d.d[1] + d.a[22] ^= d.d[2] + d.a[23] ^= d.d[3] + d.a[24] ^= d.d[4] + + // Ï and Ï€ steps + d.b[0] = d.a[0] + d.b[1] = d.a[6]<<ro_06 ^ d.a[6]>>(64-ro_06) + d.b[2] = d.a[12]<<ro_12 ^ d.a[12]>>(64-ro_12) + d.b[3] = d.a[18]<<ro_18 ^ d.a[18]>>(64-ro_18) + d.b[4] = d.a[24]<<ro_24 ^ d.a[24]>>(64-ro_24) + d.b[5] = d.a[3]<<ro_15 ^ d.a[3]>>(64-ro_15) + d.b[6] = d.a[9]<<ro_21 ^ d.a[9]>>(64-ro_21) + d.b[7] = d.a[10]<<ro_02 ^ d.a[10]>>(64-ro_02) + d.b[8] = d.a[16]<<ro_08 ^ d.a[16]>>(64-ro_08) + d.b[9] = d.a[22]<<ro_14 ^ d.a[22]>>(64-ro_14) + d.b[10] = d.a[1]<<ro_05 ^ d.a[1]>>(64-ro_05) + d.b[11] = d.a[7]<<ro_11 ^ d.a[7]>>(64-ro_11) + d.b[12] = d.a[13]<<ro_17 ^ d.a[13]>>(64-ro_17) + d.b[13] = d.a[19]<<ro_23 ^ d.a[19]>>(64-ro_23) + d.b[14] = d.a[20]<<ro_04 ^ d.a[20]>>(64-ro_04) + d.b[15] = d.a[4]<<ro_20 ^ d.a[4]>>(64-ro_20) + d.b[16] = d.a[5]<<ro_01 ^ d.a[5]>>(64-ro_01) + d.b[17] = d.a[11]<<ro_07 ^ d.a[11]>>(64-ro_07) + d.b[18] = d.a[17]<<ro_13 ^ d.a[17]>>(64-ro_13) + d.b[19] = d.a[23]<<ro_19 ^ d.a[23]>>(64-ro_19) + d.b[20] = d.a[2]<<ro_10 ^ d.a[2]>>(64-ro_10) + d.b[21] = d.a[8]<<ro_16 ^ d.a[8]>>(64-ro_16) + d.b[22] = d.a[14]<<ro_22 ^ d.a[14]>>(64-ro_22) + d.b[23] = d.a[15]<<ro_03 ^ d.a[15]>>(64-ro_03) + d.b[24] = d.a[21]<<ro_09 ^ d.a[21]>>(64-ro_09) + + // χ step + d.a[0] = d.b[0] ^ (^d.b[1] & d.b[2]) + d.a[1] = d.b[1] ^ (^d.b[2] & d.b[3]) + d.a[2] = d.b[2] ^ (^d.b[3] & d.b[4]) + d.a[3] = d.b[3] ^ (^d.b[4] & d.b[0]) + d.a[4] = d.b[4] ^ (^d.b[0] & d.b[1]) + d.a[5] = d.b[5] ^ (^d.b[6] & d.b[7]) + d.a[6] = d.b[6] ^ (^d.b[7] & d.b[8]) + d.a[7] = d.b[7] ^ (^d.b[8] & d.b[9]) + d.a[8] = d.b[8] ^ (^d.b[9] & d.b[5]) + d.a[9] = d.b[9] ^ (^d.b[5] & d.b[6]) + d.a[10] = d.b[10] ^ (^d.b[11] & d.b[12]) + d.a[11] = d.b[11] ^ (^d.b[12] & d.b[13]) + d.a[12] = d.b[12] ^ (^d.b[13] & d.b[14]) + d.a[13] = d.b[13] ^ (^d.b[14] & d.b[10]) + d.a[14] = d.b[14] ^ (^d.b[10] & d.b[11]) + d.a[15] = d.b[15] ^ (^d.b[16] & d.b[17]) + d.a[16] = d.b[16] ^ (^d.b[17] & d.b[18]) + d.a[17] = d.b[17] ^ (^d.b[18] & d.b[19]) + d.a[18] = d.b[18] ^ (^d.b[19] & d.b[15]) + d.a[19] = d.b[19] ^ (^d.b[15] & d.b[16]) + d.a[20] = d.b[20] ^ (^d.b[21] & d.b[22]) + d.a[21] = d.b[21] ^ (^d.b[22] & d.b[23]) + d.a[22] = d.b[22] ^ (^d.b[23] & d.b[24]) + d.a[23] = d.b[23] ^ (^d.b[24] & d.b[20]) + d.a[24] = d.b[24] ^ (^d.b[20] & d.b[21]) + + // ι step + d.a[0] ^= roundConstant + } +} diff --git a/manager/eris-mint/evm/sha3/sha3.go b/manager/eris-mint/evm/sha3/sha3.go new file mode 100644 index 0000000000000000000000000000000000000000..da6b381f40da8a51c39aeffd5ca7791d3e74dd66 --- /dev/null +++ b/manager/eris-mint/evm/sha3/sha3.go @@ -0,0 +1,224 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha3 implements the SHA3 hash algorithm (formerly called Keccak) chosen by NIST in 2012. +// This file provides a SHA3 implementation which implements the standard hash.Hash interface. +// Writing input data, including padding, and reading output data are computed in this file. +// Note that the current implementation can compute the hash of an integral number of bytes only. +// This is a consequence of the hash interface in which a buffer of bytes is passed in. +// The internals of the Keccak-f function are computed in keccakf.go. +// For the detailed specification, refer to the Keccak web site (http://keccak.noekeon.org/). +package sha3 + +import ( + "encoding/binary" + "hash" +) + +// laneSize is the size in bytes of each "lane" of the internal state of SHA3 (5 * 5 * 8). +// Note that changing this size would requires using a type other than uint64 to store each lane. +const laneSize = 8 + +// sliceSize represents the dimensions of the internal state, a square matrix of +// sliceSize ** 2 lanes. This is the size of both the "rows" and "columns" dimensions in the +// terminology of the SHA3 specification. +const sliceSize = 5 + +// numLanes represents the total number of lanes in the state. +const numLanes = sliceSize * sliceSize + +// stateSize is the size in bytes of the internal state of SHA3 (5 * 5 * WSize). +const stateSize = laneSize * numLanes + +// digest represents the partial evaluation of a checksum. +// Note that capacity, and not outputSize, is the critical security parameter, as SHA3 can output +// an arbitrary number of bytes for any given capacity. The Keccak proposal recommends that +// capacity = 2*outputSize to ensure that finding a collision of size outputSize requires +// O(2^{outputSize/2}) computations (the birthday lower bound). Future standards may modify the +// capacity/outputSize ratio to allow for more output with lower cryptographic security. +type digest struct { + a [numLanes]uint64 // main state of the hash + b [numLanes]uint64 // intermediate states + c [sliceSize]uint64 // intermediate states + d [sliceSize]uint64 // intermediate states + outputSize int // desired output size in bytes + capacity int // number of bytes to leave untouched during squeeze/absorb + absorbed int // number of bytes absorbed thus far +} + +// minInt returns the lesser of two integer arguments, to simplify the absorption routine. +func minInt(v1, v2 int) int { + if v1 <= v2 { + return v1 + } + return v2 +} + +// rate returns the number of bytes of the internal state which can be absorbed or squeezed +// in between calls to the permutation function. +func (d *digest) rate() int { + return stateSize - d.capacity +} + +// Reset clears the internal state by zeroing bytes in the state buffer. +// This can be skipped for a newly-created hash state; the default zero-allocated state is correct. +func (d *digest) Reset() { + d.absorbed = 0 + for i := range d.a { + d.a[i] = 0 + } +} + +// BlockSize, required by the hash.Hash interface, does not have a standard intepretation +// for a sponge-based construction like SHA3. We return the data rate: the number of bytes which +// can be absorbed per invocation of the permutation function. For Merkle-DamgÃ¥rd based hashes +// (ie SHA1, SHA2, MD5) the output size of the internal compression function is returned. +// We consider this to be roughly equivalent because it represents the number of bytes of output +// produced per cryptographic operation. +func (d *digest) BlockSize() int { return d.rate() } + +// Size returns the output size of the hash function in bytes. +func (d *digest) Size() int { + return d.outputSize +} + +// unalignedAbsorb is a helper function for Write, which absorbs data that isn't aligned with an +// 8-byte lane. This requires shifting the individual bytes into position in a uint64. +func (d *digest) unalignedAbsorb(p []byte) { + var t uint64 + for i := len(p) - 1; i >= 0; i-- { + t <<= 8 + t |= uint64(p[i]) + } + offset := (d.absorbed) % d.rate() + t <<= 8 * uint(offset%laneSize) + d.a[offset/laneSize] ^= t + d.absorbed += len(p) +} + +// Write "absorbs" bytes into the state of the SHA3 hash, updating as needed when the sponge +// "fills up" with rate() bytes. Since lanes are stored internally as type uint64, this requires +// converting the incoming bytes into uint64s using a little endian interpretation. This +// implementation is optimized for large, aligned writes of multiples of 8 bytes (laneSize). +// Non-aligned or uneven numbers of bytes require shifting and are slower. +func (d *digest) Write(p []byte) (int, error) { + // An initial offset is needed if the we aren't absorbing to the first lane initially. + offset := d.absorbed % d.rate() + toWrite := len(p) + + // The first lane may need to absorb unaligned and/or incomplete data. + if (offset%laneSize != 0 || len(p) < 8) && len(p) > 0 { + toAbsorb := minInt(laneSize-(offset%laneSize), len(p)) + d.unalignedAbsorb(p[:toAbsorb]) + p = p[toAbsorb:] + offset = (d.absorbed) % d.rate() + + // For every rate() bytes absorbed, the state must be permuted via the F Function. + if (d.absorbed)%d.rate() == 0 { + d.keccakF() + } + } + + // This loop should absorb the bulk of the data into full, aligned lanes. + // It will call the update function as necessary. + for len(p) > 7 { + firstLane := offset / laneSize + lastLane := minInt(d.rate()/laneSize, firstLane+len(p)/laneSize) + + // This inner loop absorbs input bytes into the state in groups of 8, converted to uint64s. + for lane := firstLane; lane < lastLane; lane++ { + d.a[lane] ^= binary.LittleEndian.Uint64(p[:laneSize]) + p = p[laneSize:] + } + d.absorbed += (lastLane - firstLane) * laneSize + // For every rate() bytes absorbed, the state must be permuted via the F Function. + if (d.absorbed)%d.rate() == 0 { + d.keccakF() + } + + offset = 0 + } + + // If there are insufficient bytes to fill the final lane, an unaligned absorption. + // This should always start at a correct lane boundary though, or else it would be caught + // by the uneven opening lane case above. + if len(p) > 0 { + d.unalignedAbsorb(p) + } + + return toWrite, nil +} + +// pad computes the SHA3 padding scheme based on the number of bytes absorbed. +// The padding is a 1 bit, followed by an arbitrary number of 0s and then a final 1 bit, such that +// the input bits plus padding bits are a multiple of rate(). Adding the padding simply requires +// xoring an opening and closing bit into the appropriate lanes. +func (d *digest) pad() { + offset := d.absorbed % d.rate() + // The opening pad bit must be shifted into position based on the number of bytes absorbed + padOpenLane := offset / laneSize + d.a[padOpenLane] ^= 0x0000000000000001 << uint(8*(offset%laneSize)) + // The closing padding bit is always in the last position + padCloseLane := (d.rate() / laneSize) - 1 + d.a[padCloseLane] ^= 0x8000000000000000 +} + +// finalize prepares the hash to output data by padding and one final permutation of the state. +func (d *digest) finalize() { + d.pad() + d.keccakF() +} + +// squeeze outputs an arbitrary number of bytes from the hash state. +// Squeezing can require multiple calls to the F function (one per rate() bytes squeezed), +// although this is not the case for standard SHA3 parameters. This implementation only supports +// squeezing a single time, subsequent squeezes may lose alignment. Future implementations +// may wish to support multiple squeeze calls, for example to support use as a PRNG. +func (d *digest) squeeze(in []byte, toSqueeze int) []byte { + // Because we read in blocks of laneSize, we need enough room to read + // an integral number of lanes + needed := toSqueeze + (laneSize-toSqueeze%laneSize)%laneSize + if cap(in)-len(in) < needed { + newIn := make([]byte, len(in), len(in)+needed) + copy(newIn, in) + in = newIn + } + out := in[len(in) : len(in)+needed] + + for len(out) > 0 { + for i := 0; i < d.rate() && len(out) > 0; i += laneSize { + binary.LittleEndian.PutUint64(out[:], d.a[i/laneSize]) + out = out[laneSize:] + } + if len(out) > 0 { + d.keccakF() + } + } + return in[:len(in)+toSqueeze] // Re-slice in case we wrote extra data. +} + +// Sum applies padding to the hash state and then squeezes out the desired nubmer of output bytes. +func (d *digest) Sum(in []byte) []byte { + // Make a copy of the original hash so that caller can keep writing and summing. + dup := *d + dup.finalize() + return dup.squeeze(in, dup.outputSize) +} + +// The NewKeccakX constructors enable initializing a hash in any of the four recommend sizes +// from the Keccak specification, all of which set capacity=2*outputSize. Note that the final +// NIST standard for SHA3 may specify different input/output lengths. +// The output size is indicated in bits but converted into bytes internally. +func NewKeccak224() hash.Hash { return &digest{outputSize: 224 / 8, capacity: 2 * 224 / 8} } +func NewKeccak256() hash.Hash { return &digest{outputSize: 256 / 8, capacity: 2 * 256 / 8} } +func NewKeccak384() hash.Hash { return &digest{outputSize: 384 / 8, capacity: 2 * 384 / 8} } +func NewKeccak512() hash.Hash { return &digest{outputSize: 512 / 8, capacity: 2 * 512 / 8} } + +func Sha3(data ...[]byte) []byte { + d := NewKeccak256() + for _, b := range data { + d.Write(b) + } + return d.Sum(nil) +} diff --git a/manager/eris-mint/evm/snative.go b/manager/eris-mint/evm/snative.go new file mode 100644 index 0000000000000000000000000000000000000000..c7e11a6edc960794cb3c80207b906e0dfc241771 --- /dev/null +++ b/manager/eris-mint/evm/snative.go @@ -0,0 +1,244 @@ +package vm + +import ( + "encoding/hex" + "fmt" + + . "github.com/tendermint/go-common" + ptypes "github.com/eris-ltd/eris-db/permission/types" +) + +//------------------------------------------------------------------------------------------------ +// Registered SNative contracts + +var PermissionsContract = "permissions_contract" + +func registerSNativeContracts() { + registeredNativeContracts[LeftPadWord256([]byte(PermissionsContract))] = permissionsContract + + /* + // we could expose these but we moved permission and args checks into the permissionsContract + // so calling them would be unsafe ... + registeredNativeContracts[LeftPadWord256([]byte("has_base"))] = has_base + registeredNativeContracts[LeftPadWord256([]byte("set_base"))] = set_base + registeredNativeContracts[LeftPadWord256([]byte("unset_base"))] = unset_base + registeredNativeContracts[LeftPadWord256([]byte("set_global"))] = set_global + registeredNativeContracts[LeftPadWord256([]byte("has_role"))] = has_role + registeredNativeContracts[LeftPadWord256([]byte("add_role"))] = add_role + registeredNativeContracts[LeftPadWord256([]byte("rm_role"))] = rm_role + */ +} + +//----------------------------------------------------------------------------- +// snative are native contracts that can access and modify an account's permissions + +type SNativeFuncDescription struct { + Name string + NArgs int + PermFlag ptypes.PermFlag + F NativeContract +} + +/* The solidity interface used to generate the abi function ids below +contract Permissions { + function has_base(address addr, int permFlag) constant returns (bool value) {} + function set_base(address addr, int permFlag, bool value) constant returns (bool val) {} + function unset_base(address addr, int permFlag) constant returns (int pf) {} + function set_global(address addr, int permFlag, bool value) constant returns (int pf) {} + function has_role(address addr, string role) constant returns (bool val) {} + function add_role(address addr, string role) constant returns (bool added) {} + function rm_role(address addr, string role) constant returns (bool removed) {} +} +*/ + +// function identifiers from the solidity abi +var PermsMap = map[string]SNativeFuncDescription{ + "054556ac": SNativeFuncDescription{"has_role", 2, ptypes.HasRole, has_role}, + "180d26f2": SNativeFuncDescription{"unset_base", 2, ptypes.UnsetBase, unset_base}, + "3a3fcc59": SNativeFuncDescription{"set_global", 2, ptypes.SetGlobal, set_global}, + "9a1c4141": SNativeFuncDescription{"add_role", 2, ptypes.AddRole, add_role}, + "9ea53314": SNativeFuncDescription{"set_base", 3, ptypes.SetBase, set_base}, + "bb37737a": SNativeFuncDescription{"has_base", 2, ptypes.HasBase, has_base}, + "ded3350a": SNativeFuncDescription{"rm_role", 2, ptypes.RmRole, rm_role}, +} + +func permissionsContract(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + if len(args) < 4 { + return nil, fmt.Errorf("permissionsContract expects at least a 4-byte function identifier") + } + + // map solidity abi function id to snative + funcIDbytes := args[:4] + args = args[4:] + funcID := hex.EncodeToString(funcIDbytes) + d, ok := PermsMap[funcID] + if !ok { + return nil, fmt.Errorf("unknown permissionsContract funcID %s", funcID) + } + + // check if we have permission to call this function + if !HasPermission(appState, caller, d.PermFlag) { + return nil, ErrInvalidPermission{caller.Address, d.Name} + } + + // ensure there are enough arguments + if len(args) != d.NArgs*32 { + return nil, fmt.Errorf("%s() takes %d arguments", d.Name) + } + + // call the function + return d.F(appState, caller, args, gas) +} + +// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?) + +func has_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, permNum := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + permN := ptypes.PermFlag(Uint64FromWord256(permNum)) // already shifted + if !ValidPermN(permN) { + return nil, ptypes.ErrInvalidPermission(permN) + } + permInt := byteFromBool(HasPermission(appState, vmAcc, permN)) + dbg.Printf("snative.hasBasePerm(0x%X, %b) = %v\n", addr.Postfix(20), permN, permInt) + return LeftPadWord256([]byte{permInt}).Bytes(), nil +} + +func set_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, permNum, perm := returnThreeArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + permN := ptypes.PermFlag(Uint64FromWord256(permNum)) + if !ValidPermN(permN) { + return nil, ptypes.ErrInvalidPermission(permN) + } + permV := !perm.IsZero() + if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { + return nil, err + } + appState.UpdateAccount(vmAcc) + dbg.Printf("snative.setBasePerm(0x%X, %b, %v)\n", addr.Postfix(20), permN, permV) + return perm.Bytes(), nil +} + +func unset_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, permNum := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + permN := ptypes.PermFlag(Uint64FromWord256(permNum)) + if !ValidPermN(permN) { + return nil, ptypes.ErrInvalidPermission(permN) + } + if err = vmAcc.Permissions.Base.Unset(permN); err != nil { + return nil, err + } + appState.UpdateAccount(vmAcc) + dbg.Printf("snative.unsetBasePerm(0x%X, %b)\n", addr.Postfix(20), permN) + return permNum.Bytes(), nil +} + +func set_global(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + permNum, perm := returnTwoArgs(args) + vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) + if vmAcc == nil { + PanicSanity("cant find the global permissions account") + } + permN := ptypes.PermFlag(Uint64FromWord256(permNum)) + if !ValidPermN(permN) { + return nil, ptypes.ErrInvalidPermission(permN) + } + permV := !perm.IsZero() + if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { + return nil, err + } + appState.UpdateAccount(vmAcc) + dbg.Printf("snative.setGlobalPerm(%b, %v)\n", permN, permV) + return perm.Bytes(), nil +} + +func has_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, role := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + roleS := string(role.Bytes()) + permInt := byteFromBool(vmAcc.Permissions.HasRole(roleS)) + dbg.Printf("snative.hasRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + return LeftPadWord256([]byte{permInt}).Bytes(), nil +} + +func add_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, role := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + roleS := string(role.Bytes()) + permInt := byteFromBool(vmAcc.Permissions.AddRole(roleS)) + appState.UpdateAccount(vmAcc) + dbg.Printf("snative.addRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + return LeftPadWord256([]byte{permInt}).Bytes(), nil +} + +func rm_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, role := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + roleS := string(role.Bytes()) + permInt := byteFromBool(vmAcc.Permissions.RmRole(roleS)) + appState.UpdateAccount(vmAcc) + dbg.Printf("snative.rmRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + return LeftPadWord256([]byte{permInt}).Bytes(), nil +} + +//------------------------------------------------------------------------------------------------ +// Errors and utility funcs + +type ErrInvalidPermission struct { + Address Word256 + SNative string +} + +func (e ErrInvalidPermission) Error() string { + return fmt.Sprintf("Account %X does not have permission snative.%s", e.Address.Postfix(20), e.SNative) +} + +// Checks if a permission flag is valid (a known base chain or snative permission) +func ValidPermN(n ptypes.PermFlag) bool { + if n > ptypes.TopPermFlag { + return false + } + return true +} + +// CONTRACT: length has already been checked +func returnTwoArgs(args []byte) (a Word256, b Word256) { + copy(a[:], args[:32]) + copy(b[:], args[32:64]) + return +} + +// CONTRACT: length has already been checked +func returnThreeArgs(args []byte) (a Word256, b Word256, c Word256) { + copy(a[:], args[:32]) + copy(b[:], args[32:64]) + copy(c[:], args[64:96]) + return +} + +func byteFromBool(b bool) byte { + if b { + return 0x1 + } + return 0x0 +} diff --git a/manager/eris-mint/evm/stack.go b/manager/eris-mint/evm/stack.go new file mode 100644 index 0000000000000000000000000000000000000000..979aba2e3ceb1a8d3f61bf4d7935094619579ed8 --- /dev/null +++ b/manager/eris-mint/evm/stack.go @@ -0,0 +1,126 @@ +package vm + +import ( + "fmt" + . "github.com/tendermint/go-common" +) + +// Not goroutine safe +type Stack struct { + data []Word256 + ptr int + + gas *int64 + err *error +} + +func NewStack(capacity int, gas *int64, err *error) *Stack { + return &Stack{ + data: make([]Word256, capacity), + ptr: 0, + gas: gas, + err: err, + } +} + +func (st *Stack) useGas(gasToUse int64) { + if *st.gas > gasToUse { + *st.gas -= gasToUse + } else { + st.setErr(ErrInsufficientGas) + } +} + +func (st *Stack) setErr(err error) { + if *st.err == nil { + *st.err = err + } +} + +func (st *Stack) Push(d Word256) { + st.useGas(GasStackOp) + if st.ptr == cap(st.data) { + st.setErr(ErrDataStackOverflow) + return + } + st.data[st.ptr] = d + st.ptr++ +} + +// currently only called after Sha3 +func (st *Stack) PushBytes(bz []byte) { + if len(bz) != 32 { + PanicSanity("Invalid bytes size: expected 32") + } + st.Push(LeftPadWord256(bz)) +} + +func (st *Stack) Push64(i int64) { + st.Push(Int64ToWord256(i)) +} + +func (st *Stack) Pop() Word256 { + st.useGas(GasStackOp) + if st.ptr == 0 { + st.setErr(ErrDataStackUnderflow) + return Zero256 + } + st.ptr-- + return st.data[st.ptr] +} + +func (st *Stack) PopBytes() []byte { + return st.Pop().Bytes() +} + +func (st *Stack) Pop64() int64 { + d := st.Pop() + return Int64FromWord256(d) +} + +func (st *Stack) Len() int { + return st.ptr +} + +func (st *Stack) Swap(n int) { + st.useGas(GasStackOp) + if st.ptr < n { + st.setErr(ErrDataStackUnderflow) + return + } + st.data[st.ptr-n], st.data[st.ptr-1] = st.data[st.ptr-1], st.data[st.ptr-n] + return +} + +func (st *Stack) Dup(n int) { + st.useGas(GasStackOp) + if st.ptr < n { + st.setErr(ErrDataStackUnderflow) + return + } + st.Push(st.data[st.ptr-n]) + return +} + +// Not an opcode, costs no gas. +func (st *Stack) Peek() Word256 { + if st.ptr == 0 { + st.setErr(ErrDataStackUnderflow) + return Zero256 + } + return st.data[st.ptr-1] +} + +func (st *Stack) Print(n int) { + fmt.Println("### stack ###") + if st.ptr > 0 { + nn := MinInt(n, st.ptr) + for j, i := 0, st.ptr-1; i > st.ptr-1-nn; i-- { + fmt.Printf("%-3d %X\n", j, st.data[i]) + j += 1 + } + } else { + fmt.Println("-- empty --") + } + fmt.Println("#############") +} diff --git a/manager/eris-mint/evm/test/fake_app_state.go b/manager/eris-mint/evm/test/fake_app_state.go new file mode 100644 index 0000000000000000000000000000000000000000..9204218fcfe1b72107d359b975cb7c49e7046af5 --- /dev/null +++ b/manager/eris-mint/evm/test/fake_app_state.go @@ -0,0 +1,79 @@ +package vm + +import ( + . "github.com/tendermint/go-common" + . "github.com/eris-ltd/eris-db/evm" + "github.com/eris-ltd/eris-db/evm/sha3" +) + +type FakeAppState struct { + accounts map[string]*Account + storage map[string]Word256 +} + +func (fas *FakeAppState) GetAccount(addr Word256) *Account { + account := fas.accounts[addr.String()] + return account +} + +func (fas *FakeAppState) UpdateAccount(account *Account) { + fas.accounts[account.Address.String()] = account +} + +func (fas *FakeAppState) RemoveAccount(account *Account) { + _, ok := fas.accounts[account.Address.String()] + if !ok { + panic(Fmt("Invalid account addr: %X", account.Address)) + } else { + // Remove account + delete(fas.accounts, account.Address.String()) + } +} + +func (fas *FakeAppState) CreateAccount(creator *Account) *Account { + addr := createAddress(creator) + account := fas.accounts[addr.String()] + if account == nil { + return &Account{ + Address: addr, + Balance: 0, + Code: nil, + Nonce: 0, + } + } else { + panic(Fmt("Invalid account addr: %X", addr)) + } +} + +func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 { + _, ok := fas.accounts[addr.String()] + if !ok { + panic(Fmt("Invalid account addr: %X", addr)) + } + + value, ok := fas.storage[addr.String()+key.String()] + if ok { + return value + } else { + return Zero256 + } +} + +func (fas *FakeAppState) SetStorage(addr Word256, key Word256, value Word256) { + _, ok := fas.accounts[addr.String()] + if !ok { + panic(Fmt("Invalid account addr: %X", addr)) + } + + fas.storage[addr.String()+key.String()] = value +} + +// Creates a 20 byte address and bumps the nonce. +func createAddress(creator *Account) Word256 { + nonce := creator.Nonce + creator.Nonce += 1 + temp := make([]byte, 32+8) + copy(temp, creator.Address[:]) + PutInt64BE(temp[32:], nonce) + return LeftPadWord256(sha3.Sha3(temp)[:20]) +} diff --git a/manager/eris-mint/evm/test/log_event_test.go b/manager/eris-mint/evm/test/log_event_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0d7b308c9be44a3c83e128ed9c02bfad6e2ac174 --- /dev/null +++ b/manager/eris-mint/evm/test/log_event_test.go @@ -0,0 +1,90 @@ +package vm + +import ( + "bytes" + "reflect" + "testing" + + . "github.com/eris-ltd/eris-db/evm" + "github.com/eris-ltd/eris-db/txs" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-events" +) + +var expectedData = []byte{0x10} +var expectedHeight int64 = 0 +var expectedTopics = []Word256{ + Int64ToWord256(1), + Int64ToWord256(2), + Int64ToWord256(3), + Int64ToWord256(4)} + +// Tests logs and events. +func TestLog4(t *testing.T) { + + st := newAppState() + // Create accounts + account1 := &Account{ + Address: LeftPadWord256(makeBytes(20)), + } + account2 := &Account{ + Address: LeftPadWord256(makeBytes(20)), + } + st.accounts[account1.Address.String()] = account1 + st.accounts[account2.Address.String()] = account2 + + ourVm := NewVM(st, newParams(), Zero256, nil) + + eventSwitch := events.NewEventSwitch() + _, err := eventSwitch.Start() + if err != nil { + t.Errorf("Failed to start eventSwitch: %v", err) + } + eventID := txs.EventStringLogEvent(account2.Address.Postfix(20)) + + doneChan := make(chan struct{}, 1) + + eventSwitch.AddListenerForEvent("test", eventID, func(event events.EventData) { + logEvent := event.(txs.EventDataLog) + // No need to test address as this event would not happen if it wasn't correct + if !reflect.DeepEqual(logEvent.Topics, expectedTopics) { + t.Errorf("Event topics are wrong. Got: %v. Expected: %v", logEvent.Topics, expectedTopics) + } + if !bytes.Equal(logEvent.Data, expectedData) { + t.Errorf("Event data is wrong. Got: %s. Expected: %s", logEvent.Data, expectedData) + } + if logEvent.Height != expectedHeight { + t.Errorf("Event block height is wrong. Got: %d. Expected: %d", logEvent.Height, expectedHeight) + } + doneChan <- struct{}{} + }) + + ourVm.SetFireable(eventSwitch) + + var gas int64 = 100000 + + mstore8 := byte(MSTORE8) + push1 := byte(PUSH1) + log4 := byte(LOG4) + stop := byte(STOP) + + code := []byte{ + push1, 16, // data value + push1, 0, // memory slot + mstore8, + push1, 4, // topic 4 + push1, 3, // topic 3 + push1, 2, // topic 2 + push1, 1, // topic 1 + push1, 1, // size of data + push1, 0, // data starts at this offset + log4, + stop, + } + + _, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + <-doneChan + if err != nil { + t.Fatal(err) + } +} diff --git a/manager/eris-mint/evm/test/vm_test.go b/manager/eris-mint/evm/test/vm_test.go new file mode 100644 index 0000000000000000000000000000000000000000..83bbed3a7d11cbff525b3fa0d72ac10a56e33bd8 --- /dev/null +++ b/manager/eris-mint/evm/test/vm_test.go @@ -0,0 +1,238 @@ +package vm + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "strings" + "testing" + "time" + + . "github.com/eris-ltd/eris-db/evm" + ptypes "github.com/eris-ltd/eris-db/permission/types" + "github.com/eris-ltd/eris-db/txs" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-events" +) + +func newAppState() *FakeAppState { + fas := &FakeAppState{ + accounts: make(map[string]*Account), + storage: make(map[string]Word256), + } + // For default permissions + fas.accounts[ptypes.GlobalPermissionsAddress256.String()] = &Account{ + Permissions: ptypes.DefaultAccountPermissions, + } + return fas +} + +func newParams() Params { + return Params{ + BlockHeight: 0, + BlockHash: Zero256, + BlockTime: 0, + GasLimit: 0, + } +} + +func makeBytes(n int) []byte { + b := make([]byte, n) + rand.Read(b) + return b +} + +// Runs a basic loop +func TestVM(t *testing.T) { + ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + + // Create accounts + account1 := &Account{ + Address: Int64ToWord256(100), + } + account2 := &Account{ + Address: Int64ToWord256(101), + } + + var gas int64 = 100000 + N := []byte{0x0f, 0x0f} + // Loop N times + code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} + code = append(code, N...) + code = append(code, []byte{0x60, 0x20, 0x51, 0x12, 0x15, 0x60, byte(0x1b + len(N)), 0x57, 0x60, 0x01, 0x60, 0x20, 0x51, 0x01, 0x60, 0x20, 0x52, 0x60, 0x05, 0x56, 0x5B}...) + start := time.Now() + output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + fmt.Printf("Output: %v Error: %v\n", output, err) + fmt.Println("Call took:", time.Since(start)) + if err != nil { + t.Fatal(err) + } +} + +func TestJumpErr(t *testing.T) { + ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + + // Create accounts + account1 := &Account{ + Address: Int64ToWord256(100), + } + account2 := &Account{ + Address: Int64ToWord256(101), + } + + var gas int64 = 100000 + code := []byte{0x60, 0x10, 0x56} // jump to position 16, a clear failure + var output []byte + var err error + ch := make(chan struct{}) + go func() { + output, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + ch <- struct{}{} + }() + tick := time.NewTicker(time.Second * 2) + select { + case <-tick.C: + t.Fatal("VM ended up in an infinite loop from bad jump dest (it took too long!)") + case <-ch: + if err == nil { + t.Fatal("Expected invalid jump dest err") + } + } +} + +// Tests the code for a subcurrency contract compiled by serpent +func TestSubcurrency(t *testing.T) { + st := newAppState() + // Create accounts + account1 := &Account{ + Address: LeftPadWord256(makeBytes(20)), + } + account2 := &Account{ + Address: LeftPadWord256(makeBytes(20)), + } + st.accounts[account1.Address.String()] = account1 + st.accounts[account2.Address.String()] = account2 + + ourVm := NewVM(st, newParams(), Zero256, nil) + + var gas int64 = 1000 + code_parts := []string{"620f42403355", + "7c0100000000000000000000000000000000000000000000000000000000", + "600035046315cf268481141561004657", + "6004356040526040515460605260206060f35b63693200ce81141561008757", + "60043560805260243560a052335460c0523360e05260a05160c05112151561008657", + "60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"} + code, _ := hex.DecodeString(strings.Join(code_parts, "")) + fmt.Printf("Code: %x\n", code) + data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005") + output, err := ourVm.Call(account1, account2, code, data, 0, &gas) + fmt.Printf("Output: %v Error: %v\n", output, err) + if err != nil { + t.Fatal(err) + } +} + +// Test sending tokens from a contract to another account +func TestSendCall(t *testing.T) { + fakeAppState := newAppState() + ourVm := NewVM(fakeAppState, newParams(), Zero256, nil) + + // Create accounts + account1 := &Account{ + Address: Int64ToWord256(100), + } + account2 := &Account{ + Address: Int64ToWord256(101), + } + account3 := &Account{ + Address: Int64ToWord256(102), + } + + // account1 will call account2 which will trigger CALL opcode to account3 + addr := account3.Address.Postfix(20) + contractCode := callContractCode(addr) + + //---------------------------------------------- + // account2 has insufficient balance, should fail + fmt.Println("Should fail with insufficient balance") + + exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------- + // give account2 sufficient balance, should pass + + account2.Balance = 100000 + exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + + //---------------------------------------------- + // insufficient gas, should fail + fmt.Println("Should fail with insufficient gas") + + account2.Balance = 100000 + exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100) + if exception == "" { + t.Fatal("Expected exception") + } +} + +// subscribes to an AccCall, runs the vm, returns the exception +func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas int64) string { + // we need to catch the event from the CALL to check for exceptions + evsw := events.NewEventSwitch() + evsw.Start() + ch := make(chan interface{}) + fmt.Printf("subscribe to %x\n", subscribeAddr) + evsw.AddListenerForEvent("test", txs.EventStringAccCall(subscribeAddr), func(msg events.EventData) { + ch <- msg + }) + evc := events.NewEventCache(evsw) + ourVm.SetFireable(evc) + go func() { + start := time.Now() + output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas) + fmt.Printf("Output: %v Error: %v\n", output, err) + fmt.Println("Call took:", time.Since(start)) + if err != nil { + ch <- err.Error() + } + evc.Flush() + }() + msg := <-ch + switch ev := msg.(type) { + case txs.EventDataTx: + return ev.Exception + case txs.EventDataCall: + return ev.Exception + case string: + return ev + } + return "" +} + +// this is code to call another contract (hardcoded as addr) +func callContractCode(addr []byte) []byte { + gas1, gas2 := byte(0x1), byte(0x1) + value := byte(0x69) + inOff, inSize := byte(0x0), byte(0x0) // no call data + retOff, retSize := byte(0x0), byte(0x20) + // this is the code we want to run (send funds to an account and return) + contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73} + contractCode = append(contractCode, addr...) + contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) + return contractCode +} + +/* + // infinite loop + code := []byte{0x5B, 0x60, 0x00, 0x56} + // mstore + code := []byte{0x60, 0x00, 0x60, 0x20} + // mstore, mload + code := []byte{0x60, 0x01, 0x60, 0x20, 0x52, 0x60, 0x20, 0x51} +*/ diff --git a/manager/eris-mint/evm/types.go b/manager/eris-mint/evm/types.go new file mode 100644 index 0000000000000000000000000000000000000000..5588d996161f338f6d6b3b8e928b74d28734d0dd --- /dev/null +++ b/manager/eris-mint/evm/types.go @@ -0,0 +1,49 @@ +package vm + +import ( + . "github.com/tendermint/go-common" + ptypes "github.com/eris-ltd/eris-db/permission/types" +) + +const ( + defaultDataStackCapacity = 10 +) + +type Account struct { + Address Word256 + Balance int64 + Code []byte + Nonce int64 + Other interface{} // For holding all other data. + + Permissions ptypes.AccountPermissions +} + +func (acc *Account) String() string { + if acc == nil { + return "nil-VMAccount" + } + return Fmt("VMAccount{%X B:%v C:%X N:%v}", + acc.Address, acc.Balance, acc.Code, acc.Nonce) +} + +type AppState interface { + + // Accounts + GetAccount(addr Word256) *Account + UpdateAccount(*Account) + RemoveAccount(*Account) + CreateAccount(*Account) *Account + + // Storage + GetStorage(Word256, Word256) Word256 + SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting. + +} + +type Params struct { + BlockHeight int64 + BlockHash Word256 + BlockTime int64 + GasLimit int64 +} diff --git a/manager/eris-mint/evm/vm.go b/manager/eris-mint/evm/vm.go new file mode 100644 index 0000000000000000000000000000000000000000..e96de42d13c83e58e22c75c82e02756bff536a92 --- /dev/null +++ b/manager/eris-mint/evm/vm.go @@ -0,0 +1,938 @@ +package vm + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/eris-ltd/eris-db/evm/sha3" + ptypes "github.com/eris-ltd/eris-db/permission/types" + "github.com/eris-ltd/eris-db/txs" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-events" +) + +var ( + ErrUnknownAddress = errors.New("Unknown address") + ErrInsufficientBalance = errors.New("Insufficient balance") + ErrInvalidJumpDest = errors.New("Invalid jump dest") + ErrInsufficientGas = errors.New("Insuffient gas") + ErrMemoryOutOfBounds = errors.New("Memory out of bounds") + ErrCodeOutOfBounds = errors.New("Code out of bounds") + ErrInputOutOfBounds = errors.New("Input out of bounds") + ErrCallStackOverflow = errors.New("Call stack overflow") + ErrCallStackUnderflow = errors.New("Call stack underflow") + ErrDataStackOverflow = errors.New("Data stack overflow") + ErrDataStackUnderflow = errors.New("Data stack underflow") + ErrInvalidContract = errors.New("Invalid contract") +) + +type ErrPermission struct { + typ string +} + +func (err ErrPermission) Error() string { + return fmt.Sprintf("Contract does not have permission to %s", err.typ) +} + +const ( + dataStackCapacity = 1024 + callStackCapacity = 100 // TODO ensure usage. + memoryCapacity = 1024 * 1024 // 1 MB +) + +type Debug bool + +var dbg Debug + +func SetDebug(d bool) { + dbg = Debug(d) +} + +func (d Debug) Printf(s string, a ...interface{}) { + if d { + fmt.Printf(s, a...) + } +} + +type VM struct { + appState AppState + params Params + origin Word256 + txid []byte + + callDepth int + + evc events.Fireable +} + +func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { + return &VM{ + appState: appState, + params: params, + origin: origin, + callDepth: 0, + txid: txid, + } +} + +// satisfies events.Eventable +func (vm *VM) SetFireable(evc events.Fireable) { + vm.evc = evc +} + +// CONTRACT: it is the duty of the contract writer to call known permissions +// we do not convey if a permission is not set +// (unlike in state/execution, where we guarantee HasPermission is called +// on known permissions and panics else) +// If the perm is not defined in the acc nor set by default in GlobalPermissions, +// prints a log warning and returns false. +func HasPermission(appState AppState, acc *Account, perm ptypes.PermFlag) bool { + v, err := acc.Permissions.Base.Get(perm) + if _, ok := err.(ptypes.ErrValueNotSet); ok { + if appState == nil { + log.Warn(Fmt("\n\n***** Unknown permission %b! ********\n\n", perm)) + return false + } + return HasPermission(nil, appState.GetAccount(ptypes.GlobalPermissionsAddress256), perm) + } + return v +} + +func (vm *VM) fireCallEvent(exception *string, output *[]byte, caller, callee *Account, input []byte, value int64, gas *int64) { + // fire the post call event (including exception if applicable) + if vm.evc != nil { + vm.evc.FireEvent(txs.EventStringAccCall(callee.Address.Postfix(20)), txs.EventDataCall{ + &txs.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas}, + vm.origin.Postfix(20), + vm.txid, + *output, + *exception, + }) + } +} + +// CONTRACT appState is aware of caller and callee, so we can just mutate them. +// CONTRACT code and input are not mutated. +// CONTRACT returned 'ret' is a new compact slice. +// value: To be transferred from caller to callee. Refunded upon error. +// gas: Available gas. No refunds for gas. +// code: May be nil, since the CALL opcode may be used to send value from contracts to accounts +func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { + + exception := new(string) + // fire the post call event (including exception if applicable) + defer vm.fireCallEvent(exception, &output, caller, callee, input, value, gas) + + if err = transfer(caller, callee, value); err != nil { + *exception = err.Error() + return + } + + if len(code) > 0 { + vm.callDepth += 1 + output, err = vm.call(caller, callee, code, input, value, gas) + vm.callDepth -= 1 + if err != nil { + *exception = err.Error() + err := transfer(callee, caller, value) + if err != nil { + // data has been corrupted in ram + PanicCrisis("Could not return value to caller") + } + } + } + + return +} + +// Try to deduct gasToUse from gasLeft. If ok return false, otherwise +// set err and return true. +func useGasNegative(gasLeft *int64, gasToUse int64, err *error) bool { + if *gasLeft >= gasToUse { + *gasLeft -= gasToUse + return false + } else if *err == nil { + *err = ErrInsufficientGas + } + return true +} + +// Just like Call() but does not transfer 'value' or modify the callDepth. +func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { + dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) + + var ( + pc int64 = 0 + stack = NewStack(dataStackCapacity, gas, &err) + memory = make([]byte, memoryCapacity) + ) + + for { + + // Use BaseOp gas. + if useGasNegative(gas, GasBaseOp, &err) { + return nil, err + } + + var op = codeGetOp(code, pc) + dbg.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) + + switch op { + + case ADD: // 0x01 + x, y := stack.Pop(), stack.Pop() + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + sum := new(big.Int).Add(xb, yb) + res := LeftPadWord256(U256(sum).Bytes()) + stack.Push(res) + dbg.Printf(" %v + %v = %v (%X)\n", xb, yb, sum, res) + + case MUL: // 0x02 + x, y := stack.Pop(), stack.Pop() + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + prod := new(big.Int).Mul(xb, yb) + res := LeftPadWord256(U256(prod).Bytes()) + stack.Push(res) + dbg.Printf(" %v * %v = %v (%X)\n", xb, yb, prod, res) + + case SUB: // 0x03 + x, y := stack.Pop(), stack.Pop() + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + diff := new(big.Int).Sub(xb, yb) + res := LeftPadWord256(U256(diff).Bytes()) + stack.Push(res) + dbg.Printf(" %v - %v = %v (%X)\n", xb, yb, diff, res) + + case DIV: // 0x04 + x, y := stack.Pop(), stack.Pop() + if y.IsZero() { + stack.Push(Zero256) + dbg.Printf(" %x / %x = %v\n", x, y, 0) + } else { + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + div := new(big.Int).Div(xb, yb) + res := LeftPadWord256(U256(div).Bytes()) + stack.Push(res) + dbg.Printf(" %v / %v = %v (%X)\n", xb, yb, div, res) + } + + case SDIV: // 0x05 + x, y := stack.Pop(), stack.Pop() + if y.IsZero() { + stack.Push(Zero256) + dbg.Printf(" %x / %x = %v\n", x, y, 0) + } else { + xb := S256(new(big.Int).SetBytes(x[:])) + yb := S256(new(big.Int).SetBytes(y[:])) + div := new(big.Int).Div(xb, yb) + res := LeftPadWord256(U256(div).Bytes()) + stack.Push(res) + dbg.Printf(" %v / %v = %v (%X)\n", xb, yb, div, res) + } + + case MOD: // 0x06 + x, y := stack.Pop(), stack.Pop() + if y.IsZero() { + stack.Push(Zero256) + dbg.Printf(" %v %% %v = %v\n", x, y, 0) + } else { + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + mod := new(big.Int).Mod(xb, yb) + res := LeftPadWord256(U256(mod).Bytes()) + stack.Push(res) + dbg.Printf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) + } + + case SMOD: // 0x07 + x, y := stack.Pop(), stack.Pop() + if y.IsZero() { + stack.Push(Zero256) + dbg.Printf(" %v %% %v = %v\n", x, y, 0) + } else { + xb := S256(new(big.Int).SetBytes(x[:])) + yb := S256(new(big.Int).SetBytes(y[:])) + mod := new(big.Int).Mod(xb, yb) + res := LeftPadWord256(U256(mod).Bytes()) + stack.Push(res) + dbg.Printf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) + } + + case ADDMOD: // 0x08 + x, y, z := stack.Pop(), stack.Pop(), stack.Pop() + if z.IsZero() { + stack.Push(Zero256) + dbg.Printf(" %v %% %v = %v\n", x, y, 0) + } else { + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + zb := new(big.Int).SetBytes(z[:]) + add := new(big.Int).Add(xb, yb) + mod := new(big.Int).Mod(add, zb) + res := LeftPadWord256(U256(mod).Bytes()) + stack.Push(res) + dbg.Printf(" %v + %v %% %v = %v (%X)\n", + xb, yb, zb, mod, res) + } + + case MULMOD: // 0x09 + x, y, z := stack.Pop(), stack.Pop(), stack.Pop() + if z.IsZero() { + stack.Push(Zero256) + dbg.Printf(" %v %% %v = %v\n", x, y, 0) + } else { + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + zb := new(big.Int).SetBytes(z[:]) + mul := new(big.Int).Mul(xb, yb) + mod := new(big.Int).Mod(mul, zb) + res := LeftPadWord256(U256(mod).Bytes()) + stack.Push(res) + dbg.Printf(" %v * %v %% %v = %v (%X)\n", + xb, yb, zb, mod, res) + } + + case EXP: // 0x0A + x, y := stack.Pop(), stack.Pop() + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + pow := new(big.Int).Exp(xb, yb, big.NewInt(0)) + res := LeftPadWord256(U256(pow).Bytes()) + stack.Push(res) + dbg.Printf(" %v ** %v = %v (%X)\n", xb, yb, pow, res) + + case SIGNEXTEND: // 0x0B + back := stack.Pop() + backb := new(big.Int).SetBytes(back[:]) + if backb.Cmp(big.NewInt(31)) < 0 { + bit := uint(backb.Uint64()*8 + 7) + num := stack.Pop() + numb := new(big.Int).SetBytes(num[:]) + mask := new(big.Int).Lsh(big.NewInt(1), bit) + mask.Sub(mask, big.NewInt(1)) + if numb.Bit(int(bit)) == 1 { + numb.Or(numb, mask.Not(mask)) + } else { + numb.Add(numb, mask) + } + res := LeftPadWord256(U256(numb).Bytes()) + dbg.Printf(" = %v (%X)", numb, res) + stack.Push(res) + } + + case LT: // 0x10 + x, y := stack.Pop(), stack.Pop() + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + if xb.Cmp(yb) < 0 { + stack.Push64(1) + dbg.Printf(" %v < %v = %v\n", xb, yb, 1) + } else { + stack.Push(Zero256) + dbg.Printf(" %v < %v = %v\n", xb, yb, 0) + } + + case GT: // 0x11 + x, y := stack.Pop(), stack.Pop() + xb := new(big.Int).SetBytes(x[:]) + yb := new(big.Int).SetBytes(y[:]) + if xb.Cmp(yb) > 0 { + stack.Push64(1) + dbg.Printf(" %v > %v = %v\n", xb, yb, 1) + } else { + stack.Push(Zero256) + dbg.Printf(" %v > %v = %v\n", xb, yb, 0) + } + + case SLT: // 0x12 + x, y := stack.Pop(), stack.Pop() + xb := S256(new(big.Int).SetBytes(x[:])) + yb := S256(new(big.Int).SetBytes(y[:])) + if xb.Cmp(yb) < 0 { + stack.Push64(1) + dbg.Printf(" %v < %v = %v\n", xb, yb, 1) + } else { + stack.Push(Zero256) + dbg.Printf(" %v < %v = %v\n", xb, yb, 0) + } + + case SGT: // 0x13 + x, y := stack.Pop(), stack.Pop() + xb := S256(new(big.Int).SetBytes(x[:])) + yb := S256(new(big.Int).SetBytes(y[:])) + if xb.Cmp(yb) > 0 { + stack.Push64(1) + dbg.Printf(" %v > %v = %v\n", xb, yb, 1) + } else { + stack.Push(Zero256) + dbg.Printf(" %v > %v = %v\n", xb, yb, 0) + } + + case EQ: // 0x14 + x, y := stack.Pop(), stack.Pop() + if bytes.Equal(x[:], y[:]) { + stack.Push64(1) + dbg.Printf(" %X == %X = %v\n", x, y, 1) + } else { + stack.Push(Zero256) + dbg.Printf(" %X == %X = %v\n", x, y, 0) + } + + case ISZERO: // 0x15 + x := stack.Pop() + if x.IsZero() { + stack.Push64(1) + dbg.Printf(" %v == 0 = %v\n", x, 1) + } else { + stack.Push(Zero256) + dbg.Printf(" %v == 0 = %v\n", x, 0) + } + + case AND: // 0x16 + x, y := stack.Pop(), stack.Pop() + z := [32]byte{} + for i := 0; i < 32; i++ { + z[i] = x[i] & y[i] + } + stack.Push(z) + dbg.Printf(" %X & %X = %X\n", x, y, z) + + case OR: // 0x17 + x, y := stack.Pop(), stack.Pop() + z := [32]byte{} + for i := 0; i < 32; i++ { + z[i] = x[i] | y[i] + } + stack.Push(z) + dbg.Printf(" %X | %X = %X\n", x, y, z) + + case XOR: // 0x18 + x, y := stack.Pop(), stack.Pop() + z := [32]byte{} + for i := 0; i < 32; i++ { + z[i] = x[i] ^ y[i] + } + stack.Push(z) + dbg.Printf(" %X ^ %X = %X\n", x, y, z) + + case NOT: // 0x19 + x := stack.Pop() + z := [32]byte{} + for i := 0; i < 32; i++ { + z[i] = ^x[i] + } + stack.Push(z) + dbg.Printf(" !%X = %X\n", x, z) + + case BYTE: // 0x1A + idx, val := stack.Pop64(), stack.Pop() + res := byte(0) + if idx < 32 { + res = val[idx] + } + stack.Push64(int64(res)) + dbg.Printf(" => 0x%X\n", res) + + case SHA3: // 0x20 + if useGasNegative(gas, GasSha3, &err) { + return nil, err + } + offset, size := stack.Pop64(), stack.Pop64() + data, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + data = sha3.Sha3(data) + stack.PushBytes(data) + dbg.Printf(" => (%v) %X\n", size, data) + + case ADDRESS: // 0x30 + stack.Push(callee.Address) + dbg.Printf(" => %X\n", callee.Address) + + case BALANCE: // 0x31 + addr := stack.Pop() + if useGasNegative(gas, GasGetAccount, &err) { + return nil, err + } + acc := vm.appState.GetAccount(addr) + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) + } + balance := acc.Balance + stack.Push64(balance) + dbg.Printf(" => %v (%X)\n", balance, addr) + + case ORIGIN: // 0x32 + stack.Push(vm.origin) + dbg.Printf(" => %X\n", vm.origin) + + case CALLER: // 0x33 + stack.Push(caller.Address) + dbg.Printf(" => %X\n", caller.Address) + + case CALLVALUE: // 0x34 + stack.Push64(value) + dbg.Printf(" => %v\n", value) + + case CALLDATALOAD: // 0x35 + offset := stack.Pop64() + data, ok := subslice(input, offset, 32) + if !ok { + return nil, firstErr(err, ErrInputOutOfBounds) + } + res := LeftPadWord256(data) + stack.Push(res) + dbg.Printf(" => 0x%X\n", res) + + case CALLDATASIZE: // 0x36 + stack.Push64(int64(len(input))) + dbg.Printf(" => %d\n", len(input)) + + case CALLDATACOPY: // 0x37 + memOff := stack.Pop64() + inputOff := stack.Pop64() + length := stack.Pop64() + data, ok := subslice(input, inputOff, length) + if !ok { + return nil, firstErr(err, ErrInputOutOfBounds) + } + dest, ok := subslice(memory, memOff, length) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, data) + dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) + + case CODESIZE: // 0x38 + l := int64(len(code)) + stack.Push64(l) + dbg.Printf(" => %d\n", l) + + case CODECOPY: // 0x39 + memOff := stack.Pop64() + codeOff := stack.Pop64() + length := stack.Pop64() + data, ok := subslice(code, codeOff, length) + if !ok { + return nil, firstErr(err, ErrCodeOutOfBounds) + } + dest, ok := subslice(memory, memOff, length) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, data) + dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + + case GASPRICE_DEPRECATED: // 0x3A + stack.Push(Zero256) + dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n") + + case EXTCODESIZE: // 0x3B + addr := stack.Pop() + if useGasNegative(gas, GasGetAccount, &err) { + return nil, err + } + acc := vm.appState.GetAccount(addr) + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) + } + code := acc.Code + l := int64(len(code)) + stack.Push64(l) + dbg.Printf(" => %d\n", l) + + case EXTCODECOPY: // 0x3C + addr := stack.Pop() + if useGasNegative(gas, GasGetAccount, &err) { + return nil, err + } + acc := vm.appState.GetAccount(addr) + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) + } + code := acc.Code + memOff := stack.Pop64() + codeOff := stack.Pop64() + length := stack.Pop64() + data, ok := subslice(code, codeOff, length) + if !ok { + return nil, firstErr(err, ErrCodeOutOfBounds) + } + dest, ok := subslice(memory, memOff, length) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, data) + dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + + case BLOCKHASH: // 0x40 + stack.Push(Zero256) + dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + + case COINBASE: // 0x41 + stack.Push(Zero256) + dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + + case TIMESTAMP: // 0x42 + time := vm.params.BlockTime + stack.Push64(int64(time)) + dbg.Printf(" => 0x%X\n", time) + + case BLOCKHEIGHT: // 0x43 + number := int64(vm.params.BlockHeight) + stack.Push64(number) + dbg.Printf(" => 0x%X\n", number) + + case GASLIMIT: // 0x45 + stack.Push64(vm.params.GasLimit) + dbg.Printf(" => %v\n", vm.params.GasLimit) + + case POP: // 0x50 + popped := stack.Pop() + dbg.Printf(" => 0x%X\n", popped) + + case MLOAD: // 0x51 + offset := stack.Pop64() + data, ok := subslice(memory, offset, 32) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + stack.Push(LeftPadWord256(data)) + dbg.Printf(" => 0x%X\n", data) + + case MSTORE: // 0x52 + offset, data := stack.Pop64(), stack.Pop() + dest, ok := subslice(memory, offset, 32) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, data[:]) + dbg.Printf(" => 0x%X\n", data) + + case MSTORE8: // 0x53 + offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) + if len(memory) <= int(offset) { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + memory[offset] = val + dbg.Printf(" => [%v] 0x%X\n", offset, val) + + case SLOAD: // 0x54 + loc := stack.Pop() + data := vm.appState.GetStorage(callee.Address, loc) + stack.Push(data) + dbg.Printf(" {0x%X : 0x%X}\n", loc, data) + + case SSTORE: // 0x55 + loc, data := stack.Pop(), stack.Pop() + if useGasNegative(gas, GasStorageUpdate, &err) { + return nil, err + } + vm.appState.SetStorage(callee.Address, loc, data) + dbg.Printf(" {0x%X : 0x%X}\n", loc, data) + + case JUMP: // 0x56 + if err = jump(code, stack.Pop64(), &pc); err != nil { + return nil, err + } + continue + + case JUMPI: // 0x57 + pos, cond := stack.Pop64(), stack.Pop() + if !cond.IsZero() { + if err = jump(code, pos, &pc); err != nil { + return nil, err + } + continue + } + dbg.Printf(" ~> false\n") + + case PC: // 0x58 + stack.Push64(pc) + + case MSIZE: // 0x59 + stack.Push64(int64(len(memory))) + + case GAS: // 0x5A + stack.Push64(*gas) + dbg.Printf(" => %X\n", *gas) + + case JUMPDEST: // 0x5B + dbg.Printf("\n") + // Do nothing + + case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: + a := int64(op - PUSH1 + 1) + codeSegment, ok := subslice(code, pc+1, a) + if !ok { + return nil, firstErr(err, ErrCodeOutOfBounds) + } + res := LeftPadWord256(codeSegment) + stack.Push(res) + pc += a + dbg.Printf(" => 0x%X\n", res) + //stack.Print(10) + + case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: + n := int(op - DUP1 + 1) + stack.Dup(n) + dbg.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) + + case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: + n := int(op - SWAP1 + 2) + stack.Swap(n) + dbg.Printf(" => [%d] %X\n", n, stack.Peek()) + //stack.Print(10) + + case LOG0, LOG1, LOG2, LOG3, LOG4: + n := int(op - LOG0) + topics := make([]Word256, n) + offset, size := stack.Pop64(), stack.Pop64() + for i := 0; i < n; i++ { + topics[i] = stack.Pop() + } + data, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + data = copyslice(data) + if vm.evc != nil { + eventID := txs.EventStringLogEvent(callee.Address.Postfix(20)) + fmt.Printf("eventID: %s\n", eventID) + log := txs.EventDataLog{ + callee.Address, + topics, + data, + vm.params.BlockHeight, + } + vm.evc.FireEvent(eventID, log) + } + dbg.Printf(" => T:%X D:%X\n", topics, data) + + case CREATE: // 0xF0 + if !HasPermission(vm.appState, callee, ptypes.CreateContract) { + return nil, ErrPermission{"create_contract"} + } + contractValue := stack.Pop64() + offset, size := stack.Pop64(), stack.Pop64() + input, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + + // Check balance + if callee.Balance < contractValue { + return nil, firstErr(err, ErrInsufficientBalance) + } + + // TODO charge for gas to create account _ the code length * GasCreateByte + + newAccount := vm.appState.CreateAccount(callee) + // Run the input to get the contract code. + // NOTE: no need to copy 'input' as per Call contract. + ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas) + if err_ != nil { + stack.Push(Zero256) + } else { + newAccount.Code = ret // Set the code (ret need not be copied as per Call contract) + stack.Push(newAccount.Address) + } + + case CALL, CALLCODE: // 0xF1, 0xF2 + if !HasPermission(vm.appState, callee, ptypes.Call) { + return nil, ErrPermission{"call"} + } + gasLimit := stack.Pop64() + addr, value := stack.Pop(), stack.Pop64() + inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs + retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs + dbg.Printf(" => %X\n", addr) + + // Get the arguments from the memory + args, ok := subslice(memory, inOffset, inSize) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + args = copyslice(args) + + // Ensure that gasLimit is reasonable + if *gas < gasLimit { + return nil, firstErr(err, ErrInsufficientGas) + } else { + *gas -= gasLimit + // NOTE: we will return any used gas later. + } + + // Begin execution + var ret []byte + var err error + if nativeContract := registeredNativeContracts[addr]; nativeContract != nil { + // Native contract + ret, err = nativeContract(vm.appState, callee, args, &gasLimit) + + // for now we fire the Call event. maybe later we'll fire more particulars + var exception string + if err != nil { + exception = err.Error() + } + // NOTE: these fire call events and not particular events for eg name reg or permissions + vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, gas) + } else { + // EVM contract + if useGasNegative(gas, GasGetAccount, &err) { + return nil, err + } + acc := vm.appState.GetAccount(addr) + // since CALL is used also for sending funds, + // acc may not exist yet. This is an error for + // CALLCODE, but not for CALL, though I don't think + // ethereum actually cares + if op == CALLCODE { + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) + } + ret, err = vm.Call(callee, callee, acc.Code, args, value, gas) + } else { + if acc == nil { + // nil account means we're sending funds to a new account + if !HasPermission(vm.appState, caller, ptypes.CreateAccount) { + return nil, ErrPermission{"create_account"} + } + acc = &Account{Address: addr} + vm.appState.UpdateAccount(acc) + // send funds to new account + ret, err = vm.Call(callee, acc, acc.Code, args, value, gas) + } else { + // call standard contract + ret, err = vm.Call(callee, acc, acc.Code, args, value, gas) + } + } + } + + // Push result + if err != nil { + dbg.Printf("error on call: %s\n", err.Error()) + stack.Push(Zero256) + } else { + stack.Push(One256) + dest, ok := subslice(memory, retOffset, retSize) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, ret) + } + + // Handle remaining gas. + *gas += gasLimit + + dbg.Printf("resume %X (%v)\n", callee.Address, gas) + + case RETURN: // 0xF3 + offset, size := stack.Pop64(), stack.Pop64() + ret, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) + output = copyslice(ret) + return output, nil + + case SUICIDE: // 0xFF + addr := stack.Pop() + if useGasNegative(gas, GasGetAccount, &err) { + return nil, err + } + // TODO if the receiver is , then make it the fee. (?) + // TODO: create account if doesn't exist (no reason not to) + receiver := vm.appState.GetAccount(addr) + if receiver == nil { + return nil, firstErr(err, ErrUnknownAddress) + } + balance := callee.Balance + receiver.Balance += balance + vm.appState.UpdateAccount(receiver) + vm.appState.RemoveAccount(callee) + dbg.Printf(" => (%X) %v\n", addr[:4], balance) + fallthrough + + case STOP: // 0x00 + return nil, nil + + default: + dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) + return nil, fmt.Errorf("Invalid opcode %X", op) + } + + pc++ + + } +} + +func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { + size := int64(len(data)) + if size < offset { + return nil, false + } else if size < offset+length { + ret, ok = data[offset:], true + ret = RightPadBytes(ret, 32) + } else { + ret, ok = data[offset:offset+length], true + } + return +} + +func copyslice(src []byte) (dest []byte) { + dest = make([]byte, len(src)) + copy(dest, src) + return dest +} + +func rightMostBytes(data []byte, n int) []byte { + size := MinInt(len(data), n) + offset := len(data) - size + return data[offset:] +} + +func codeGetOp(code []byte, n int64) OpCode { + if int64(len(code)) <= n { + return OpCode(0) // stop + } else { + return OpCode(code[n]) + } +} + +func jump(code []byte, to int64, pc *int64) (err error) { + dest := codeGetOp(code, to) + if dest != JUMPDEST { + dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest) + return ErrInvalidJumpDest + } + dbg.Printf(" ~> %v\n", to) + *pc = to + return nil +} + +func firstErr(errA, errB error) error { + if errA != nil { + return errA + } else { + return errB + } +} + +func transfer(from, to *Account, amount int64) error { + if from.Balance < amount { + return ErrInsufficientBalance + } else { + from.Balance -= amount + to.Balance += amount + return nil + } +} diff --git a/manager/eris-mint/state/block_cache.go b/manager/eris-mint/state/block_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..1cefa6a3b18b996800a2d89e0670675ee14dd1d2 --- /dev/null +++ b/manager/eris-mint/state/block_cache.go @@ -0,0 +1,288 @@ +package state + +import ( + "bytes" + "sort" + + . "github.com/tendermint/go-common" + dbm "github.com/tendermint/go-db" + "github.com/tendermint/go-merkle" + + acm "github.com/eris-ltd/eris-db/account" + "github.com/eris-ltd/eris-db/txs" +) + +func makeStorage(db dbm.DB, root []byte) merkle.Tree { + storage := merkle.NewIAVLTree(1024, db) + storage.Load(root) + return storage +} + +// The blockcache helps prevent unnecessary IAVLTree updates and garbage generation. +type BlockCache struct { + db dbm.DB + backend *State + accounts map[string]accountInfo + storages map[Tuple256]storageInfo + names map[string]nameInfo +} + +func NewBlockCache(backend *State) *BlockCache { + return &BlockCache{ + db: backend.DB, + backend: backend, + accounts: make(map[string]accountInfo), + storages: make(map[Tuple256]storageInfo), + names: make(map[string]nameInfo), + } +} + +func (cache *BlockCache) State() *State { + return cache.backend +} + +//------------------------------------- +// BlockCache.account + +func (cache *BlockCache) GetAccount(addr []byte) *acm.Account { + acc, _, removed, _ := cache.accounts[string(addr)].unpack() + if removed { + return nil + } else if acc != nil { + return acc + } else { + acc = cache.backend.GetAccount(addr) + cache.accounts[string(addr)] = accountInfo{acc, nil, false, false} + return acc + } +} + +func (cache *BlockCache) UpdateAccount(acc *acm.Account) { + addr := acc.Address + _, storage, removed, _ := cache.accounts[string(addr)].unpack() + if removed { + PanicSanity("UpdateAccount on a removed account") + } + cache.accounts[string(addr)] = accountInfo{acc, storage, false, true} +} + +func (cache *BlockCache) RemoveAccount(addr []byte) { + _, _, removed, _ := cache.accounts[string(addr)].unpack() + if removed { + PanicSanity("RemoveAccount on a removed account") + } + cache.accounts[string(addr)] = accountInfo{nil, nil, true, false} +} + +// BlockCache.account +//------------------------------------- +// BlockCache.storage + +func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) { + // Check cache + info, ok := cache.storages[Tuple256{addr, key}] + if ok { + return info.value + } + + // Get or load storage + acc, storage, removed, dirty := cache.accounts[string(addr.Postfix(20))].unpack() + if removed { + PanicSanity("GetStorage() on removed account") + } + if acc != nil && storage == nil { + storage = makeStorage(cache.db, acc.StorageRoot) + cache.accounts[string(addr.Postfix(20))] = accountInfo{acc, storage, false, dirty} + } else if acc == nil { + return Zero256 + } + + // Load and set cache + _, val_, _ := storage.Get(key.Bytes()) + value = Zero256 + if val_ != nil { + value = LeftPadWord256(val_) + } + cache.storages[Tuple256{addr, key}] = storageInfo{value, false} + return value +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { + _, _, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack() + if removed { + PanicSanity("SetStorage() on a removed account") + } + cache.storages[Tuple256{addr, key}] = storageInfo{value, true} +} + +// BlockCache.storage +//------------------------------------- +// BlockCache.names + +func (cache *BlockCache) GetNameRegEntry(name string) *txs.NameRegEntry { + entry, removed, _ := cache.names[name].unpack() + if removed { + return nil + } else if entry != nil { + return entry + } else { + entry = cache.backend.GetNameRegEntry(name) + cache.names[name] = nameInfo{entry, false, false} + return entry + } +} + +func (cache *BlockCache) UpdateNameRegEntry(entry *txs.NameRegEntry) { + name := entry.Name + cache.names[name] = nameInfo{entry, false, true} +} + +func (cache *BlockCache) RemoveNameRegEntry(name string) { + _, removed, _ := cache.names[name].unpack() + if removed { + PanicSanity("RemoveNameRegEntry on a removed entry") + } + cache.names[name] = nameInfo{nil, true, false} +} + +// BlockCache.names +//------------------------------------- + +// CONTRACT the updates are in deterministic order. +func (cache *BlockCache) Sync() { + + // Determine order for storage updates + // The address comes first so it'll be grouped. + storageKeys := make([]Tuple256, 0, len(cache.storages)) + for keyTuple := range cache.storages { + storageKeys = append(storageKeys, keyTuple) + } + Tuple256Slice(storageKeys).Sort() + + // Update storage for all account/key. + // Later we'll iterate over all the users and save storage + update storage root. + var ( + curAddr Word256 + curAcc *acm.Account + curAccRemoved bool + curStorage merkle.Tree + ) + for _, storageKey := range storageKeys { + addr, key := Tuple256Split(storageKey) + if addr != curAddr || curAcc == nil { + acc, storage, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack() + if !removed && storage == nil { + storage = makeStorage(cache.db, acc.StorageRoot) + } + curAddr = addr + curAcc = acc + curAccRemoved = removed + curStorage = storage + } + if curAccRemoved { + continue + } + value, dirty := cache.storages[storageKey].unpack() + if !dirty { + continue + } + if value.IsZero() { + curStorage.Remove(key.Bytes()) + } else { + curStorage.Set(key.Bytes(), value.Bytes()) + cache.accounts[string(addr.Postfix(20))] = accountInfo{curAcc, curStorage, false, true} + } + } + + // Determine order for accounts + addrStrs := []string{} + for addrStr := range cache.accounts { + addrStrs = append(addrStrs, addrStr) + } + sort.Strings(addrStrs) + + // Update or delete accounts. + for _, addrStr := range addrStrs { + acc, storage, removed, dirty := cache.accounts[addrStr].unpack() + if removed { + removed := cache.backend.RemoveAccount([]byte(addrStr)) + if !removed { + PanicCrisis(Fmt("Could not remove account to be removed: %X", acc.Address)) + } + } else { + if acc == nil { + continue + } + if storage != nil { + newStorageRoot := storage.Save() + if !bytes.Equal(newStorageRoot, acc.StorageRoot) { + acc.StorageRoot = newStorageRoot + dirty = true + } + } + if dirty { + cache.backend.UpdateAccount(acc) + } + } + } + + // Determine order for names + // note names may be of any length less than some limit + nameStrs := []string{} + for nameStr := range cache.names { + nameStrs = append(nameStrs, nameStr) + } + sort.Strings(nameStrs) + + // Update or delete names. + for _, nameStr := range nameStrs { + entry, removed, dirty := cache.names[nameStr].unpack() + if removed { + removed := cache.backend.RemoveNameRegEntry(nameStr) + if !removed { + PanicCrisis(Fmt("Could not remove namereg entry to be removed: %s", nameStr)) + } + } else { + if entry == nil { + continue + } + if dirty { + cache.backend.UpdateNameRegEntry(entry) + } + } + } + +} + +//----------------------------------------------------------------------------- + +type accountInfo struct { + account *acm.Account + storage merkle.Tree + removed bool + dirty bool +} + +func (accInfo accountInfo) unpack() (*acm.Account, merkle.Tree, bool, bool) { + return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty +} + +type storageInfo struct { + value Word256 + dirty bool +} + +func (stjInfo storageInfo) unpack() (Word256, bool) { + return stjInfo.value, stjInfo.dirty +} + +type nameInfo struct { + name *txs.NameRegEntry + removed bool + dirty bool +} + +func (nInfo nameInfo) unpack() (*txs.NameRegEntry, bool, bool) { + return nInfo.name, nInfo.removed, nInfo.dirty +} diff --git a/manager/eris-mint/state/common.go b/manager/eris-mint/state/common.go new file mode 100644 index 0000000000000000000000000000000000000000..1f572a2108044082d9be6fbb0229af6a8418286f --- /dev/null +++ b/manager/eris-mint/state/common.go @@ -0,0 +1,18 @@ +package state + +import ( + . "github.com/tendermint/go-common" + acm "github.com/eris-ltd/eris-db/account" + "github.com/eris-ltd/eris-db/evm" +) + +type AccountGetter interface { + GetAccount(addr []byte) *acm.Account +} + +type VMAccountState interface { + GetAccount(addr Word256) *vm.Account + UpdateAccount(acc *vm.Account) + RemoveAccount(acc *vm.Account) + CreateAccount(creator *vm.Account) *vm.Account +} diff --git a/manager/eris-mint/state/execution.go b/manager/eris-mint/state/execution.go new file mode 100644 index 0000000000000000000000000000000000000000..7247dd5057a7e5d2d5e43afcb3e9b66eb346cec6 --- /dev/null +++ b/manager/eris-mint/state/execution.go @@ -0,0 +1,1005 @@ +package state + +import ( + "bytes" + "errors" + "fmt" + + . "github.com/tendermint/go-common" + "github.com/tendermint/go-events" + + acm "github.com/eris-ltd/eris-db/account" + "github.com/eris-ltd/eris-db/evm" + ptypes "github.com/eris-ltd/eris-db/permission/types" // for GlobalPermissionAddress ... + + "github.com/eris-ltd/eris-db/txs" +) + +// ExecBlock stuff is now taken care of by the consensus engine. +// But we leave here for now for reference when we have to do validator updates + +/* + +// NOTE: If an error occurs during block execution, state will be left +// at an invalid state. Copy the state before calling ExecBlock! +func ExecBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { + err := execBlock(s, block, blockPartsHeader) + if err != nil { + return err + } + // State.Hash should match block.StateHash + stateHash := s.Hash() + if !bytes.Equal(stateHash, block.StateHash) { + return errors.New(Fmt("Invalid state hash. Expected %X, got %X", + stateHash, block.StateHash)) + } + return nil +} + +// executes transactions of a block, does not check block.StateHash +// NOTE: If an error occurs during block execution, state will be left +// at an invalid state. Copy the state before calling execBlock! +func execBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { + // Basic block validation. + err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime) + if err != nil { + return err + } + + // Validate block LastValidation. + if block.Height == 1 { + if len(block.LastValidation.Precommits) != 0 { + return errors.New("Block at height 1 (first block) should have no LastValidation precommits") + } + } else { + if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() { + return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", + s.LastBondedValidators.Size(), len(block.LastValidation.Precommits))) + } + err := s.LastBondedValidators.VerifyValidation( + s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation) + if err != nil { + return err + } + } + + // Update Validator.LastCommitHeight as necessary. + for i, precommit := range block.LastValidation.Precommits { + if precommit == nil { + continue + } + _, val := s.LastBondedValidators.GetByIndex(i) + if val == nil { + PanicCrisis(Fmt("Failed to fetch validator at index %v", i)) + } + if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { + val_.LastCommitHeight = block.Height - 1 + updated := s.BondedValidators.Update(val_) + if !updated { + PanicCrisis("Failed to update bonded validator LastCommitHeight") + } + } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil { + val_.LastCommitHeight = block.Height - 1 + updated := s.UnbondingValidators.Update(val_) + if !updated { + PanicCrisis("Failed to update unbonding validator LastCommitHeight") + } + } else { + PanicCrisis("Could not find validator") + } + } + + // Remember LastBondedValidators + s.LastBondedValidators = s.BondedValidators.Copy() + + // Create BlockCache to cache changes to state. + blockCache := NewBlockCache(s) + + // Execute each tx + for _, tx := range block.Data.Txs { + err := ExecTx(blockCache, tx, true, s.evc) + if err != nil { + return InvalidTxError{tx, err} + } + } + + // Now sync the BlockCache to the backend. + blockCache.Sync() + + // If any unbonding periods are over, + // reward account with bonded coins. + toRelease := []*txs.Validator{} + s.UnbondingValidators.Iterate(func(index int, val *txs.Validator) bool { + if val.UnbondHeight+unbondingPeriodBlocks < block.Height { + toRelease = append(toRelease, val) + } + return false + }) + for _, val := range toRelease { + s.releaseValidator(val) + } + + // If any validators haven't signed in a while, + // unbond them, they have timed out. + toTimeout := []*txs.Validator{} + s.BondedValidators.Iterate(func(index int, val *txs.Validator) bool { + lastActivityHeight := MaxInt(val.BondHeight, val.LastCommitHeight) + if lastActivityHeight+validatorTimeoutBlocks < block.Height { + log.Notice("Validator timeout", "validator", val, "height", block.Height) + toTimeout = append(toTimeout, val) + } + return false + }) + for _, val := range toTimeout { + s.unbondValidator(val) + } + + // Increment validator AccumPowers + s.BondedValidators.IncrementAccum(1) + s.LastBlockHeight = block.Height + s.LastBlockHash = block.Hash() + s.LastBlockParts = blockPartsHeader + s.LastBlockTime = block.Time + return nil +} +*/ + +// The accounts from the TxInputs must either already have +// acm.PubKey.(type) != nil, (it must be known), +// or it must be specified in the TxInput. If redeclared, +// the TxInput is modified and input.PubKey set to nil. +func getInputs(state AccountGetter, ins []*txs.TxInput) (map[string]*acm.Account, error) { + accounts := map[string]*acm.Account{} + for _, in := range ins { + // Account shouldn't be duplicated + if _, ok := accounts[string(in.Address)]; ok { + return nil, txs.ErrTxDuplicateAddress + } + acc := state.GetAccount(in.Address) + if acc == nil { + return nil, txs.ErrTxInvalidAddress + } + // PubKey should be present in either "account" or "in" + if err := checkInputPubKey(acc, in); err != nil { + return nil, err + } + accounts[string(in.Address)] = acc + } + return accounts, nil +} + +func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, outs []*txs.TxOutput) (map[string]*acm.Account, error) { + if accounts == nil { + accounts = make(map[string]*acm.Account) + } + + // we should err if an account is being created but the inputs don't have permission + var checkedCreatePerms bool + for _, out := range outs { + // Account shouldn't be duplicated + if _, ok := accounts[string(out.Address)]; ok { + return nil, txs.ErrTxDuplicateAddress + } + acc := state.GetAccount(out.Address) + // output account may be nil (new) + if acc == nil { + if !checkedCreatePerms { + if !hasCreateAccountPermission(state, accounts) { + return nil, fmt.Errorf("At least one input does not have permission to create accounts") + } + checkedCreatePerms = true + } + acc = &acm.Account{ + Address: out.Address, + PubKey: nil, + Sequence: 0, + Balance: 0, + Permissions: ptypes.ZeroAccountPermissions, + } + } + accounts[string(out.Address)] = acc + } + return accounts, nil +} + +func checkInputPubKey(acc *acm.Account, in *txs.TxInput) error { + if acc.PubKey == nil { + if in.PubKey == nil { + return txs.ErrTxUnknownPubKey + } + if !bytes.Equal(in.PubKey.Address(), acc.Address) { + return txs.ErrTxInvalidPubKey + } + acc.PubKey = in.PubKey + } else { + in.PubKey = nil + } + return nil +} + +func validateInputs(accounts map[string]*acm.Account, signBytes []byte, ins []*txs.TxInput) (total int64, err error) { + for _, in := range ins { + acc := accounts[string(in.Address)] + if acc == nil { + PanicSanity("validateInputs() expects account in accounts") + } + err = validateInput(acc, signBytes, in) + if err != nil { + return + } + // Good. Add amount to total + total += in.Amount + } + return total, nil +} + +func validateInput(acc *acm.Account, signBytes []byte, in *txs.TxInput) (err error) { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check signatures + if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { + return txs.ErrTxInvalidSignature + } + // Check sequences + if acc.Sequence+1 != in.Sequence { + return txs.ErrTxInvalidSequence{ + Got: in.Sequence, + Expected: acc.Sequence + 1, + } + } + // Check amount + if acc.Balance < in.Amount { + return txs.ErrTxInsufficientFunds + } + return nil +} + +func validateOutputs(outs []*txs.TxOutput) (total int64, err error) { + for _, out := range outs { + // Check TxOutput basic + if err := out.ValidateBasic(); err != nil { + return 0, err + } + // Good. Add amount to total + total += out.Amount + } + return total, nil +} + +func adjustByInputs(accounts map[string]*acm.Account, ins []*txs.TxInput) { + for _, in := range ins { + acc := accounts[string(in.Address)] + if acc == nil { + PanicSanity("adjustByInputs() expects account in accounts") + } + if acc.Balance < in.Amount { + PanicSanity("adjustByInputs() expects sufficient funds") + } + acc.Balance -= in.Amount + acc.Sequence += 1 + } +} + +func adjustByOutputs(accounts map[string]*acm.Account, outs []*txs.TxOutput) { + for _, out := range outs { + acc := accounts[string(out.Address)] + if acc == nil { + PanicSanity("adjustByOutputs() expects account in accounts") + } + acc.Balance += out.Amount + } +} + +// If the tx is invalid, an error will be returned. +// Unlike ExecBlock(), state will not be altered. +func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable) (err error) { + + // TODO: do something with fees + fees := int64(0) + _s := blockCache.State() // hack to access validators and block height + + // Exec tx + switch tx := tx.(type) { + case *txs.SendTx: + accounts, err := getInputs(blockCache, tx.Inputs) + if err != nil { + return err + } + + // ensure all inputs have send permissions + if !hasSendPermission(blockCache, accounts) { + return fmt.Errorf("At least one input lacks permission for SendTx") + } + + // add outputs to accounts map + // if any outputs don't exist, all inputs must have CreateAccount perm + accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs) + if err != nil { + return err + } + + signBytes := acm.SignBytes(_s.ChainID, tx) + inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) + if err != nil { + return err + } + outTotal, err := validateOutputs(tx.Outputs) + if err != nil { + return err + } + if outTotal > inTotal { + return txs.ErrTxInsufficientFunds + } + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + adjustByInputs(accounts, tx.Inputs) + adjustByOutputs(accounts, tx.Outputs) + for _, acc := range accounts { + blockCache.UpdateAccount(acc) + } + + // if the evc is nil, nothing will happen + if evc != nil { + for _, i := range tx.Inputs { + evc.FireEvent(txs.EventStringAccInput(i.Address), txs.EventDataTx{tx, nil, ""}) + } + + for _, o := range tx.Outputs { + evc.FireEvent(txs.EventStringAccOutput(o.Address), txs.EventDataTx{tx, nil, ""}) + } + } + return nil + + case *txs.CallTx: + var inAcc, outAcc *acm.Account + + // Validate input + inAcc = blockCache.GetAccount(tx.Input.Address) + if inAcc == nil { + log.Info(Fmt("Can't find in account %X", tx.Input.Address)) + return txs.ErrTxInvalidAddress + } + + createContract := len(tx.Address) == 0 + if createContract { + if !hasCreateContractPermission(blockCache, inAcc) { + return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address) + } + } else { + if !hasCallPermission(blockCache, inAcc) { + return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) + } + } + + // pubKey should be present in either "inAcc" or "tx.Input" + if err := checkInputPubKey(inAcc, tx.Input); err != nil { + log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address)) + return err + } + signBytes := acm.SignBytes(_s.ChainID, tx) + err := validateInput(inAcc, signBytes, tx.Input) + if err != nil { + log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err)) + return err + } + if tx.Input.Amount < tx.Fee { + log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) + return txs.ErrTxInsufficientFunds + } + + if !createContract { + // Validate output + if len(tx.Address) != 20 { + log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address)) + return txs.ErrTxInvalidAddress + } + // check if its a native contract + if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) { + return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)") + } + + // Output account may be nil if we are still in mempool and contract was created in same block as this tx + // but that's fine, because the account will be created properly when the create tx runs in the block + // and then this won't return nil. otherwise, we take their fee + outAcc = blockCache.GetAccount(tx.Address) + } + + log.Info(Fmt("Out account: %v", outAcc)) + + // Good! + value := tx.Input.Amount - tx.Fee + inAcc.Sequence += 1 + inAcc.Balance -= tx.Fee + blockCache.UpdateAccount(inAcc) + + // The logic in runCall MUST NOT return. + if runCall { + + // VM call variables + var ( + gas int64 = tx.GasLimit + err error = nil + caller *vm.Account = toVMAccount(inAcc) + callee *vm.Account = nil // initialized below + code []byte = nil + ret []byte = nil + txCache = NewTxCache(blockCache) + params = vm.Params{ + BlockHeight: int64(_s.LastBlockHeight), + BlockHash: LeftPadWord256(_s.LastBlockHash), + BlockTime: _s.LastBlockTime.Unix(), + GasLimit: _s.GetGasLimit(), + } + ) + + if !createContract && (outAcc == nil || len(outAcc.Code) == 0) { + // if you call an account that doesn't exist + // or an account with no code then we take fees (sorry pal) + // NOTE: it's fine to create a contract and call it within one + // block (nonce will prevent re-ordering of those txs) + // but to create with one contract and call with another + // you have to wait a block to avoid a re-ordering attack + // that will take your fees + if outAcc == nil { + log.Info(Fmt("%X tries to call %X but it does not exist.", + inAcc.Address, tx.Address)) + } else { + log.Info(Fmt("%X tries to call %X but code is blank.", + inAcc.Address, tx.Address)) + } + err = txs.ErrTxInvalidAddress + goto CALL_COMPLETE + } + + // get or create callee + if createContract { + // We already checked for permission + callee = txCache.CreateAccount(caller) + log.Info(Fmt("Created new contract %X", callee.Address)) + code = tx.Data + } else { + callee = toVMAccount(outAcc) + log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code)) + code = callee.Code + } + log.Info(Fmt("Code for this contract: %X", code)) + + // Run VM call and sync txCache to blockCache. + { // Capture scope for goto. + // Write caller/callee to txCache. + txCache.UpdateAccount(caller) + txCache.UpdateAccount(callee) + vmach := vm.NewVM(txCache, params, caller.Address, txs.TxID(_s.ChainID, tx)) + vmach.SetFireable(evc) + // NOTE: Call() transfers the value from caller to callee iff call succeeds. + ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas) + if err != nil { + // Failure. Charge the gas fee. The 'value' was otherwise not transferred. + log.Info(Fmt("Error on execution: %v", err)) + goto CALL_COMPLETE + } + + log.Info("Successful execution") + if createContract { + callee.Code = ret + } + txCache.Sync() + } + + CALL_COMPLETE: // err may or may not be nil. + + // Create a receipt from the ret and whether errored. + log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err) + + // Fire Events for sender and receiver + // a separate event will be fired from vm for each additional call + if evc != nil { + exception := "" + if err != nil { + exception = err.Error() + } + evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, ret, exception}) + evc.FireEvent(txs.EventStringAccOutput(tx.Address), txs.EventDataTx{tx, ret, exception}) + } + } else { + // The mempool does not call txs until + // the proposer determines the order of txs. + // So mempool will skip the actual .Call(), + // and only deduct from the caller's balance. + inAcc.Balance -= value + if createContract { + inAcc.Sequence += 1 + } + blockCache.UpdateAccount(inAcc) + } + + return nil + + case *txs.NameTx: + var inAcc *acm.Account + + // Validate input + inAcc = blockCache.GetAccount(tx.Input.Address) + if inAcc == nil { + log.Info(Fmt("Can't find in account %X", tx.Input.Address)) + return txs.ErrTxInvalidAddress + } + // check permission + if !hasNamePermission(blockCache, inAcc) { + return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address) + } + // pubKey should be present in either "inAcc" or "tx.Input" + if err := checkInputPubKey(inAcc, tx.Input); err != nil { + log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address)) + return err + } + signBytes := acm.SignBytes(_s.ChainID, tx) + err := validateInput(inAcc, signBytes, tx.Input) + if err != nil { + log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err)) + return err + } + // fee is in addition to the amount which is used to determine the TTL + if tx.Input.Amount < tx.Fee { + log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) + return txs.ErrTxInsufficientFunds + } + + // validate the input strings + if err := tx.ValidateStrings(); err != nil { + return err + } + + value := tx.Input.Amount - tx.Fee + + // let's say cost of a name for one block is len(data) + 32 + costPerBlock := txs.NameCostPerBlock(txs.NameBaseCost(tx.Name, tx.Data)) + expiresIn := int(value / costPerBlock) + lastBlockHeight := _s.LastBlockHeight + + log.Info("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight) + + // check if the name exists + entry := blockCache.GetNameRegEntry(tx.Name) + + if entry != nil { + var expired bool + // if the entry already exists, and hasn't expired, we must be owner + if entry.Expires > lastBlockHeight { + // ensure we are owner + if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { + log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) + return txs.ErrTxPermissionDenied + } + } else { + expired = true + } + + // no value and empty data means delete the entry + if value == 0 && len(tx.Data) == 0 { + // maybe we reward you for telling us we can delete this crap + // (owners if not expired, anyone if expired) + log.Info("Removing namereg entry", "name", entry.Name) + blockCache.RemoveNameRegEntry(entry.Name) + } else { + // update the entry by bumping the expiry + // and changing the data + if expired { + if expiresIn < txs.MinNameRegistrationPeriod { + return errors.New(Fmt("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)) + } + entry.Expires = lastBlockHeight + expiresIn + entry.Owner = tx.Input.Address + log.Info("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner) + } else { + // since the size of the data may have changed + // we use the total amount of "credit" + oldCredit := int64(entry.Expires-lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data) + credit := oldCredit + value + expiresIn = int(credit / costPerBlock) + if expiresIn < txs.MinNameRegistrationPeriod { + return errors.New(Fmt("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)) + } + entry.Expires = lastBlockHeight + expiresIn + log.Info("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit) + } + entry.Data = tx.Data + blockCache.UpdateNameRegEntry(entry) + } + } else { + if expiresIn < txs.MinNameRegistrationPeriod { + return errors.New(Fmt("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)) + } + // entry does not exist, so create it + entry = &txs.NameRegEntry{ + Name: tx.Name, + Owner: tx.Input.Address, + Data: tx.Data, + Expires: lastBlockHeight + expiresIn, + } + log.Info("Creating namereg entry", "name", entry.Name, "expiresIn", expiresIn) + blockCache.UpdateNameRegEntry(entry) + } + + // TODO: something with the value sent? + + // Good! + inAcc.Sequence += 1 + inAcc.Balance -= value + blockCache.UpdateAccount(inAcc) + + // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? + + if evc != nil { + evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, nil, ""}) + evc.FireEvent(txs.EventStringNameReg(tx.Name), txs.EventDataTx{tx, nil, ""}) + } + + return nil + + // Consensus related Txs inactivated for now + // TODO! + /* + case *txs.BondTx: + valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) + if valInfo != nil { + // TODO: In the future, check that the validator wasn't destroyed, + // add funds, merge UnbondTo outputs, and unbond validator. + return errors.New("Adding coins to existing validators not yet supported") + } + + accounts, err := getInputs(blockCache, tx.Inputs) + if err != nil { + return err + } + + // add outputs to accounts map + // if any outputs don't exist, all inputs must have CreateAccount perm + // though outputs aren't created until unbonding/release time + canCreate := hasCreateAccountPermission(blockCache, accounts) + for _, out := range tx.UnbondTo { + acc := blockCache.GetAccount(out.Address) + if acc == nil && !canCreate { + return fmt.Errorf("At least one input does not have permission to create accounts") + } + } + + bondAcc := blockCache.GetAccount(tx.PubKey.Address()) + if !hasBondPermission(blockCache, bondAcc) { + return fmt.Errorf("The bonder does not have permission to bond") + } + + if !hasBondOrSendPermission(blockCache, accounts) { + return fmt.Errorf("At least one input lacks permission to bond") + } + + signBytes := acm.SignBytes(_s.ChainID, tx) + inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) + if err != nil { + return err + } + if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) { + return txs.ErrTxInvalidSignature + } + outTotal, err := validateOutputs(tx.UnbondTo) + if err != nil { + return err + } + if outTotal > inTotal { + return txs.ErrTxInsufficientFunds + } + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + adjustByInputs(accounts, tx.Inputs) + for _, acc := range accounts { + blockCache.UpdateAccount(acc) + } + // Add ValidatorInfo + _s.SetValidatorInfo(&txs.ValidatorInfo{ + Address: tx.PubKey.Address(), + PubKey: tx.PubKey, + UnbondTo: tx.UnbondTo, + FirstBondHeight: _s.LastBlockHeight + 1, + FirstBondAmount: outTotal, + }) + // Add Validator + added := _s.BondedValidators.Add(&txs.Validator{ + Address: tx.PubKey.Address(), + PubKey: tx.PubKey, + BondHeight: _s.LastBlockHeight + 1, + VotingPower: outTotal, + Accum: 0, + }) + if !added { + PanicCrisis("Failed to add validator") + } + if evc != nil { + // TODO: fire for all inputs + evc.FireEvent(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""}) + } + return nil + + case *txs.UnbondTx: + // The validator must be active + _, val := _s.BondedValidators.GetByAddress(tx.Address) + if val == nil { + return txs.ErrTxInvalidAddress + } + + // Verify the signature + signBytes := acm.SignBytes(_s.ChainID, tx) + if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + return txs.ErrTxInvalidSignature + } + + // tx.Height must be greater than val.LastCommitHeight + if tx.Height <= val.LastCommitHeight { + return errors.New("Invalid unbond height") + } + + // Good! + _s.unbondValidator(val) + if evc != nil { + evc.FireEvent(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""}) + } + return nil + + case *txs.RebondTx: + // The validator must be inactive + _, val := _s.UnbondingValidators.GetByAddress(tx.Address) + if val == nil { + return txs.ErrTxInvalidAddress + } + + // Verify the signature + signBytes := acm.SignBytes(_s.ChainID, tx) + if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + return txs.ErrTxInvalidSignature + } + + // tx.Height must be in a suitable range + minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2) + maxRebondHeight := _s.LastBlockHeight + 2 + if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) { + return errors.New(Fmt("Rebond height not in range. Expected %v <= %v <= %v", + minRebondHeight, tx.Height, maxRebondHeight)) + } + + // Good! + _s.rebondValidator(val) + if evc != nil { + evc.FireEvent(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""}) + } + return nil + + case *txs.DupeoutTx: + // Verify the signatures + _, accused := _s.BondedValidators.GetByAddress(tx.Address) + if accused == nil { + _, accused = _s.UnbondingValidators.GetByAddress(tx.Address) + if accused == nil { + return txs.ErrTxInvalidAddress + } + } + voteASignBytes := acm.SignBytes(_s.ChainID, &tx.VoteA) + voteBSignBytes := acm.SignBytes(_s.ChainID, &tx.VoteB) + if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || + !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { + return txs.ErrTxInvalidSignature + } + + // Verify equivocation + // TODO: in the future, just require one vote from a previous height that + // doesn't exist on this chain. + if tx.VoteA.Height != tx.VoteB.Height { + return errors.New("DupeoutTx heights don't match") + } + if tx.VoteA.Round != tx.VoteB.Round { + return errors.New("DupeoutTx rounds don't match") + } + if tx.VoteA.Type != tx.VoteB.Type { + return errors.New("DupeoutTx types don't match") + } + if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { + return errors.New("DupeoutTx blockhashes shouldn't match") + } + + // Good! (Bad validator!) + _s.destroyValidator(accused) + if evc != nil { + evc.FireEvent(txs.EventStringDupeout(), txs.EventDataTx{tx, nil, ""}) + } + return nil + */ + + case *txs.PermissionsTx: + var inAcc *acm.Account + + // Validate input + inAcc = blockCache.GetAccount(tx.Input.Address) + if inAcc == nil { + log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) + return txs.ErrTxInvalidAddress + } + + permFlag := tx.PermArgs.PermFlag() + // check permission + if !HasPermission(blockCache, inAcc, permFlag) { + return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag) + } + + // pubKey should be present in either "inAcc" or "tx.Input" + if err := checkInputPubKey(inAcc, tx.Input); err != nil { + log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) + return err + } + signBytes := acm.SignBytes(_s.ChainID, tx) + err := validateInput(inAcc, signBytes, tx.Input) + if err != nil { + log.Debug(Fmt("validateInput failed on %X: %v", tx.Input.Address, err)) + return err + } + + value := tx.Input.Amount + + log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs) + + var permAcc *acm.Account + switch args := tx.PermArgs.(type) { + case *ptypes.HasBaseArgs: + // this one doesn't make sense from txs + return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain") + case *ptypes.SetBaseArgs: + if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { + return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address) + } + err = permAcc.Permissions.Base.Set(args.Permission, args.Value) + case *ptypes.UnsetBaseArgs: + if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { + return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address) + } + err = permAcc.Permissions.Base.Unset(args.Permission) + case *ptypes.SetGlobalArgs: + if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil { + PanicSanity("can't find global permissions account") + } + err = permAcc.Permissions.Base.Set(args.Permission, args.Value) + case *ptypes.HasRoleArgs: + return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain") + case *ptypes.AddRoleArgs: + if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { + return fmt.Errorf("Trying to update roles for unknown account %X", args.Address) + } + if !permAcc.Permissions.AddRole(args.Role) { + return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address) + } + case *ptypes.RmRoleArgs: + if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { + return fmt.Errorf("Trying to update roles for unknown account %X", args.Address) + } + if !permAcc.Permissions.RmRole(args.Role) { + return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address) + } + default: + PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag))) + } + + // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? + if err != nil { + return err + } + + // Good! + inAcc.Sequence += 1 + inAcc.Balance -= value + blockCache.UpdateAccount(inAcc) + if permAcc != nil { + blockCache.UpdateAccount(permAcc) + } + + if evc != nil { + evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, nil, ""}) + evc.FireEvent(txs.EventStringPermissions(ptypes.PermFlagToString(permFlag)), txs.EventDataTx{tx, nil, ""}) + } + + return nil + + default: + // binary decoding should not let this happen + PanicSanity("Unknown Tx type") + return nil + } +} + +//--------------------------------------------------------------- + +// Get permission on an account or fall back to global value +func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) bool { + if perm > ptypes.AllPermFlags { + PanicSanity("Checking an unknown permission in state should never happen") + } + + if acc == nil { + // TODO + // this needs to fall back to global or do some other specific things + // eg. a bondAcc may be nil and so can only bond if global bonding is true + } + permString := ptypes.PermFlagToString(perm) + + v, err := acc.Permissions.Base.Get(perm) + if _, ok := err.(ptypes.ErrValueNotSet); ok { + if state == nil { + PanicSanity("All known global permissions should be set!") + } + log.Info("Permission for account is not set. Querying GlobalPermissionsAddress", "perm", permString) + return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm) + } else if v { + log.Info("Account has permission", "address", Fmt("%X", acc.Address), "perm", permString) + } else { + log.Info("Account does not have permission", "address", Fmt("%X", acc.Address), "perm", permString) + } + return v +} + +// TODO: for debug log the failed accounts +func hasSendPermission(state AccountGetter, accs map[string]*acm.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.Send) { + return false + } + } + return true +} + +func hasNamePermission(state AccountGetter, acc *acm.Account) bool { + return HasPermission(state, acc, ptypes.Name) +} + +func hasCallPermission(state AccountGetter, acc *acm.Account) bool { + return HasPermission(state, acc, ptypes.Call) +} + +func hasCreateContractPermission(state AccountGetter, acc *acm.Account) bool { + return HasPermission(state, acc, ptypes.CreateContract) +} + +func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.CreateAccount) { + return false + } + } + return true +} + +func hasBondPermission(state AccountGetter, acc *acm.Account) bool { + return HasPermission(state, acc, ptypes.Bond) +} + +func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.Bond) { + if !HasPermission(state, acc, ptypes.Send) { + return false + } + } + } + return true +} + +//----------------------------------------------------------------------------- + +type InvalidTxError struct { + Tx txs.Tx + Reason error +} + +func (txErr InvalidTxError) Error() string { + return Fmt("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason) +} diff --git a/manager/eris-mint/state/genesis_test.go b/manager/eris-mint/state/genesis_test.go new file mode 100644 index 0000000000000000000000000000000000000000..05f16cae8054af645585076ef15c5b9ecae61b57 --- /dev/null +++ b/manager/eris-mint/state/genesis_test.go @@ -0,0 +1,87 @@ +package state + +import ( + "bytes" + "encoding/hex" + "fmt" + "testing" + + ptypes "github.com/eris-ltd/eris-db/permission/types" + . "github.com/eris-ltd/eris-db/state/types" + tdb "github.com/tendermint/go-db" +) + +var chain_id = "lone_ranger" +var addr1, _ = hex.DecodeString("964B1493BBE3312278B7DEB94C39149F7899A345") +var send1, name1, call1 = 1, 1, 0 +var perms, setbit = 66, 70 +var accName = "me" +var roles1 = []string{"master", "universal-ruler"} +var amt1 int64 = 1000000 +var g1 = fmt.Sprintf(` +{ + "chain_id":"%s", + "accounts": [ + { + "address": "%X", + "amount": %d, + "name": "%s", + "permissions": { + "base": { + "perms": %d, + "set": %d + }, + "roles": [ + "%s", + "%s" + ] + } + } + ], + "validators": [ + { + "amount": 100000000, + "pub_key": "F6C79CF0CB9D66B677988BCB9B8EADD9A091CD465A60542A8AB85476256DBA92", + "unbond_to": [ + { + "address": "964B1493BBE3312278B7DEB94C39149F7899A345", + "amount": 10000000 + } + ] + } + ] +} +`, chain_id, addr1, amt1, accName, perms, setbit, roles1[0], roles1[1]) + +func TestGenesisReadable(t *testing.T) { + genDoc := GenesisDocFromJSON([]byte(g1)) + if genDoc.ChainID != chain_id { + t.Fatalf("Incorrect chain id. Got %d, expected %d\n", genDoc.ChainID, chain_id) + } + acc := genDoc.Accounts[0] + if bytes.Compare(acc.Address, addr1) != 0 { + t.Fatalf("Incorrect address for account. Got %X, expected %X\n", acc.Address, addr1) + } + if acc.Amount != amt1 { + t.Fatalf("Incorrect amount for account. Got %d, expected %d\n", acc.Amount, amt1) + } + if acc.Name != accName { + t.Fatalf("Incorrect name for account. Got %s, expected %s\n", acc.Name, accName) + } + + perm, _ := acc.Permissions.Base.Get(ptypes.Send) + if perm != (send1 > 0) { + t.Fatalf("Incorrect permission for send. Got %v, expected %v\n", perm, send1 > 0) + } +} + +func TestGenesisMakeState(t *testing.T) { + genDoc := GenesisDocFromJSON([]byte(g1)) + db := tdb.NewMemDB() + st := MakeGenesisState(db, genDoc) + acc := st.GetAccount(addr1) + v, _ := acc.Permissions.Base.Get(ptypes.Send) + if v != (send1 > 0) { + t.Fatalf("Incorrect permission for send. Got %v, expected %v\n", v, send1 > 0) + } +} diff --git a/manager/eris-mint/state/log.go b/manager/eris-mint/state/log.go new file mode 100644 index 0000000000000000000000000000000000000000..5b102b5703e0474c1ee2db377f3b95c7a8705c3c --- /dev/null +++ b/manager/eris-mint/state/log.go @@ -0,0 +1,7 @@ +package state + +import ( + "github.com/tendermint/go-logger" +) + +var log = logger.New("module", "state") diff --git a/manager/eris-mint/state/permissions_test.go b/manager/eris-mint/state/permissions_test.go new file mode 100644 index 0000000000000000000000000000000000000000..46aa570a7fb6dda0ef56e9416bd77a49eb4f8a97 --- /dev/null +++ b/manager/eris-mint/state/permissions_test.go @@ -0,0 +1,1298 @@ +package state + +import ( + "bytes" + "encoding/hex" + "fmt" + "strconv" + "testing" + "time" + + acm "github.com/eris-ltd/eris-db/account" + ptypes "github.com/eris-ltd/eris-db/permission/types" + . "github.com/eris-ltd/eris-db/state/types" + "github.com/eris-ltd/eris-db/txs" + + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + dbm "github.com/tendermint/go-db" + "github.com/tendermint/go-events" + + "github.com/tendermint/tendermint/config/tendermint_test" +) + +func init() { + tendermint_test.ResetConfig("permissions_test") +} + +var ( + dbBackend = "memdb" + dbDir = "" +) + +/* +Permission Tests: + +- SendTx: +x - 1 input, no perm, call perm, create perm +x - 1 input, perm +x - 2 inputs, one with perm one without + +- CallTx, CALL +x - 1 input, no perm, send perm, create perm +x - 1 input, perm +x - contract runs call but doesn't have call perm +x - contract runs call and has call perm +x - contract runs call (with perm), runs contract that runs call (without perm) +x - contract runs call (with perm), runs contract that runs call (with perm) + +- CallTx for Create, CREATE +x - 1 input, no perm, send perm, call perm +x - 1 input, perm +x - contract runs create but doesn't have create perm +x - contract runs create but has perm +x - contract runs call with empty address (has call and create perm) + +- NameTx + - no perm, send perm, call perm + - with perm + +- BondTx +x - 1 input, no perm +x - 1 input, perm +x - 1 bonder with perm, input without send or bond +x - 1 bonder with perm, input with send +x - 1 bonder with perm, input with bond +x - 2 inputs, one with perm one without + +- SendTx for new account +x - 1 input, 1 unknown ouput, input with send, not create (fail) +x - 1 input, 1 unknown ouput, input with send and create (pass) +x - 2 inputs, 1 unknown ouput, both inputs with send, one with create, one without (fail) +x - 2 inputs, 1 known output, 1 unknown ouput, one input with create, one without (fail) +x - 2 inputs, 1 unknown ouput, both inputs with send, both inputs with create (pass ) +x - 2 inputs, 1 known output, 1 unknown ouput, both inputs with create, (pass) + + +- CALL for new account +x - unknown output, without create (fail) +x - unknown output, with create (pass) + + +- SNative (CallTx, CALL): + - for each of CallTx, Call +x - call each snative without permission, fails +x - call each snative with permission, pass + - list: +x - base: has,set,unset +x - globals: set +x - roles: has, add, rm + + +*/ + +// keys +var user = makeUsers(10) +var chainID = "testchain" + +func makeUsers(n int) []*acm.PrivAccount { + accounts := []*acm.PrivAccount{} + for i := 0; i < n; i++ { + secret := ("mysecret" + strconv.Itoa(i)) + user := acm.GenPrivAccountFromSecret(secret) + accounts = append(accounts, user) + } + return accounts +} + +var ( + PermsAllFalse = ptypes.ZeroAccountPermissions +) + +func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) GenesisDoc { + genAccounts := []GenesisAccount{} + for _, u := range user[:5] { + accountPermCopy := accountPerm // Create new instance for custom overridability. + genAccounts = append(genAccounts, GenesisAccount{ + Address: u.Address, + Amount: 1000000, + Permissions: &accountPermCopy, + }) + } + + return GenesisDoc{ + GenesisTime: time.Now(), + ChainID: chainID, + Params: &GenesisParams{ + GlobalPermissions: &globalPerm, + }, + Accounts: genAccounts, + Validators: []GenesisValidator{ + GenesisValidator{ + PubKey: user[0].PubKey.(crypto.PubKeyEd25519), + Amount: 10, + UnbondTo: []BasicAccount{ + BasicAccount{ + Address: user[0].Address, + }, + }, + }, + }, + } +} + +func TestSendFails(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //------------------- + // send txs + + // simple send tx should fail + tx := txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx with call perm should fail + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[4].Address, 5) + tx.SignInput(chainID, 0, user[2]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx with create perm should fail + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[4].Address, 5) + tx.SignInput(chainID, 0, user[3]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx to unknown account without create_account perm should fail + acc := blockCache.GetAccount(user[3].Address) + acc.Permissions.Base.Set(ptypes.Send, true) + blockCache.UpdateAccount(acc) + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[6].Address, 5) + tx.SignInput(chainID, 0, user[3]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestName(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Name, true) + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //------------------- + // name txs + + // simple name tx without perm should fail + tx, err := txs.NewNameTx(st, user[0].PubKey, "somename", "somedata", 10000, 100) + if err != nil { + t.Fatal(err) + } + tx.Sign(chainID, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple name tx with perm should pass + tx, err = txs.NewNameTx(st, user[1].PubKey, "somename", "somedata", 10000, 100) + if err != nil { + t.Fatal(err) + } + tx.Sign(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal(err) + } +} + +func TestCallFails(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //------------------- + // call txs + + // simple call tx should fail + tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, user[4].Address, nil, 100, 100, 100) + tx.Sign(chainID, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call tx with send permission should fail + tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, user[4].Address, nil, 100, 100, 100) + tx.Sign(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call tx with create permission should fail + tx, _ = txs.NewCallTx(blockCache, user[3].PubKey, user[4].Address, nil, 100, 100, 100) + tx.Sign(chainID, user[3]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + //------------------- + // create txs + + // simple call create tx should fail + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, nil, 100, 100, 100) + tx.Sign(chainID, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call create tx with send perm should fail + tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, nil, nil, 100, 100, 100) + tx.Sign(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call create tx with call perm should fail + tx, _ = txs.NewCallTx(blockCache, user[2].PubKey, nil, nil, 100, 100, 100) + tx.Sign(chainID, user[2]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestSendPermission(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + // A single input, having the permission, should succeed + tx := txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, one with permission, one without, should fail + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[2].Address, 10) + tx.SignInput(chainID, 0, user[0]) + tx.SignInput(chainID, 1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestCallPermission(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //------------------------------ + // call to simple contract + fmt.Println("\n##### SIMPLE CONTRACT") + + // create simple contract + simpleContractAddr := NewContractAddress(user[0].Address, 100) + simpleAcc := &acm.Account{ + Address: simpleContractAddr, + Balance: 0, + Code: []byte{0x60}, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.ZeroAccountPermissions, + } + st.UpdateAccount(simpleAcc) + + // A single input, having the permission, should succeed + tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, simpleContractAddr, nil, 100, 100, 100) + tx.Sign(chainID, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Transaction failed", err) + } + + //---------------------------------------------------------- + // call to contract that calls simple contract - without perm + fmt.Println("\n##### CALL TO SIMPLE CONTRACT (FAIL)") + + // create contract that calls the simple contract + contractCode := callContractCode(simpleContractAddr) + caller1ContractAddr := NewContractAddress(user[0].Address, 101) + caller1Acc := &acm.Account{ + Address: caller1ContractAddr, + Balance: 10000, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.ZeroAccountPermissions, + } + blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // call to contract that calls simple contract - with perm + fmt.Println("\n##### CALL TO SIMPLE CONTRACT (PASS)") + + // A single input, having the permission, and the contract has permission + caller1Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception:", exception) + } + + //---------------------------------------------------------- + // call to contract that calls contract that calls simple contract - without perm + // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. + // caller1Contract does not have call perms, but caller2Contract does. + fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") + + contractCode2 := callContractCode(caller1ContractAddr) + caller2ContractAddr := NewContractAddress(user[0].Address, 102) + caller2Acc := &acm.Account{ + Address: caller2ContractAddr, + Balance: 1000, + Code: contractCode2, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.ZeroAccountPermissions, + } + caller1Acc.Permissions.Base.Set(ptypes.Call, false) + caller2Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + blockCache.UpdateAccount(caller2Acc) + + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // call to contract that calls contract that calls simple contract - without perm + // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. + // both caller1 and caller2 have permission + fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") + + caller1Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } +} + +func TestCreatePermission(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateContract, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //------------------------------ + // create a simple contract + fmt.Println("\n##### CREATE SIMPLE CONTRACT") + + contractCode := []byte{0x60} + createCode := wrapContractForCreate(contractCode) + + // A single input, having the permission, should succeed + tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, nil, createCode, 100, 100, 100) + tx.Sign(chainID, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Transaction failed", err) + } + // ensure the contract is there + contractAddr := NewContractAddress(tx.Input.Address, tx.Input.Sequence) + contractAcc := blockCache.GetAccount(contractAddr) + if contractAcc == nil { + t.Fatalf("failed to create contract %X", contractAddr) + } + if bytes.Compare(contractAcc.Code, contractCode) != 0 { + t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, contractCode) + } + + //------------------------------ + // create contract that uses the CREATE op + fmt.Println("\n##### CREATE FACTORY") + + contractCode = []byte{0x60} + createCode = wrapContractForCreate(contractCode) + factoryCode := createContractCode() + createFactoryCode := wrapContractForCreate(factoryCode) + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, createFactoryCode, 100, 100, 100) + tx.Sign(chainID, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Transaction failed", err) + } + // ensure the contract is there + contractAddr = NewContractAddress(tx.Input.Address, tx.Input.Sequence) + contractAcc = blockCache.GetAccount(contractAddr) + if contractAcc == nil { + t.Fatalf("failed to create contract %X", contractAddr) + } + if bytes.Compare(contractAcc.Code, factoryCode) != 0 { + t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, factoryCode) + } + + //------------------------------ + // call the contract (should FAIL) + fmt.Println("\n###### CALL THE FACTORY (FAIL)") + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) + tx.Sign(chainID, user[0]) + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(contractAddr)) // + if exception == "" { + t.Fatal("expected exception") + } + + //------------------------------ + // call the contract (should PASS) + fmt.Println("\n###### CALL THE FACTORY (PASS)") + + contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) + blockCache.UpdateAccount(contractAcc) + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) + tx.Sign(chainID, user[0]) + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(contractAddr)) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + + //-------------------------------- + fmt.Println("\n##### CALL to empty address") + zeroAddr := LeftPadBytes([]byte{}, 20) + code := callContractCode(zeroAddr) + + contractAddr = NewContractAddress(user[0].Address, 110) + contractAcc = &acm.Account{ + Address: contractAddr, + Balance: 1000, + Code: code, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.ZeroAccountPermissions, + } + contractAcc.Permissions.Base.Set(ptypes.Call, true) + contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) + blockCache.UpdateAccount(contractAcc) + + // this should call the 0 address but not create ... + tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 10000, 100) + tx.Sign(chainID, user[0]) + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(zeroAddr)) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + zeroAcc := blockCache.GetAccount(zeroAddr) + if len(zeroAcc.Code) != 0 { + t.Fatal("the zero account was given code from a CALL!") + } +} + +/* TODO +func TestBondPermission(t *testing.T) { + stateDB := dbm.GetDB("state",dbBackend,dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + var bondAcc *acm.Account + + //------------------------------ + // one bonder without permission should fail + tx, _ := txs.NewBondTx(user[1].PubKey) + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[1]) + tx.SignBond(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + //------------------------------ + // one bonder with permission should pass + bondAcc = blockCache.GetAccount(user[1].Address) + bondAcc.Permissions.Base.Set(ptypes.Bond, true) + blockCache.UpdateAccount(bondAcc) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + blockCache = NewBlockCache(st) + bondAcc = blockCache.GetAccount(user[1].Address) + bondAcc.Permissions.Base.Set(ptypes.Bond, true) + blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input without send should fail + tx, _ = txs.NewBondTx(user[1].PubKey) + if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[2]) + tx.SignBond(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + blockCache = NewBlockCache(st) + bondAcc = blockCache.GetAccount(user[1].Address) + bondAcc.Permissions.Base.Set(ptypes.Bond, true) + blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input with send should pass + sendAcc := blockCache.GetAccount(user[2].Address) + sendAcc.Permissions.Base.Set(ptypes.Send, true) + blockCache.UpdateAccount(sendAcc) + tx, _ = txs.NewBondTx(user[1].PubKey) + if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[2]) + tx.SignBond(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + blockCache = NewBlockCache(st) + bondAcc = blockCache.GetAccount(user[1].Address) + bondAcc.Permissions.Base.Set(ptypes.Bond, true) + blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input with bond should pass + sendAcc.Permissions.Base.Set(ptypes.Bond, true) + blockCache.UpdateAccount(sendAcc) + tx, _ = txs.NewBondTx(user[1].PubKey) + if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[2]) + tx.SignBond(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + blockCache = NewBlockCache(st) + bondAcc = blockCache.GetAccount(user[1].Address) + bondAcc.Permissions.Base.Set(ptypes.Bond, true) + blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input from that bonder and an input without send or bond should fail + tx, _ = txs.NewBondTx(user[1].PubKey) + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[1].Address, 5) + tx.SignInput(chainID, 0, user[1]) + tx.SignInput(chainID, 1, user[2]) + tx.SignBond(chainID, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } +} +*/ + +func TestCreateAccountPermission(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission + genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateAccount, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //---------------------------------------------------------- + // SendTx to unknown account + + // A single input, having the permission, should succeed + tx := txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[6].Address, 5) + tx.SignInput(chainID, 0, user[0]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, both with send, one with create, one without, should fail + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 10) + tx.SignInput(chainID, 0, user[0]) + tx.SignInput(chainID, 1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, one with create, one without, two ouputs (one known, one unknown) should fail + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 4) + tx.AddOutput(user[4].Address, 6) + tx.SignInput(chainID, 0, user[0]) + tx.SignInput(chainID, 1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, both with create, should pass + acc := blockCache.GetAccount(user[1].Address) + acc.Permissions.Base.Set(ptypes.CreateAccount, true) + blockCache.UpdateAccount(acc) + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 10) + tx.SignInput(chainID, 0, user[0]) + tx.SignInput(chainID, 1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // Two inputs, both with send, both with create, two outputs (one known, one unknown) should pass + tx = txs.NewSendTx() + if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(user[7].Address, 7) + tx.AddOutput(user[4].Address, 3) + tx.SignInput(chainID, 0, user[0]) + tx.SignInput(chainID, 1, user[1]) + if err := ExecTx(blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + //---------------------------------------------------------- + // CALL to unknown account + + acc = blockCache.GetAccount(user[0].Address) + acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(acc) + + // call to contract that calls unknown account - without create_account perm + // create contract that calls the simple contract + contractCode := callContractCode(user[9].Address) + caller1ContractAddr := NewContractAddress(user[4].Address, 101) + caller1Acc := &acm.Account{ + Address: caller1ContractAddr, + Balance: 0, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.ZeroAccountPermissions, + } + blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + txCall, _ := txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(chainID, user[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, txCall, txs.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + // NOTE: for a contract to be able to CreateAccount, it must be able to call + // NOTE: for a user to be able to CreateAccount, it must be able to send! + caller1Acc.Permissions.Base.Set(ptypes.CreateAccount, true) + caller1Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + // A single input, having the permission, but the contract doesn't have permission + txCall, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(chainID, user[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, txCall, txs.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + +} + +// holla at my boy +var DougAddress = append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte("THISISDOUG")...) + +func TestSNativeCALL(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission + genDoc.Accounts[3].Permissions.Base.Set(ptypes.Bond, true) // some arbitrary permission to play with + genDoc.Accounts[3].Permissions.AddRole("bumble") + genDoc.Accounts[3].Permissions.AddRole("bee") + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //---------------------------------------------------------- + // Test CALL to SNative contracts + + // make the main contract once + doug := &acm.Account{ + Address: DougAddress, + Balance: 0, + Code: nil, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.ZeroAccountPermissions, + } + doug.Permissions.Base.Set(ptypes.Call, true) + //doug.Permissions.Base.Set(ptypes.HasBase, true) + blockCache.UpdateAccount(doug) + + fmt.Println("\n#### HasBase") + // HasBase + snativeAddress, pF, data := snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### SetBase") + // SetBase + snativeAddress, pF, data = snativePermTestInputCALL("set_base", user[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + snativeAddress, pF, data = snativePermTestInputCALL("set_base", user[3], ptypes.CreateContract, true) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### UnsetBase") + // UnsetBase + snativeAddress, pF, data = snativePermTestInputCALL("unset_base", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### SetGlobal") + // SetGlobalPerm + snativeAddress, pF, data = snativePermTestInputCALL("set_global", user[3], ptypes.CreateContract, true) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### HasRole") + // HasRole + snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "bumble") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### AddRole") + // AddRole + snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + snativeAddress, pF, data = snativeRoleTestInputCALL("add_role", user[3], "chuck") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### RmRole") + // RmRole + snativeAddress, pF, data = snativeRoleTestInputCALL("rm_role", user[3], "chuck") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) +} + +func TestSNativeTx(t *testing.T) { + stateDB := dbm.GetDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission + genDoc.Accounts[3].Permissions.Base.Set(ptypes.Bond, true) // some arbitrary permission to play with + genDoc.Accounts[3].Permissions.AddRole("bumble") + genDoc.Accounts[3].Permissions.AddRole("bee") + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + + //---------------------------------------------------------- + // Test SNativeTx + + fmt.Println("\n#### SetBase") + // SetBase + snativeArgs := snativePermTestInputTx("set_base", user[3], ptypes.Bond, false) + testSNativeTxExpectFail(t, blockCache, snativeArgs) + testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) + acc := blockCache.GetAccount(user[3].Address) + if v, _ := acc.Permissions.Base.Get(ptypes.Bond); v { + t.Fatal("expected permission to be set false") + } + snativeArgs = snativePermTestInputTx("set_base", user[3], ptypes.CreateContract, true) + testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) + acc = blockCache.GetAccount(user[3].Address) + if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { + t.Fatal("expected permission to be set true") + } + + fmt.Println("\n#### UnsetBase") + // UnsetBase + snativeArgs = snativePermTestInputTx("unset_base", user[3], ptypes.CreateContract, false) + testSNativeTxExpectFail(t, blockCache, snativeArgs) + testSNativeTxExpectPass(t, blockCache, ptypes.UnsetBase, snativeArgs) + acc = blockCache.GetAccount(user[3].Address) + if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); v { + t.Fatal("expected permission to be set false") + } + + fmt.Println("\n#### SetGlobal") + // SetGlobalPerm + snativeArgs = snativePermTestInputTx("set_global", user[3], ptypes.CreateContract, true) + testSNativeTxExpectFail(t, blockCache, snativeArgs) + testSNativeTxExpectPass(t, blockCache, ptypes.SetGlobal, snativeArgs) + acc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress) + if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { + t.Fatal("expected permission to be set true") + } + + fmt.Println("\n#### AddRole") + // AddRole + snativeArgs = snativeRoleTestInputTx("add_role", user[3], "chuck") + testSNativeTxExpectFail(t, blockCache, snativeArgs) + testSNativeTxExpectPass(t, blockCache, ptypes.AddRole, snativeArgs) + acc = blockCache.GetAccount(user[3].Address) + if v := acc.Permissions.HasRole("chuck"); !v { + t.Fatal("expected role to be added") + } + + fmt.Println("\n#### RmRole") + // RmRole + snativeArgs = snativeRoleTestInputTx("rm_role", user[3], "chuck") + testSNativeTxExpectFail(t, blockCache, snativeArgs) + testSNativeTxExpectPass(t, blockCache, ptypes.RmRole, snativeArgs) + acc = blockCache.GetAccount(user[3].Address) + if v := acc.Permissions.HasRole("chuck"); v { + t.Fatal("expected role to be removed") + } +} + +//------------------------------------------------------------------------------------- +// helpers + +var ExceptionTimeOut = "timed out waiting for event" + +// run ExecTx and wait for the Call event on given addr +// returns the msg data and an error/exception +func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx txs.Tx, eventid string) (interface{}, string) { + evsw := events.NewEventSwitch() + evsw.Start() + ch := make(chan interface{}) + evsw.AddListenerForEvent("test", eventid, func(msg events.EventData) { + ch <- msg + }) + evc := events.NewEventCache(evsw) + go func() { + if err := ExecTx(blockCache, tx, true, evc); err != nil { + ch <- err.Error() + } + evc.Flush() + }() + ticker := time.NewTicker(5 * time.Second) + var msg interface{} + select { + case msg = <-ch: + case <-ticker.C: + return nil, ExceptionTimeOut + } + + switch ev := msg.(type) { + case txs.EventDataTx: + return ev, ev.Exception + case txs.EventDataCall: + return ev, ev.Exception + case string: + return nil, ev + default: + return ev, "" + } +} + +// give a contract perms for an snative, call it, it calls the snative, but shouldn't have permission +func testSNativeCALLExpectFail(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte) { + testSNativeCALL(t, false, blockCache, doug, 0, snativeAddress, data, nil) +} + +// give a contract perms for an snative, call it, it calls the snative, ensure the check funciton (f) succeeds +func testSNativeCALLExpectPass(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativePerm ptypes.PermFlag, snativeAddress, data []byte, f func([]byte) error) { + testSNativeCALL(t, true, blockCache, doug, snativePerm, snativeAddress, data, f) +} + +func testSNativeCALL(t *testing.T, expectPass bool, blockCache *BlockCache, doug *acm.Account, snativePerm ptypes.PermFlag, snativeAddress, data []byte, f func([]byte) error) { + if expectPass { + doug.Permissions.Base.Set(snativePerm, true) + } + var addr []byte + contractCode := callContractCode(snativeAddress) + doug.Code = contractCode + blockCache.UpdateAccount(doug) + addr = doug.Address + tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100) + tx.Sign(chainID, user[0]) + fmt.Println("subscribing to", txs.EventStringAccCall(snativeAddress)) + ev, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(snativeAddress)) + if exception == ExceptionTimeOut { + t.Fatal("Timed out waiting for event") + } + if expectPass { + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + evv := ev.(txs.EventDataCall) + ret := evv.Return + if err := f(ret); err != nil { + t.Fatal(err) + } + } else { + if exception == "" { + t.Fatal("Expected exception") + } + } +} + +func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.PermArgs) { + testSNativeTx(t, false, blockCache, 0, snativeArgs) +} + +func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) { + testSNativeTx(t, true, blockCache, perm, snativeArgs) +} + +func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) { + if expectPass { + acc := blockCache.GetAccount(user[0].Address) + acc.Permissions.Base.Set(perm, true) + blockCache.UpdateAccount(acc) + } + tx, _ := txs.NewPermissionsTx(blockCache, user[0].PubKey, snativeArgs) + tx.Sign(chainID, user[0]) + err := ExecTx(blockCache, tx, true, nil) + if expectPass { + if err != nil { + t.Fatal("Unexpected exception", err) + } + } else { + if err == nil { + t.Fatal("Expected exception") + } + } +} + +func boolToWord256(v bool) Word256 { + var vint byte + if v { + vint = 0x1 + } else { + vint = 0x0 + } + return LeftPadWord256([]byte{vint}) +} + +func permNameToFuncID(s string) []byte { + for k, v := range vm.PermsMap { + if s == v.Name { + b, _ := hex.DecodeString(k) + return b + } + } + panic("didn't find snative function signature!") +} + +func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, pF ptypes.PermFlag, data []byte) { + addr = LeftPadWord256([]byte(vm.PermissionsContract)).Postfix(20) + switch name { + case "has_base", "unset_base": + data = LeftPadBytes(user.Address, 32) + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + case "set_base": + data = LeftPadBytes(user.Address, 32) + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + data = append(data, boolToWord256(val).Bytes()...) + case "set_global": + data = Uint64ToWord256(uint64(perm)).Bytes() + data = append(data, boolToWord256(val).Bytes()...) + } + data = append(permNameToFuncID(name), data...) + var err error + if pF, err = ptypes.PermStringToFlag(name); err != nil { + panic(Fmt("failed to convert perm string (%s) to flag", name)) + } + return +} + +func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.PermArgs) { + switch name { + case "has_base": + snativeArgs = &ptypes.HasBaseArgs{user.Address, perm} + case "unset_base": + snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm} + case "set_base": + snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val} + case "set_global": + snativeArgs = &ptypes.SetGlobalArgs{perm, val} + } + return +} + +func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) (addr []byte, pF ptypes.PermFlag, data []byte) { + addr = LeftPadWord256([]byte(vm.PermissionsContract)).Postfix(20) + data = LeftPadBytes(user.Address, 32) + data = append(data, LeftPadBytes([]byte(role), 32)...) + data = append(permNameToFuncID(name), data...) + + var err error + if pF, err = ptypes.PermStringToFlag(name); err != nil { + panic(Fmt("failed to convert perm string (%s) to flag", name)) + } + return +} + +func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.PermArgs) { + switch name { + case "has_role": + snativeArgs = &ptypes.HasRoleArgs{user.Address, role} + case "add_role": + snativeArgs = &ptypes.AddRoleArgs{user.Address, role} + case "rm_role": + snativeArgs = &ptypes.RmRoleArgs{user.Address, role} + } + return +} + +// convenience function for contract that calls a given address +func callContractCode(contractAddr []byte) []byte { + // calldatacopy into mem and use as input to call + memOff, inputOff := byte(0x0), byte(0x0) + contractCode := []byte{0x36, 0x60, inputOff, 0x60, memOff, 0x37} + + gas1, gas2 := byte(0x1), byte(0x1) + value := byte(0x1) + inOff := byte(0x0) + retOff, retSize := byte(0x0), byte(0x20) + // this is the code we want to run (call a contract and return) + contractCode = append(contractCode, []byte{0x60, retSize, 0x60, retOff, 0x36, 0x60, inOff, 0x60, value, 0x73}...) + contractCode = append(contractCode, contractAddr...) + contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) + return contractCode +} + +// convenience function for contract that is a factory for the code that comes as call data +func createContractCode() []byte { + // TODO: gas ... + + // calldatacopy the calldatasize + memOff, inputOff := byte(0x0), byte(0x0) + contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} + + // create + value := byte(0x1) + contractCode = append(contractCode, []byte{0x60, value, 0x36, 0x60, memOff, 0xf0}...) + return contractCode +} + +// wrap a contract in create code +func wrapContractForCreate(contractCode []byte) []byte { + // the is the code we need to return the contractCode when the contract is initialized + lenCode := len(contractCode) + // push code to the stack + code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...) + // store it in memory + code = append(code, []byte{0x60, 0x0, 0x52}...) + // return whats in memory + code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) + // return init code, contract code, expected return + return code +} diff --git a/manager/eris-mint/state/state.go b/manager/eris-mint/state/state.go new file mode 100644 index 0000000000000000000000000000000000000000..aea7f949539bff8f3987b3dd1f316d5d4e7fe090 --- /dev/null +++ b/manager/eris-mint/state/state.go @@ -0,0 +1,498 @@ +package state + +import ( + "bytes" + "io" + "io/ioutil" + "time" + + acm "github.com/eris-ltd/eris-db/account" + ptypes "github.com/eris-ltd/eris-db/permission/types" + . "github.com/eris-ltd/eris-db/state/types" + txs "github.com/eris-ltd/eris-db/txs" + + . "github.com/tendermint/go-common" + dbm "github.com/tendermint/go-db" + "github.com/tendermint/go-events" + "github.com/tendermint/go-merkle" + "github.com/tendermint/go-wire" + + "github.com/tendermint/tendermint/types" +) + +var ( + stateKey = []byte("stateKey") + minBondAmount = int64(1) // TODO adjust + defaultAccountsCacheCapacity = 1000 // TODO adjust + unbondingPeriodBlocks = int(60 * 24 * 365) // TODO probably better to make it time based. + validatorTimeoutBlocks = int(10) // TODO adjust + maxLoadStateElementSize = 0 // no max +) + +//----------------------------------------------------------------------------- + +// NOTE: not goroutine-safe. +type State struct { + DB dbm.DB + ChainID string + LastBlockHeight int + LastBlockHash []byte + LastBlockParts types.PartSetHeader + LastBlockTime time.Time + // BondedValidators *types.ValidatorSet + // LastBondedValidators *types.ValidatorSet + // UnbondingValidators *types.ValidatorSet + accounts merkle.Tree // Shouldn't be accessed directly. + validatorInfos merkle.Tree // Shouldn't be accessed directly. + nameReg merkle.Tree // Shouldn't be accessed directly. + + evc events.Fireable // typically an events.EventCache +} + +func LoadState(db dbm.DB) *State { + s := &State{DB: db} + buf := db.Get(stateKey) + if len(buf) == 0 { + return nil + } else { + r, n, err := bytes.NewReader(buf), new(int), new(error) + s.ChainID = wire.ReadString(r, maxLoadStateElementSize, n, err) + s.LastBlockHeight = wire.ReadVarint(r, n, err) + s.LastBlockHash = wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) + s.LastBlockParts = wire.ReadBinary(types.PartSetHeader{}, r, maxLoadStateElementSize, n, err).(types.PartSetHeader) + s.LastBlockTime = wire.ReadTime(r, n, err) + // s.BondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) + // s.LastBondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) + // s.UnbondingValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) + accountsHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) + s.accounts = merkle.NewIAVLTree(defaultAccountsCacheCapacity, db) + s.accounts.Load(accountsHash) + //validatorInfosHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) + //s.validatorInfos = merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) + //s.validatorInfos.Load(validatorInfosHash) + nameRegHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) + s.nameReg = merkle.NewIAVLTree(0, db) + s.nameReg.Load(nameRegHash) + if *err != nil { + // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED + Exit(Fmt("Data has been corrupted or its spec has changed: %v\n", *err)) + } + // TODO: ensure that buf is completely read. + } + return s +} + +func (s *State) Save() { + s.accounts.Save() + //s.validatorInfos.Save() + s.nameReg.Save() + buf, n, err := new(bytes.Buffer), new(int), new(error) + wire.WriteString(s.ChainID, buf, n, err) + wire.WriteVarint(s.LastBlockHeight, buf, n, err) + wire.WriteByteSlice(s.LastBlockHash, buf, n, err) + wire.WriteBinary(s.LastBlockParts, buf, n, err) + wire.WriteTime(s.LastBlockTime, buf, n, err) + // wire.WriteBinary(s.BondedValidators, buf, n, err) + // wire.WriteBinary(s.LastBondedValidators, buf, n, err) + // wire.WriteBinary(s.UnbondingValidators, buf, n, err) + wire.WriteByteSlice(s.accounts.Hash(), buf, n, err) + //wire.WriteByteSlice(s.validatorInfos.Hash(), buf, n, err) + wire.WriteByteSlice(s.nameReg.Hash(), buf, n, err) + if *err != nil { + PanicCrisis(*err) + } + s.DB.Set(stateKey, buf.Bytes()) +} + +// CONTRACT: +// Copy() is a cheap way to take a snapshot, +// as if State were copied by value. +func (s *State) Copy() *State { + return &State{ + DB: s.DB, + ChainID: s.ChainID, + LastBlockHeight: s.LastBlockHeight, + LastBlockHash: s.LastBlockHash, + LastBlockParts: s.LastBlockParts, + LastBlockTime: s.LastBlockTime, + // BondedValidators: s.BondedValidators.Copy(), // TODO remove need for Copy() here. + // LastBondedValidators: s.LastBondedValidators.Copy(), // That is, make updates to the validator set + // UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily. + accounts: s.accounts.Copy(), + //validatorInfos: s.validatorInfos.Copy(), + nameReg: s.nameReg.Copy(), + evc: nil, + } +} + +// Returns a hash that represents the state data, excluding Last* +func (s *State) Hash() []byte { + return merkle.SimpleHashFromMap(map[string]interface{}{ + //"BondedValidators": s.BondedValidators, + //"UnbondingValidators": s.UnbondingValidators, + "Accounts": s.accounts, + //"ValidatorInfos": s.validatorInfos, + "NameRegistry": s.nameReg, + }) +} + +/* //XXX Done by tendermint core +// Mutates the block in place and updates it with new state hash. +func (s *State) ComputeBlockStateHash(block *types.Block) error { + sCopy := s.Copy() + // sCopy has no event cache in it, so this won't fire events + err := execBlock(sCopy, block, types.PartSetHeader{}) + if err != nil { + return err + } + // Set block.StateHash + block.StateHash = sCopy.Hash() + return nil +} +*/ + +func (s *State) SetDB(db dbm.DB) { + s.DB = db +} + +//------------------------------------- +// State.params + +func (s *State) GetGasLimit() int64 { + return 1000000 // TODO +} + +// State.params +//------------------------------------- +// State.accounts + +// Returns nil if account does not exist with given address. +// Implements Statelike +func (s *State) GetAccount(address []byte) *acm.Account { + _, accBytes, _ := s.accounts.Get(address) + if accBytes == nil { + return nil + } + return acm.DecodeAccount(accBytes) +} + +// The account is copied before setting, so mutating it +// afterwards has no side effects. +// Implements Statelike +func (s *State) UpdateAccount(account *acm.Account) bool { + return s.accounts.Set(account.Address, acm.EncodeAccount(account)) +} + +// Implements Statelike +func (s *State) RemoveAccount(address []byte) bool { + _, removed := s.accounts.Remove(address) + return removed +} + +// The returned Account is a copy, so mutating it +// has no side effects. +func (s *State) GetAccounts() merkle.Tree { + return s.accounts.Copy() +} + +// Set the accounts tree +func (s *State) SetAccounts(accounts merkle.Tree) { + s.accounts = accounts +} + +// State.accounts +//------------------------------------- +// State.validators + +// XXX: now handled by tendermint core + +/* + +// The returned ValidatorInfo is a copy, so mutating it +// has no side effects. +func (s *State) GetValidatorInfo(address []byte) *types.ValidatorInfo { + _, valInfo := s.validatorInfos.Get(address) + if valInfo == nil { + return nil + } + return valInfo.(*types.ValidatorInfo).Copy() +} + +// Returns false if new, true if updated. +// The valInfo is copied before setting, so mutating it +// afterwards has no side effects. +func (s *State) SetValidatorInfo(valInfo *types.ValidatorInfo) (updated bool) { + return s.validatorInfos.Set(valInfo.Address, valInfo.Copy()) +} + +func (s *State) GetValidatorInfos() merkle.Tree { + return s.validatorInfos.Copy() +} + +func (s *State) unbondValidator(val *types.Validator) { + // Move validator to UnbondingValidators + val, removed := s.BondedValidators.Remove(val.Address) + if !removed { + PanicCrisis("Couldn't remove validator for unbonding") + } + val.UnbondHeight = s.LastBlockHeight + 1 + added := s.UnbondingValidators.Add(val) + if !added { + PanicCrisis("Couldn't add validator for unbonding") + } +} + +func (s *State) rebondValidator(val *types.Validator) { + // Move validator to BondingValidators + val, removed := s.UnbondingValidators.Remove(val.Address) + if !removed { + PanicCrisis("Couldn't remove validator for rebonding") + } + val.BondHeight = s.LastBlockHeight + 1 + added := s.BondedValidators.Add(val) + if !added { + PanicCrisis("Couldn't add validator for rebonding") + } +} + +func (s *State) releaseValidator(val *types.Validator) { + // Update validatorInfo + valInfo := s.GetValidatorInfo(val.Address) + if valInfo == nil { + PanicSanity("Couldn't find validatorInfo for release") + } + valInfo.ReleasedHeight = s.LastBlockHeight + 1 + s.SetValidatorInfo(valInfo) + + // Send coins back to UnbondTo outputs + accounts, err := getOrMakeOutputs(s, nil, valInfo.UnbondTo) + if err != nil { + PanicSanity("Couldn't get or make unbondTo accounts") + } + adjustByOutputs(accounts, valInfo.UnbondTo) + for _, acc := range accounts { + s.UpdateAccount(acc) + } + + // Remove validator from UnbondingValidators + _, removed := s.UnbondingValidators.Remove(val.Address) + if !removed { + PanicCrisis("Couldn't remove validator for release") + } +} + +func (s *State) destroyValidator(val *types.Validator) { + // Update validatorInfo + valInfo := s.GetValidatorInfo(val.Address) + if valInfo == nil { + PanicSanity("Couldn't find validatorInfo for release") + } + valInfo.DestroyedHeight = s.LastBlockHeight + 1 + valInfo.DestroyedAmount = val.VotingPower + s.SetValidatorInfo(valInfo) + + // Remove validator + _, removed := s.BondedValidators.Remove(val.Address) + if !removed { + _, removed := s.UnbondingValidators.Remove(val.Address) + if !removed { + PanicCrisis("Couldn't remove validator for destruction") + } + } + +} + +// Set the validator infos tree +func (s *State) SetValidatorInfos(validatorInfos merkle.Tree) { + s.validatorInfos = validatorInfos +} + +*/ + +// State.validators +//------------------------------------- +// State.storage + +func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { + storage = merkle.NewIAVLTree(1024, s.DB) + storage.Load(hash) + return storage +} + +// State.storage +//------------------------------------- +// State.nameReg + +func (s *State) GetNameRegEntry(name string) *txs.NameRegEntry { + _, valueBytes, _ := s.nameReg.Get([]byte(name)) + if valueBytes == nil { + return nil + } + + return DecodeNameRegEntry(valueBytes) +} + +func DecodeNameRegEntry(entryBytes []byte) *txs.NameRegEntry { + var n int + var err error + value := NameRegCodec.Decode(bytes.NewBuffer(entryBytes), &n, &err) + return value.(*txs.NameRegEntry) +} + +func (s *State) UpdateNameRegEntry(entry *txs.NameRegEntry) bool { + w := new(bytes.Buffer) + var n int + var err error + NameRegCodec.Encode(entry, w, &n, &err) + return s.nameReg.Set([]byte(entry.Name), w.Bytes()) +} + +func (s *State) RemoveNameRegEntry(name string) bool { + _, removed := s.nameReg.Remove([]byte(name)) + return removed +} + +func (s *State) GetNames() merkle.Tree { + return s.nameReg.Copy() +} + +// Set the name reg tree +func (s *State) SetNameReg(nameReg merkle.Tree) { + s.nameReg = nameReg +} + +func NameRegEncoder(o interface{}, w io.Writer, n *int, err *error) { + wire.WriteBinary(o.(*txs.NameRegEntry), w, n, err) +} + +func NameRegDecoder(r io.Reader, n *int, err *error) interface{} { + return wire.ReadBinary(&txs.NameRegEntry{}, r, txs.MaxDataLength, n, err) +} + +var NameRegCodec = wire.Codec{ + Encode: NameRegEncoder, + Decode: NameRegDecoder, +} + +// State.nameReg +//------------------------------------- + +// Implements events.Eventable. Typically uses events.EventCache +func (s *State) SetFireable(evc events.Fireable) { + s.evc = evc +} + +//----------------------------------------------------------------------------- +// Genesis + +func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*GenesisDoc, *State) { + jsonBlob, err := ioutil.ReadFile(genDocFile) + if err != nil { + Exit(Fmt("Couldn't read GenesisDoc file: %v", err)) + } + genDoc := GenesisDocFromJSON(jsonBlob) + return genDoc, MakeGenesisState(db, genDoc) +} + +func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { + if len(genDoc.Validators) == 0 { + Exit(Fmt("The genesis file has no validators")) + } + + if genDoc.GenesisTime.IsZero() { + genDoc.GenesisTime = time.Now() + } + + // Make accounts state tree + accounts := merkle.NewIAVLTree(defaultAccountsCacheCapacity, db) + for _, genAcc := range genDoc.Accounts { + perm := ptypes.ZeroAccountPermissions + if genAcc.Permissions != nil { + perm = *genAcc.Permissions + } + acc := &acm.Account{ + Address: genAcc.Address, + PubKey: nil, + Sequence: 0, + Balance: genAcc.Amount, + Permissions: perm, + } + accounts.Set(acc.Address, acm.EncodeAccount(acc)) + } + + // global permissions are saved as the 0 address + // so they are included in the accounts tree + globalPerms := ptypes.DefaultAccountPermissions + if genDoc.Params != nil && genDoc.Params.GlobalPermissions != nil { + globalPerms = *genDoc.Params.GlobalPermissions + // XXX: make sure the set bits are all true + // Without it the HasPermission() functions will fail + globalPerms.Base.SetBit = ptypes.AllPermFlags + } + + permsAcc := &acm.Account{ + Address: ptypes.GlobalPermissionsAddress, + PubKey: nil, + Sequence: 0, + Balance: 1337, + Permissions: globalPerms, + } + accounts.Set(permsAcc.Address, acm.EncodeAccount(permsAcc)) + + // Make validatorInfos state tree && validators slice + /* + validatorInfos := merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) + validators := make([]*types.Validator, len(genDoc.Validators)) + for i, val := range genDoc.Validators { + pubKey := val.PubKey + address := pubKey.Address() + + // Make ValidatorInfo + valInfo := &types.ValidatorInfo{ + Address: address, + PubKey: pubKey, + UnbondTo: make([]*types.TxOutput, len(val.UnbondTo)), + FirstBondHeight: 0, + FirstBondAmount: val.Amount, + } + for i, unbondTo := range val.UnbondTo { + valInfo.UnbondTo[i] = &types.TxOutput{ + Address: unbondTo.Address, + Amount: unbondTo.Amount, + } + } + validatorInfos.Set(address, valInfo) + + // Make validator + validators[i] = &types.Validator{ + Address: address, + PubKey: pubKey, + VotingPower: val.Amount, + } + } + */ + + // Make namereg tree + nameReg := merkle.NewIAVLTree(0, db) + // TODO: add names, contracts to genesis.json + + // IAVLTrees must be persisted before copy operations. + accounts.Save() + //validatorInfos.Save() + nameReg.Save() + + return &State{ + DB: db, + ChainID: genDoc.ChainID, + LastBlockHeight: 0, + LastBlockHash: nil, + LastBlockParts: types.PartSetHeader{}, + LastBlockTime: genDoc.GenesisTime, + //BondedValidators: types.NewValidatorSet(validators), + //LastBondedValidators: types.NewValidatorSet(nil), + //UnbondingValidators: types.NewValidatorSet(nil), + accounts: accounts, + //validatorInfos: validatorInfos, + nameReg: nameReg, + } +} diff --git a/manager/eris-mint/state/state_test.go b/manager/eris-mint/state/state_test.go new file mode 100644 index 0000000000000000000000000000000000000000..eba2226bba2854d62851fea37896fb5648811b74 --- /dev/null +++ b/manager/eris-mint/state/state_test.go @@ -0,0 +1,708 @@ +package state + +import ( + "bytes" + "testing" + //"time" + + "github.com/tendermint/tendermint/config/tendermint_test" + // tmtypes "github.com/tendermint/tendermint/types" + + "github.com/eris-ltd/eris-db/txs" +) + +func init() { + tendermint_test.ResetConfig("state_test") +} + +func execTxWithState(state *State, tx txs.Tx, runCall bool) error { + cache := NewBlockCache(state) + if err := ExecTx(cache, tx, runCall, nil); err != nil { + return err + } else { + cache.Sync() + return nil + } +} + +func execTxWithStateNewBlock(state *State, tx txs.Tx, runCall bool) error { + if err := execTxWithState(state, tx, runCall); err != nil { + return err + } + + state.LastBlockHeight += 1 + return nil +} + +func TestCopyState(t *testing.T) { + // Generate a random state + s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000) + s0Hash := s0.Hash() + if len(s0Hash) == 0 { + t.Error("Expected state hash") + } + + // Check hash of copy + s0Copy := s0.Copy() + if !bytes.Equal(s0Hash, s0Copy.Hash()) { + t.Error("Expected state copy hash to be the same") + } + + // Mutate the original; hash should change. + acc0Address := privAccounts[0].PubKey.Address() + acc := s0.GetAccount(acc0Address) + acc.Balance += 1 + + // The account balance shouldn't have changed yet. + if s0.GetAccount(acc0Address).Balance == acc.Balance { + t.Error("Account balance changed unexpectedly") + } + + // Setting, however, should change the balance. + s0.UpdateAccount(acc) + if s0.GetAccount(acc0Address).Balance != acc.Balance { + t.Error("Account balance wasn't set") + } + + // Now that the state changed, the hash should change too. + if bytes.Equal(s0Hash, s0.Hash()) { + t.Error("Expected state hash to have changed") + } + + // The s0Copy shouldn't have changed though. + if !bytes.Equal(s0Hash, s0Copy.Hash()) { + t.Error("Expected state copy hash to have not changed") + } +} + +/* +func makeBlock(t *testing.T, state *State, validation *tmtypes.Commit, txs []txs.Tx) *tmtypes.Block { + if validation == nil { + validation = &tmtypes.Commit{} + } + block := &tmtypes.Block{ + Header: &tmtypes.Header{ + ChainID: state.ChainID, + Height: state.LastBlockHeight + 1, + Time: state.LastBlockTime.Add(time.Minute), + NumTxs: len(txs), + LastBlockHash: state.LastBlockHash, + LastBlockParts: state.LastBlockParts, + AppHash: nil, + }, + LastCommit: validation, + Data: &tmtypes.Data{ + Txs: txs, + }, + } + block.FillHeader() + + // Fill in block StateHash + err := state.ComputeBlockStateHash(block) + if err != nil { + t.Error("Error appending initial block:", err) + } + if len(block.Header.StateHash) == 0 { + t.Error("Expected StateHash but got nothing.") + } + + return block +} + +func TestGenesisSaveLoad(t *testing.T) { + + // Generate a state, save & load it. + s0, _, _ := RandGenesisState(10, true, 1000, 5, true, 1000) + + // Make complete block and blockParts + block := makeBlock(t, s0, nil, nil) + blockParts := block.MakePartSet() + + // Now append the block to s0. + err := ExecBlock(s0, block, blockParts.Header()) + if err != nil { + t.Error("Error appending initial block:", err) + } + + // Save s0 + s0.Save() + + // Sanity check s0 + //s0.DB.(*dbm.MemDB).Print() + if s0.BondedValidators.TotalVotingPower() == 0 { + t.Error("s0 BondedValidators TotalVotingPower should not be 0") + } + if s0.LastBlockHeight != 1 { + t.Error("s0 LastBlockHeight should be 1, got", s0.LastBlockHeight) + } + + // Load s1 + s1 := LoadState(s0.DB) + + // Compare height & blockHash + if s0.LastBlockHeight != s1.LastBlockHeight { + t.Error("LastBlockHeight mismatch") + } + if !bytes.Equal(s0.LastBlockHash, s1.LastBlockHash) { + t.Error("LastBlockHash mismatch") + } + + // Compare state merkle trees + if s0.BondedValidators.Size() != s1.BondedValidators.Size() { + t.Error("BondedValidators Size mismatch") + } + if s0.BondedValidators.TotalVotingPower() != s1.BondedValidators.TotalVotingPower() { + t.Error("BondedValidators TotalVotingPower mismatch") + } + if !bytes.Equal(s0.BondedValidators.Hash(), s1.BondedValidators.Hash()) { + t.Error("BondedValidators hash mismatch") + } + if s0.UnbondingValidators.Size() != s1.UnbondingValidators.Size() { + t.Error("UnbondingValidators Size mismatch") + } + if s0.UnbondingValidators.TotalVotingPower() != s1.UnbondingValidators.TotalVotingPower() { + t.Error("UnbondingValidators TotalVotingPower mismatch") + } + if !bytes.Equal(s0.UnbondingValidators.Hash(), s1.UnbondingValidators.Hash()) { + t.Error("UnbondingValidators hash mismatch") + } + if !bytes.Equal(s0.accounts.Hash(), s1.accounts.Hash()) { + t.Error("Accounts mismatch") + } + if !bytes.Equal(s0.validatorInfos.Hash(), s1.validatorInfos.Hash()) { + t.Error("Accounts mismatch") + } +} +*/ + +func TestTxSequence(t *testing.T) { + + state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) + acc0PubKey := privAccounts[0].PubKey + acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) + + // Test a variety of sequence numbers for the tx. + // The tx should only pass when i == 1. + for i := -1; i < 3; i++ { + sequence := acc0.Sequence + i + tx := txs.NewSendTx() + tx.AddInputWithNonce(acc0PubKey, 1, sequence) + tx.AddOutput(acc1.Address, 1) + tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) + stateCopy := state.Copy() + err := execTxWithState(stateCopy, tx, true) + if i == 1 { + // Sequence is good. + if err != nil { + t.Errorf("Expected good sequence to pass: %v", err) + } + // Check acc.Sequence. + newAcc0 := stateCopy.GetAccount(acc0.Address) + if newAcc0.Sequence != sequence { + t.Errorf("Expected account sequence to change to %v, got %v", + sequence, newAcc0.Sequence) + } + } else { + // Sequence is bad. + if err == nil { + t.Errorf("Expected bad sequence to fail") + } + // Check acc.Sequence. (shouldn't have changed) + newAcc0 := stateCopy.GetAccount(acc0.Address) + if newAcc0.Sequence != acc0.Sequence { + t.Errorf("Expected account sequence to not change from %v, got %v", + acc0.Sequence, newAcc0.Sequence) + } + } + } +} + +func TestNameTxs(t *testing.T) { + state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + + txs.MinNameRegistrationPeriod = 5 + startingBlock := state.LastBlockHeight + + // try some bad names. these should all fail + names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyessurprised", "no spaces please"} + data := "something about all this just doesn't feel right." + fee := int64(1000) + numDesiredBlocks := 5 + for _, name := range names { + amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[0]) + + if err := execTxWithState(state, tx, true); err == nil { + t.Fatalf("Expected invalid name error from %s", name) + } + } + + // try some bad data. these should all fail + name := "hold_it_chum" + datas := []string{"cold&warm", "!@#$%^&*()", "<<<>>>>", "because why would you ever need a ~ or a & or even a % in a json file? make your case and we'll talk"} + for _, data := range datas { + amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[0]) + + if err := execTxWithState(state, tx, true); err == nil { + t.Fatalf("Expected invalid data error from %s", data) + } + } + + validateEntry := func(t *testing.T, entry *txs.NameRegEntry, name, data string, addr []byte, expires int) { + + if entry == nil { + t.Fatalf("Could not find name %s", name) + } + if bytes.Compare(entry.Owner, addr) != 0 { + t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr) + } + if data != entry.Data { + t.Fatalf("Wrong data. Got %s expected %s", entry.Data, data) + } + if name != entry.Name { + t.Fatalf("Wrong name. Got %s expected %s", entry.Name, name) + } + if expires != entry.Expires { + t.Fatalf("Wrong expiry. Got %d, expected %d", entry.Expires, expires) + } + } + + // try a good one, check data, owner, expiry + name = "@looking_good/karaoke_bar.broadband" + data = "on this side of neptune there are 1234567890 people: first is OMNIVORE+-3. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')" + amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[0]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry := state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks) + + // fail to update it as non-owner, in same block + tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[1]) + if err := execTxWithState(state, tx, true); err == nil { + t.Fatal("Expected error") + } + + // update it as owner, just to increase expiry, in same block + // NOTE: we have to resend the data or it will clear it (is this what we want?) + tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[0]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*2) + + // update it as owner, just to increase expiry, in next block + tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[0]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3) + + // fail to update it as non-owner + state.LastBlockHeight = entry.Expires - 1 + tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[1]) + if err := execTxWithState(state, tx, true); err == nil { + t.Fatal("Expected error") + } + + // once expires, non-owner succeeds + state.LastBlockHeight = entry.Expires + tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[1]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) + + // update it as new owner, with new data (longer), but keep the expiry! + data = "In the beginning there was no thing, not even the beginning. It hadn't been here, no there, nor for that matter anywhere, not especially because it had not to even exist, let alone to not. Nothing especially odd about that." + oldCredit := amt - fee + numDesiredBlocks = 10 + amt = fee + (int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - oldCredit) + tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[1]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) + + // test removal + amt = fee + data = "" + tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[1]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + if entry != nil { + t.Fatal("Expected removed entry to be nil") + } + + // create entry by key0, + // test removal by key1 after expiry + name = "looking_good/karaoke_bar" + data = "some data" + amt = fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[0]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, state.LastBlockHeight+numDesiredBlocks) + state.LastBlockHeight = entry.Expires + + amt = fee + data = "" + tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(state.ChainID, privAccounts[1]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + if entry != nil { + t.Fatal("Expected removed entry to be nil") + } +} + +// TODO: test overflows. +// TODO: test for unbonding validators. +func TestTxs(t *testing.T) { + + state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + + //val0 := state.GetValidatorInfo(privValidators[0].Address) + acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) + acc0PubKey := privAccounts[0].PubKey + acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) + + // SendTx. + { + state := state.Copy() + tx := &txs.SendTx{ + Inputs: []*txs.TxInput{ + &txs.TxInput{ + Address: acc0.Address, + Amount: 1, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + }, + Outputs: []*txs.TxOutput{ + &txs.TxOutput{ + Address: acc1.Address, + Amount: 1, + }, + }, + } + + tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing send transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-1 != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-1, newAcc0.Balance) + } + newAcc1 := state.GetAccount(acc1.Address) + if acc1.Balance+1 != newAcc1.Balance { + t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", + acc1.Balance+1, newAcc1.Balance) + } + } + + // CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more + { + state := state.Copy() + newAcc1 := state.GetAccount(acc1.Address) + newAcc1.Code = []byte{0x60} + state.UpdateAccount(newAcc1) + tx := &txs.CallTx{ + Input: &txs.TxInput{ + Address: acc0.Address, + Amount: 1, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + Address: acc1.Address, + GasLimit: 10, + } + + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-1 != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-1, newAcc0.Balance) + } + newAcc1 = state.GetAccount(acc1.Address) + if acc1.Balance+1 != newAcc1.Balance { + t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", + acc1.Balance+1, newAcc1.Balance) + } + } + + // NameTx. + { + entryName := "satoshi" + entryData := ` +A purely peer-to-peer version of electronic cash would allow online +payments to be sent directly from one party to another without going through a +financial institution. Digital signatures provide part of the solution, but the main +benefits are lost if a trusted third party is still required to prevent double-spending. +We propose a solution to the double-spending problem using a peer-to-peer network. +The network timestamps transactions by hashing them into an ongoing chain of +hash-based proof-of-work, forming a record that cannot be changed without redoing +the proof-of-work. The longest chain not only serves as proof of the sequence of +events witnessed, but proof that it came from the largest pool of CPU power. As +long as a majority of CPU power is controlled by nodes that are not cooperating to +attack the network, they'll generate the longest chain and outpace attackers. The +network itself requires minimal structure. Messages are broadcast on a best effort +basis, and nodes can leave and rejoin the network at will, accepting the longest +proof-of-work chain as proof of what happened while they were gone ` + entryAmount := int64(10000) + + state := state.Copy() + tx := &txs.NameTx{ + Input: &txs.TxInput{ + Address: acc0.Address, + Amount: entryAmount, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + Name: entryName, + Data: entryData, + } + + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-entryAmount != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-entryAmount, newAcc0.Balance) + } + entry := state.GetNameRegEntry(entryName) + if entry == nil { + t.Errorf("Expected an entry but got nil") + } + if entry.Data != entryData { + t.Errorf("Wrong data stored") + } + + // test a bad string + tx.Data = string([]byte{0, 1, 2, 3, 127, 128, 129, 200, 251}) + tx.Input.Sequence += 1 + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + err = execTxWithState(state, tx, true) + if _, ok := err.(txs.ErrTxInvalidString); !ok { + t.Errorf("Expected invalid string error. Got: %s", err.Error()) + } + } + + // BondTx. TODO + /* + { + state := state.Copy() + tx := &txs.BondTx{ + PubKey: acc0PubKey.(crypto.PubKeyEd25519), + Inputs: []*txs.TxInput{ + &txs.TxInput{ + Address: acc0.Address, + Amount: 1, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + }, + UnbondTo: []*txs.TxOutput{ + &txs.TxOutput{ + Address: acc0.Address, + Amount: 1, + }, + }, + } + tx.Signature = privAccounts[0].Sign(state.ChainID, tx).(crypto.SignatureEd25519) + tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing bond transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if newAcc0.Balance != acc0.Balance-1 { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-1, newAcc0.Balance) + } + _, acc0Val := state.BondedValidators.GetByAddress(acc0.Address) + if acc0Val == nil { + t.Errorf("acc0Val not present") + } + if acc0Val.BondHeight != state.LastBlockHeight+1 { + t.Errorf("Unexpected bond height. Expected %v, got %v", + state.LastBlockHeight, acc0Val.BondHeight) + } + if acc0Val.VotingPower != 1 { + t.Errorf("Unexpected voting power. Expected %v, got %v", + acc0Val.VotingPower, acc0.Balance) + } + if acc0Val.Accum != 0 { + t.Errorf("Unexpected accum. Expected 0, got %v", + acc0Val.Accum) + } + } */ + + // TODO UnbondTx. + +} + +func TestSuicide(t *testing.T) { + + state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + + acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) + acc0PubKey := privAccounts[0].PubKey + acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) + acc2 := state.GetAccount(privAccounts[2].Address) + sendingAmount, refundedBalance, oldBalance := int64(1), acc1.Balance, acc2.Balance + + newAcc1 := state.GetAccount(acc1.Address) + + // store 0x1 at 0x1, push an address, then suicide :) + contractCode := []byte{0x60, 0x01, 0x60, 0x01, 0x55, 0x73} + contractCode = append(contractCode, acc2.Address...) + contractCode = append(contractCode, 0xff) + newAcc1.Code = contractCode + state.UpdateAccount(newAcc1) + + // send call tx with no data, cause suicide + tx := txs.NewCallTxWithNonce(acc0PubKey, acc1.Address, nil, sendingAmount, 1000, 0, acc0.Sequence+1) + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + + // we use cache instead of execTxWithState so we can run the tx twice + cache := NewBlockCache(state) + if err := ExecTx(cache, tx, true, nil); err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + + // if we do it again, we won't get an error, but the suicide + // shouldn't happen twice and the caller should lose fee + tx.Input.Sequence += 1 + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + if err := ExecTx(cache, tx, true, nil); err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + + // commit the block + cache.Sync() + + // acc2 should receive the sent funds and the contracts balance + newAcc2 := state.GetAccount(acc2.Address) + newBalance := sendingAmount + refundedBalance + oldBalance + if newAcc2.Balance != newBalance { + t.Errorf("Unexpected newAcc2 balance. Expected %v, got %v", + newAcc2.Balance, newBalance) + } + newAcc1 = state.GetAccount(acc1.Address) + if newAcc1 != nil { + t.Errorf("Expected account to be removed") + } + +} + +/* TODO +func TestAddValidator(t *testing.T) { + + // Generate a state, save & load it. + s0, privAccounts, privValidators := RandGenesisState(10, false, 1000, 1, false, 1000) + + // The first privAccount will become a validator + acc0 := privAccounts[0] + bondTx := &txs.BondTx{ + PubKey: acc0.PubKey.(account.PubKeyEd25519), + Inputs: []*txs.TxInput{ + &txs.TxInput{ + Address: acc0.Address, + Amount: 1000, + Sequence: 1, + PubKey: acc0.PubKey, + }, + }, + UnbondTo: []*txs.TxOutput{ + &txs.TxOutput{ + Address: acc0.Address, + Amount: 1000, + }, + }, + } + bondTx.Signature = acc0.Sign(s0.ChainID, bondTx).(account.SignatureEd25519) + bondTx.Inputs[0].Signature = acc0.Sign(s0.ChainID, bondTx) + + // Make complete block and blockParts + block0 := makeBlock(t, s0, nil, []txs.Tx{bondTx}) + block0Parts := block0.MakePartSet() + + // Sanity check + if s0.BondedValidators.Size() != 1 { + t.Error("Expected there to be 1 validators before bondTx") + } + + // Now append the block to s0. + err := ExecBlock(s0, block0, block0Parts.Header()) + if err != nil { + t.Error("Error appending initial block:", err) + } + + // Must save before further modification + s0.Save() + + // Test new validator set + if s0.BondedValidators.Size() != 2 { + t.Error("Expected there to be 2 validators after bondTx") + } + + // The validation for the next block should only require 1 signature + // (the new validator wasn't active for block0) + precommit0 := &txs.Vote{ + Height: 1, + Round: 0, + Type: txs.VoteTypePrecommit, + BlockHash: block0.Hash(), + BlockPartsHeader: block0Parts.Header(), + } + privValidators[0].SignVote(s0.ChainID, precommit0) + + block1 := makeBlock(t, s0, + &txs.Validation{ + Precommits: []*txs.Vote{ + precommit0, + }, + }, nil, + ) + block1Parts := block1.MakePartSet() + err = ExecBlock(s0, block1, block1Parts.Header()) + if err != nil { + t.Error("Error appending secondary block:", err) + } +} +*/ diff --git a/manager/eris-mint/state/tx_cache.go b/manager/eris-mint/state/tx_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..292c35314966cbb450e45d0dd55da500462da673 --- /dev/null +++ b/manager/eris-mint/state/tx_cache.go @@ -0,0 +1,200 @@ +package state + +import ( + acm "github.com/eris-ltd/eris-db/account" + "github.com/eris-ltd/eris-db/evm" + ptypes "github.com/eris-ltd/eris-db/permission/types" // for GlobalPermissionAddress ... + "github.com/eris-ltd/eris-db/txs" + + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" +) + +type TxCache struct { + backend *BlockCache + accounts map[Word256]vmAccountInfo + storages map[Tuple256]Word256 +} + +func NewTxCache(backend *BlockCache) *TxCache { + return &TxCache{ + backend: backend, + accounts: make(map[Word256]vmAccountInfo), + storages: make(map[Tuple256]Word256), + } +} + +//------------------------------------- +// TxCache.account + +func (cache *TxCache) GetAccount(addr Word256) *vm.Account { + acc, removed := cache.accounts[addr].unpack() + if removed { + return nil + } else if acc == nil { + acc2 := cache.backend.GetAccount(addr.Postfix(20)) + if acc2 != nil { + return toVMAccount(acc2) + } + } + return acc +} + +func (cache *TxCache) UpdateAccount(acc *vm.Account) { + addr := acc.Address + _, removed := cache.accounts[addr].unpack() + if removed { + PanicSanity("UpdateAccount on a removed account") + } + cache.accounts[addr] = vmAccountInfo{acc, false} +} + +func (cache *TxCache) RemoveAccount(acc *vm.Account) { + addr := acc.Address + _, removed := cache.accounts[addr].unpack() + if removed { + PanicSanity("RemoveAccount on a removed account") + } + cache.accounts[addr] = vmAccountInfo{acc, true} +} + +// Creates a 20 byte address and bumps the creator's nonce. +func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account { + + // Generate an address + nonce := creator.Nonce + creator.Nonce += 1 + + addr := LeftPadWord256(NewContractAddress(creator.Address.Postfix(20), int(nonce))) + + // Create account from address. + account, removed := cache.accounts[addr].unpack() + if removed || account == nil { + account = &vm.Account{ + Address: addr, + Balance: 0, + Code: nil, + Nonce: 0, + Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress256).Permissions, + Other: vmAccountOther{ + PubKey: nil, + StorageRoot: nil, + }, + } + cache.accounts[addr] = vmAccountInfo{account, false} + return account + } else { + // either we've messed up nonce handling, or sha3 is broken + PanicSanity(Fmt("Could not create account, address already exists: %X", addr)) + return nil + } +} + +// TxCache.account +//------------------------------------- +// TxCache.storage + +func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 { + // Check cache + value, ok := cache.storages[Tuple256{addr, key}] + if ok { + return value + } + + // Load from backend + return cache.backend.GetStorage(addr, key) +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) { + _, removed := cache.accounts[addr].unpack() + if removed { + PanicSanity("SetStorage() on a removed account") + } + cache.storages[Tuple256{addr, key}] = value +} + +// TxCache.storage +//------------------------------------- + +// These updates do not have to be in deterministic order, +// the backend is responsible for ordering updates. +func (cache *TxCache) Sync() { + + // Remove or update storage + for addrKey, value := range cache.storages { + addr, key := Tuple256Split(addrKey) + cache.backend.SetStorage(addr, key, value) + } + + // Remove or update accounts + for addr, accInfo := range cache.accounts { + acc, removed := accInfo.unpack() + if removed { + cache.backend.RemoveAccount(addr.Postfix(20)) + } else { + cache.backend.UpdateAccount(toStateAccount(acc)) + } + } +} + +//----------------------------------------------------------------------------- + +// Convenience function to return address of new contract +func NewContractAddress(caller []byte, nonce int) []byte { + return txs.NewContractAddress(caller, nonce) +} + +// Converts backend.Account to vm.Account struct. +func toVMAccount(acc *acm.Account) *vm.Account { + return &vm.Account{ + Address: LeftPadWord256(acc.Address), + Balance: acc.Balance, + Code: acc.Code, // This is crazy. + Nonce: int64(acc.Sequence), + Permissions: acc.Permissions, // Copy + Other: vmAccountOther{ + PubKey: acc.PubKey, + StorageRoot: acc.StorageRoot, + }, + } +} + +// Converts vm.Account to backend.Account struct. +func toStateAccount(acc *vm.Account) *acm.Account { + var pubKey crypto.PubKey + var storageRoot []byte + if acc.Other != nil { + pubKey, storageRoot = acc.Other.(vmAccountOther).unpack() + } + + return &acm.Account{ + Address: acc.Address.Postfix(20), + PubKey: pubKey, + Balance: acc.Balance, + Code: acc.Code, + Sequence: int(acc.Nonce), + StorageRoot: storageRoot, + Permissions: acc.Permissions, // Copy + } +} + +// Everything in acmAccount that doesn't belong in +// exported vmAccount fields. +type vmAccountOther struct { + PubKey crypto.PubKey + StorageRoot []byte +} + +func (accOther vmAccountOther) unpack() (crypto.PubKey, []byte) { + return accOther.PubKey, accOther.StorageRoot +} + +type vmAccountInfo struct { + account *vm.Account + removed bool +} + +func (accInfo vmAccountInfo) unpack() (*vm.Account, bool) { + return accInfo.account, accInfo.removed +} diff --git a/manager/eris-mint/state/tx_cache_test.go b/manager/eris-mint/state/tx_cache_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bc0a3c0f16e9784d18c38bdd6068a800b2fc0418 --- /dev/null +++ b/manager/eris-mint/state/tx_cache_test.go @@ -0,0 +1,23 @@ +package state + +import ( + "bytes" + "testing" + + + "github.com/tendermint/go-wire" +) + +func TestStateToFromVMAccount(t *testing.T) { + acmAcc1, _ := stypes.RandAccount(true, 456) + vmAcc := toVMAccount(acmAcc1) + acmAcc2 := toStateAccount(vmAcc) + + acmAcc1Bytes := wire.BinaryBytes(acmAcc1) + acmAcc2Bytes := wire.BinaryBytes(acmAcc2) + if !bytes.Equal(acmAcc1Bytes, acmAcc2Bytes) { + t.Errorf("Unexpected account wire bytes\n%X vs\n%X", + acmAcc1Bytes, acmAcc2Bytes) + } + +} diff --git a/manager/eris-mint/state/types/genesis.go b/manager/eris-mint/state/types/genesis.go new file mode 100644 index 0000000000000000000000000000000000000000..806d701b29b2ce0daf3e9f5d99862917e7f8b5db --- /dev/null +++ b/manager/eris-mint/state/types/genesis.go @@ -0,0 +1,61 @@ +package types + +import ( + "time" + + ptypes "github.com/eris-ltd/eris-db/permission/types" + . "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +//------------------------------------------------------------ +// we store the gendoc in the db + +var GenDocKey = []byte("GenDocKey") + +//------------------------------------------------------------ +// core types for a genesis definition + +type BasicAccount struct { + Address []byte `json:"address"` + Amount int64 `json:"amount"` +} + +type GenesisAccount struct { + Address []byte `json:"address"` + Amount int64 `json:"amount"` + Name string `json:"name"` + Permissions *ptypes.AccountPermissions `json:"permissions"` +} + +type GenesisValidator struct { + PubKey crypto.PubKey `json:"pub_key"` + Amount int64 `json:"amount"` + Name string `json:"name"` + UnbondTo []BasicAccount `json:"unbond_to"` +} + +type GenesisParams struct { + GlobalPermissions *ptypes.AccountPermissions `json:"global_permissions"` +} + +type GenesisDoc struct { + GenesisTime time.Time `json:"genesis_time"` + ChainID string `json:"chain_id"` + Params *GenesisParams `json:"params"` + Accounts []GenesisAccount `json:"accounts"` + Validators []GenesisValidator `json:"validators"` +} + +//------------------------------------------------------------ +// Make genesis state from file + +func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) { + var err error + wire.ReadJSONPtr(&genState, jsonBlob, &err) + if err != nil { + Exit(Fmt("Couldn't read GenesisDoc: %v", err)) + } + return +}