From 74ec890aa9a977ab6c080863e3a5128c9c1e6d11 Mon Sep 17 00:00:00 2001 From: Casey Kuhlman <casey@erisindustries.com> Date: Fri, 3 Jul 2015 16:27:13 +0200 Subject: [PATCH] ensure docker image complies with eris platform stds; update to current permissions head --- .dockerignore | 8 + DOCKER/Dockerfile | 58 +- DOCKER/build.sh | 13 + DOCKER/docker_build.sh | 13 - DOCKER/docker_run.sh | 19 - DOCKER/start.sh | 7 + Godeps/Godeps.json | 44 +- .../tendermint/tendermint/account/account.go | 6 +- .../tendermint/tendermint/node/node.go | 22 +- .../tendermint/tendermint/p2p/connection.go | 3 +- .../tendermint/tendermint/p2p/switch.go | 11 + .../tendermint/permission/types/errors.go | 44 + .../permission/types/permissions.go | 290 ++++ .../tendermint/permission/types/snatives.go | 24 + .../tendermint/rpc/core/accounts.go | 3 + .../tendermint/tendermint/rpc/core/net.go | 23 +- .../tendermint/tendermint/rpc/core/pipe.go | 5 + .../tendermint/rpc/server/handlers.go | 58 +- .../tendermint/tendermint/state/execution.go | 202 ++- .../tendermint/tendermint/state/genesis.go | 65 +- .../tendermint/state/genesis_test.go | 86 ++ .../tendermint/state/permissions_test.go | 1259 +++++++++++++++++ .../tendermint/tendermint/state/state.go | 25 +- .../tendermint/tendermint/state/test.go | 21 +- .../tendermint/tendermint/state/tx_cache.go | 6 + .../tendermint/tendermint/vm/native.go | 12 +- .../tendermint/tendermint/vm/opcodes.go | 2 +- .../tendermint/tendermint/vm/snative.go | 259 ++++ .../tendermint/tendermint/vm/stack.go | 1 + .../tendermint/tendermint/vm/test/vm_test.go | 6 +- .../tendermint/tendermint/vm/types.go | 3 + .../github.com/tendermint/tendermint/vm/vm.go | 74 +- circle.yml | 20 + erisdb/pipe/blockchain.go | 4 +- 34 files changed, 2546 insertions(+), 150 deletions(-) create mode 100644 .dockerignore create mode 100755 DOCKER/build.sh delete mode 100755 DOCKER/docker_build.sh delete mode 100755 DOCKER/docker_run.sh create mode 100755 DOCKER/start.sh create mode 100644 Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/errors.go create mode 100644 Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/permissions.go create mode 100644 Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/snatives.go create mode 100644 Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis_test.go create mode 100644 Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go create mode 100644 Godeps/_workspace/src/github.com/tendermint/tendermint/vm/snative.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e9d9ecf8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +.project +run.sh +build +Vagrantfile +README.md +circle.yml +api.md \ No newline at end of file diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index 4ee8372f..d58e8f8a 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -1,5 +1,6 @@ -# Pull eris/data -FROM eris/data +# Pull base image. +FROM eris/base +MAINTAINER Eris Industries <support@erisindustries.com> # Set the env variables to non-interactive ENV DEBIAN_FRONTEND noninteractive @@ -14,10 +15,51 @@ RUN apt-get update && \ libgmp3-dev && \ rm -rf /var/lib/apt/lists/* +# install the wrapper/start script +COPY DOCKER/start.sh /usr/local/bin/erisdb-wrapper + # set the repo and install tendermint -ENV repo /go/src/github.com/eris-ltd/eris-db -ADD . $repo -WORKDIR $repo -RUN cd ./cmd/erisdb && go install -USER eris -ENTRYPOINT ["erisdb"] +ENV REPO github.com/tendermint/tendermint +ENV BRANCH permissions +RUN mkdir --parents $GOPATH/src/$REPO +WORKDIR $GOPATH/src/$REPO +RUN git clone https://$REPO . && \ + git checkout $BRANCH && \ + make && \ + rm ./build/barak && \ + mv ./build/* /usr/local/bin/ + +# set the repo and install erisdb +ENV REPO $GOPATH/src/github.com/eris-ltd/eris-db +COPY . $REPO +WORKDIR $REPO +RUN cd ./cmd/erisdb && \ + go build && \ + mv erisdb /usr/local/bin/ && \ + cd ../erisdbss && \ + go build && \ + mv erisdbss /usr/local/bin/ + +# set the repo and install mint-client +ENV REPO github.com/eris-ltd/mint-client +ENV BRANCH master +RUN mkdir --parents $GOPATH/src/$REPO +WORKDIR $GOPATH/src/$REPO +RUN git clone https://$REPO . && \ + git checkout $BRANCH && \ + go install ./... && \ + mv $GOPATH/bin/mint* /usr/local/bin && \ + mv ./mint-client /usr/local/bin/ + +# cleanup +RUN rm -rf $GOPATH/src/* + +# persist data, set user +VOLUME /home/$USER/.eris +WORKDIR /home/$USER/.eris +USER $USER +RUN mkdir --parents /home/$USER/.eris/blockchains/tendermint +ENV TMROOT /home/$USER/.eris/blockchains/tendermint + +# run tendermint +CMD ["erisdb-wrapper"] diff --git a/DOCKER/build.sh b/DOCKER/build.sh new file mode 100755 index 00000000..0ed34a66 --- /dev/null +++ b/DOCKER/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +base=github.com/eris-ltd/eris-db +repo=$GOPATH/src/$base +branch=${ERISDB_BUILD_BRANCH:=docker} +start=`pwd` + +cd $repo +git checkout $branch +git pull origin + +docker build -t eris/erisdb:0.10 -f DOCKER/Dockerfile . + +cd $start \ No newline at end of file diff --git a/DOCKER/docker_build.sh b/DOCKER/docker_build.sh deleted file mode 100755 index c914d70b..00000000 --- a/DOCKER/docker_build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/sh - -CUR=`pwd` - -if [ $CUR = "$GOPATH/src/github.com/eris-ltd/erisdb" ]; then - - docker build -t eris-db -f DOCKER/Dockerfile . -else - docker build -t eris-db -f Dockerfile .. -fi - - - diff --git a/DOCKER/docker_run.sh b/DOCKER/docker_run.sh deleted file mode 100755 index 41311b33..00000000 --- a/DOCKER/docker_run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# Using ~/.eris on drive. -ERIS_PATH=$HOME/.eris -CONTAINER="eris-db" -RUNNING=$(docker inspect --format="{{ .State.Running }}" eris-db) -mkdir -v -p $ERIS_PATH - -# Run in the terminal and attach on start. -if [ "$RUNNING" == "true" ]; then - echo "Container 'eris-db' already running. Exiting." - exit 1 -elif [ "$RUNNING" == "false" ]; then - echo "Container 'eris-db' found. Starting." - docker start --attach=true eris-db -else - echo "Container 'eris-db' not found. Creating." - docker run --name eris-db -v $ERIS_PATH:/home/eris/.eris -p 46656:46656 -p 46657:46657 -p 1337:1337 eris-db -fi diff --git a/DOCKER/start.sh b/DOCKER/start.sh new file mode 100755 index 00000000..eb3457cd --- /dev/null +++ b/DOCKER/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash +if [[ $FAST_SYNC ]]; then + tendermint node --fast_sync +else + tendermint node +fi + diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index d3c54ebe..30e3d8c3 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -85,83 +85,87 @@ }, { "ImportPath": "github.com/tendermint/tendermint/account", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/alert", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/binary", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/blockchain", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/common", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/config", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/consensus", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/db", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/events", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/logger", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/mempool", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/merkle", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/node", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/p2p", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" + }, + { + "ImportPath": "github.com/tendermint/tendermint/permission/types", + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/rpc/core", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/rpc/server", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/rpc/types", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/state", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/types", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tendermint/tendermint/vm", - "Rev": "46bd0e5d51692347647bb2e250fd0e637fc63a11" + "Rev": "1d61dbc86b4b9e084b28b6b52412ab0a3ceab2e6" }, { "ImportPath": "github.com/tommy351/gin-cors", diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/account/account.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/account/account.go index 1c1492ab..99c8cba5 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/account/account.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/account/account.go @@ -7,6 +7,7 @@ import ( "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/merkle" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" ) // Signable is an interface for all signable things. @@ -42,6 +43,8 @@ type Account struct { Balance uint64 `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 { @@ -50,7 +53,8 @@ func (acc *Account) Copy() *Account { } func (acc *Account) String() string { - return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot) + // return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot) + return fmt.Sprintf("Account{%X:%v C:%v S:%X P:(%s)}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot, acc.Permissions) } func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) { diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go index d94b01df..43e7b2b3 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/node/node.go @@ -1,6 +1,7 @@ package node import ( + "bytes" "fmt" "math/rand" "net" @@ -10,6 +11,7 @@ import ( "strings" "time" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" bc "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/blockchain" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/consensus" @@ -42,6 +44,7 @@ type Node struct { consensusState *consensus.ConsensusState consensusReactor *consensus.ConsensusReactor privValidator *sm.PrivValidator + genDoc *sm.GenesisDoc } func NewNode() *Node { @@ -52,9 +55,24 @@ func NewNode() *Node { // Get State stateDB := dbm.GetDB("state") state := sm.LoadState(stateDB) + var genDoc *sm.GenesisDoc if state == nil { - state = sm.MakeGenesisStateFromFile(stateDB, config.GetString("genesis_file")) + genDoc, state = sm.MakeGenesisStateFromFile(stateDB, config.GetString("genesis_file")) state.Save() + // write the gendoc to db + buf, n, err := new(bytes.Buffer), new(int64), new(error) + binary.WriteJSON(genDoc, buf, n, err) + stateDB.Set(sm.GenDocKey, buf.Bytes()) + if *err != nil { + panic(Fmt("Unable to write gendoc to db: %v", err)) + } + } else { + genDocBytes := stateDB.Get(sm.GenDocKey) + err := new(error) + binary.ReadJSON(&genDoc, genDocBytes, err) + if *err != nil { + panic(Fmt("Unable to read gendoc from db: %v", err)) + } } // add the chainid to the global config config.Set("chain_id", state.ChainID) @@ -115,6 +133,7 @@ func NewNode() *Node { consensusState: consensusState, consensusReactor: consensusReactor, privValidator: privValidator, + genDoc: genDoc, } } @@ -184,6 +203,7 @@ func (n *Node) StartRPC() { core.SetMempoolReactor(n.mempoolReactor) core.SetSwitch(n.sw) core.SetPrivValidator(n.privValidator) + core.SetGenDoc(n.genDoc) listenAddr := config.GetString("rpc_laddr") mux := http.NewServeMux() diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/connection.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/connection.go index d6119b36..8e48d958 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/connection.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/connection.go @@ -11,8 +11,7 @@ import ( "time" flow "github.com/eris-ltd/eris-db/Godeps/_workspace/src/code.google.com/p/mxk/go1/flowcontrol" - //"github.com/tendermint/log15" - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" //"github.com/tendermint/log15" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" ) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/switch.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/switch.go index 3755151c..c44974f7 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/switch.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/p2p/switch.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net" + "strconv" "sync/atomic" "time" @@ -165,6 +166,16 @@ func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) (*Peer, er return nil, err } + // the peerNodeInfo is not verified, + // so we overwrite the IP with that from the conn + // and if we dialed out, the port too + // everything else we just have to trust + ip, port, _ := net.SplitHostPort(conn.RemoteAddr().String()) + peerNodeInfo.Host = ip + if outbound { + porti, _ := strconv.Atoi(port) + peerNodeInfo.P2PPort = uint16(porti) + } peer := newPeer(conn, peerNodeInfo, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError) // Add the peer to .peers diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/errors.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/errors.go new file mode 100644 index 00000000..5a170a7c --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/errors.go @@ -0,0 +1,44 @@ +package types + +import ( + "fmt" +) + +//------------------------------------------------------------------------------------------------ +// Some errors + +// permission number out of bounds +type ErrInvalidPermission PermFlag + +func (e ErrInvalidPermission) Error() string { + return fmt.Sprintf("invalid permission %d", e) +} + +// unknown string for permission +type ErrInvalidPermissionString string + +func (e ErrInvalidPermissionString) Error() string { + return fmt.Sprintf("invalid permission '%s'", e) +} + +// already exists (err on add) +type ErrPermissionExists string + +func (e ErrPermissionExists) Error() string { + return fmt.Sprintf("permission '%s' already exists", e) +} + +// unknown string for snative contract +type ErrInvalidSNativeString string + +func (e ErrInvalidSNativeString) Error() string { + return fmt.Sprintf("invalid snative contract '%s'", e) +} + +// set=false. This error should be caught and the global +// value fetched for the permission by the caller +type ErrValueNotSet PermFlag + +func (e ErrValueNotSet) Error() string { + return fmt.Sprintf("the value for permission %d is not set", e) +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/permissions.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/permissions.go new file mode 100644 index 00000000..c530a42e --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/permissions.go @@ -0,0 +1,290 @@ +package types + +import ( + "fmt" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" + "reflect" +) + +//------------------------------------------------------------------------------------------------ + +var ( + GlobalPermissionsAddress = Zero256[:20] + GlobalPermissionsAddress256 = Zero256 + DougAddress = append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte("THISISDOUG")...) + DougAddress256 = LeftPadWord256(DougAddress) +) + +// A particular permission +type PermFlag uint64 + +// Base permission references are like unix (the index is already bit shifted) +const ( + Root PermFlag = 1 << iota // 1 + Send // 2 + Call // 4 + CreateContract // 8 + CreateAccount // 16 + Bond // 32 + Name // 64 + + DefaultBBPB = Send | Call | CreateContract | CreateAccount | Bond | Name + + // XXX: must be adjusted if base perms added/removed + NumBasePermissions uint = 7 + TopBasePermission PermFlag = 1 << (NumBasePermissions - 1) + AllBasePermissions PermFlag = TopBasePermission | (TopBasePermission - 1) + + AllSet PermFlag = AllBasePermissions | AllSNativePermissions +) + +// should have same ordering as above +type BasePermissionsString struct { + Root bool `json:"root,omitempty"` + Send bool `json:"send,omitempty"` + Call bool `json:"call,omitempty"` + CreateContract bool `json:"create_contract,omitempty"` + CreateAccount bool `json:"create_account,omitempty"` + Bond bool `json:"bond,omitempty"` + Name bool `json:"name,omitempty"` +} + +//--------------------------------------------------------------------------------------------- + +// Base chain permissions struct +type BasePermissions struct { + // bit array with "has"/"doesn't have" for each permission + Perms PermFlag `json:"perms"` + + // bit array with "set"/"not set" for each permission (not-set should fall back to global) + SetBit PermFlag `json:"set"` +} + +func NewBasePermissions() *BasePermissions { + return &BasePermissions{0, 0} +} + +// Get a permission value. ty should be a power of 2. +// ErrValueNotSet is returned if the permission's set bit is off, +// and should be caught by caller so the global permission can be fetched +func (p *BasePermissions) Get(ty PermFlag) (bool, error) { + if ty == 0 { + return false, ErrInvalidPermission(ty) + } + if p.SetBit&ty == 0 { + return false, ErrValueNotSet(ty) + } + return p.Perms&ty > 0, nil +} + +// Set a permission bit. Will set the permission's set bit to true. +func (p *BasePermissions) Set(ty PermFlag, value bool) error { + if ty == 0 { + return ErrInvalidPermission(ty) + } + p.SetBit |= ty + if value { + p.Perms |= ty + } else { + p.Perms &= ^ty + } + return nil +} + +// Set the permission's set bit to false +func (p *BasePermissions) Unset(ty PermFlag) error { + if ty == 0 { + return ErrInvalidPermission(ty) + } + p.SetBit &= ^ty + return nil +} + +// Check if the permission is set +func (p *BasePermissions) IsSet(ty PermFlag) bool { + if ty == 0 { + return false + } + return p.SetBit&ty > 0 +} + +func (p *BasePermissions) Copy() *BasePermissions { + if p == nil { + return nil + } + return &BasePermissions{ + Perms: p.Perms, + SetBit: p.SetBit, + } +} + +func (p *BasePermissions) String() string { + return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit) +} + +//--------------------------------------------------------------------------------------------- + +type AccountPermissions struct { + Base *BasePermissions `json:"base"` + Roles []string `json:"roles"` +} + +func NewAccountPermissions() *AccountPermissions { + return &AccountPermissions{ + Base: NewBasePermissions(), + Roles: []string{}, + } +} + +// Returns true if the role is found +func (aP *AccountPermissions) HasRole(role string) bool { + role = string(LeftPadBytes([]byte(role), 32)) + for _, r := range aP.Roles { + if r == role { + return true + } + } + return false +} + +// Returns true if the role is added, and false if it already exists +func (aP *AccountPermissions) AddRole(role string) bool { + role = string(LeftPadBytes([]byte(role), 32)) + for _, r := range aP.Roles { + if r == role { + return false + } + } + aP.Roles = append(aP.Roles, role) + return true +} + +// Returns true if the role is removed, and false if it is not found +func (aP *AccountPermissions) RmRole(role string) bool { + role = string(LeftPadBytes([]byte(role), 32)) + for i, r := range aP.Roles { + if r == role { + post := []string{} + if len(aP.Roles) > i+1 { + post = aP.Roles[i+1:] + } + aP.Roles = append(aP.Roles[:i], post...) + return true + } + } + return false +} + +func (aP *AccountPermissions) Copy() *AccountPermissions { + if aP == nil { + return nil + } + r := make([]string, len(aP.Roles)) + copy(r, aP.Roles) + return &AccountPermissions{ + Base: aP.Base.Copy(), + Roles: r, + } +} + +func NewDefaultAccountPermissions() *AccountPermissions { + return &AccountPermissions{ + Base: &BasePermissions{ + Perms: DefaultBBPB, + SetBit: AllSet, + }, + Roles: []string{}, + } +} + +//--------------------------------------------------------------------------------------------- +// Utilities to make bitmasks human readable + +func NewDefaultAccountPermissionsString() BasePermissionsString { + return BasePermissionsString{ + Root: false, + Bond: true, + Send: true, + Call: true, + Name: true, + CreateAccount: true, + CreateContract: true, + } +} + +func AccountPermissionsFromStrings(perms *BasePermissionsString, roles []string) (*AccountPermissions, error) { + base := NewBasePermissions() + permRv := reflect.ValueOf(perms) + for i := uint(0); i < uint(permRv.NumField()); i++ { + v := permRv.Field(int(i)).Bool() + base.Set(1<<i, v) + } + + aP := &AccountPermissions{ + Base: base, + Roles: make([]string, len(roles)), + } + copy(aP.Roles, roles) + return aP, nil +} + +func AccountPermissionsToStrings(aP *AccountPermissions) (*BasePermissionsString, []string, error) { + perms := new(BasePermissionsString) + permsRv := reflect.ValueOf(perms).Elem() + for i := uint(0); i < NumBasePermissions; i++ { + pf := PermFlag(1 << i) + if aP.Base.IsSet(pf) { + // won't err if the bit is set + v, _ := aP.Base.Get(pf) + f := permsRv.Field(int(i)) + f.SetBool(v) + } + } + roles := make([]string, len(aP.Roles)) + copy(roles, aP.Roles) + return perms, roles, nil +} + +func PermFlagToString(pf PermFlag) (perm string, err error) { + switch pf { + case Root: + perm = "root" + case Send: + perm = "send" + case Call: + perm = "call" + case CreateContract: + perm = "create_contract" + case CreateAccount: + perm = "create_account" + case Bond: + perm = "bond" + case Name: + perm = "name" + default: + err = fmt.Errorf("Unknown permission flag %b", pf) + } + return +} + +func PermStringToFlag(perm string) (pf PermFlag, err error) { + switch perm { + case "root": + pf = Root + case "send": + pf = Send + case "call": + pf = Call + case "create_contract": + pf = CreateContract + case "create_account": + pf = CreateAccount + case "bond": + pf = Bond + case "name": + pf = Name + default: + err = fmt.Errorf("Unknown permission %s", perm) + } + return +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/snatives.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/snatives.go new file mode 100644 index 00000000..d4e0ee4b --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types/snatives.go @@ -0,0 +1,24 @@ +package types + +const ( + // first 32 bits of BasePermission are for chain, second 32 are for snative + FirstSNativePerm PermFlag = 1 << 32 +) + +// we need to reset iota with no const block +const ( + // each snative has an associated permission flag + HasBasePerm PermFlag = FirstSNativePerm << iota + SetBasePerm + UnsetBasePerm + SetGlobalPerm + ClearBasePerm + HasRole + AddRole + RmRole + + // XXX: must be adjusted if snative's added/removed + NumSNativePermissions uint = 8 + TopSNativePermission PermFlag = FirstSNativePerm << (NumSNativePermissions - 1) + AllSNativePermissions PermFlag = (TopSNativePermission | (TopSNativePermission - 1)) &^ (FirstSNativePerm - 1) +) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/accounts.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/accounts.go index c2c9daf0..d256242c 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/accounts.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/accounts.go @@ -4,6 +4,7 @@ import ( "fmt" acm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" ctypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/types" ) @@ -15,6 +16,7 @@ func GetAccount(address []byte) (*acm.Account, error) { cache := mempoolReactor.Mempool.GetCache() account := cache.GetAccount(address) if account == nil { + // XXX: shouldn't we return "account not found"? account = &acm.Account{ Address: address, PubKey: nil, @@ -22,6 +24,7 @@ func GetAccount(address []byte) (*acm.Account, error) { Balance: 0, Code: nil, StorageRoot: nil, + Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress).Permissions, } } return account, nil diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/net.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/net.go index 22af3e9a..715e27fd 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/net.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/net.go @@ -1,9 +1,6 @@ package core import ( - "io/ioutil" - - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" dbm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" ctypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/types" sm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state" @@ -12,9 +9,14 @@ import ( //----------------------------------------------------------------------------- +// cache the genesis state +var genesisState *sm.State + func Status() (*ctypes.ResponseStatus, error) { db := dbm.NewMemDB() - genesisState := sm.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) + if genesisState == nil { + genesisState = sm.MakeGenesisState(db, genDoc) + } genesisHash := genesisState.Hash() latestHeight := blockStore.Height() var ( @@ -63,19 +65,6 @@ func NetInfo() (*ctypes.ResponseNetInfo, error) { //----------------------------------------------------------------------------- -// cache the genesis structure -var genDoc *sm.GenesisDoc - func Genesis() (*sm.GenesisDoc, error) { - if genDoc == nil { - b, err := ioutil.ReadFile(config.GetString("genesis_file")) - if err != nil { - return nil, err - } - binary.ReadJSON(&genDoc, b, &err) - if err != nil { - return nil, err - } - } return genDoc, nil } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/pipe.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/pipe.go index a2c115f9..92a50dc1 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/pipe.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/core/pipe.go @@ -14,6 +14,7 @@ var consensusReactor *consensus.ConsensusReactor var mempoolReactor *mempl.MempoolReactor var p2pSwitch *p2p.Switch var privValidator *state.PrivValidator +var genDoc *state.GenesisDoc // cache the genesis structure func SetBlockStore(bs *bc.BlockStore) { blockStore = bs @@ -38,3 +39,7 @@ func SetSwitch(sw *p2p.Switch) { func SetPrivValidator(pv *state.PrivValidator) { privValidator = pv } + +func SetGenDoc(doc *state.GenesisDoc) { + genDoc = doc +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/server/handlers.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/server/handlers.go index 9bf9a908..4205bbea 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/server/handlers.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/server/handlers.go @@ -5,15 +5,17 @@ import ( "encoding/json" "errors" "fmt" - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/gorilla/websocket" - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" - "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" - . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/types" "io/ioutil" "net/http" "reflect" + "sort" "sync/atomic" "time" + + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/gorilla/websocket" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/binary" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/rpc/types" ) func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc) { @@ -87,6 +89,14 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc { return } b, _ := ioutil.ReadAll(r.Body) + + // if its an empty request (like from a browser), + // just display a list of functions + if len(b) == 0 { + writeListOfEndpoints(w, r, funcMap) + return + } + var request RPCRequest err := json.Unmarshal(b, &request) if err != nil { @@ -393,3 +403,43 @@ func unreflectResponse(returns []reflect.Value) (interface{}, error) { } return returns[0].Interface(), nil } + +// writes a list of available rpc endpoints as an html page +func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) { + noArgNames := []string{} + argNames := []string{} + for name, funcData := range funcMap { + if len(funcData.args) == 0 { + noArgNames = append(noArgNames, name) + } else { + argNames = append(argNames, name) + } + } + sort.Strings(noArgNames) + sort.Strings(argNames) + buf := new(bytes.Buffer) + buf.WriteString("<html><body>") + buf.WriteString("<br>Available endpoints:<br>") + + for _, name := range noArgNames { + link := fmt.Sprintf("http://%s/%s", r.Host, name) + buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link)) + } + + buf.WriteString("<br>Endpoints that require arguments:<br>") + for _, name := range argNames { + link := fmt.Sprintf("http://%s/%s?", r.Host, name) + funcData := funcMap[name] + for i, argName := range funcData.argNames { + link += argName + "=_" + if i < len(funcData.argNames)-1 { + link += "&" + } + } + buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link)) + } + buf.WriteString("</body></html>") + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(200) + w.Write(buf.Bytes()) +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/execution.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/execution.go index 438086dd..e59d92b2 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/execution.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/execution.go @@ -8,6 +8,7 @@ import ( "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ... "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" ) @@ -163,7 +164,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade // account.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 getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) { +func getInputs(state AccountGetter, ins []*types.TxInput) (map[string]*account.Account, error) { accounts := map[string]*account.Account{} for _, in := range ins { // Account shouldn't be duplicated @@ -180,6 +181,16 @@ func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types. } accounts[string(in.Address)] = acc } + return accounts, nil +} + +func getOrMakeOutputs(state AccountGetter, accounts map[string]*account.Account, outs []*types.TxOutput) (map[string]*account.Account, error) { + if accounts == nil { + accounts = make(map[string]*account.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 { @@ -188,11 +199,18 @@ func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types. 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 = &account.Account{ - Address: out.Address, - PubKey: nil, - Sequence: 0, - Balance: 0, + Address: out.Address, + PubKey: nil, + Sequence: 0, + Balance: 0, + Permissions: ptypes.NewAccountPermissions(), } } accounts[string(out.Address)] = acc @@ -293,7 +311,6 @@ func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutpu // If the tx is invalid, an error will be returned. // Unlike ExecBlock(), state will not be altered. func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Fireable) error { - // TODO: do something with fees fees := uint64(0) _s := blockCache.State() // hack to access validators and block height @@ -301,10 +318,23 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // Exec tx switch tx := tx_.(type) { case *types.SendTx: - accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs) + 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 := account.SignBytes(_s.ChainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { @@ -348,6 +378,18 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) return types.ErrTxInvalidAddress } + + createAccount := len(tx.Address) == 0 + if createAccount { + if !hasCreateContractPermission(blockCache, inAcc) { + return fmt.Errorf("Account %X does not have Create 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.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) @@ -364,7 +406,6 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return types.ErrTxInsufficientFunds } - createAccount := len(tx.Address) == 0 if !createAccount { // Validate output if len(tx.Address) != 20 { @@ -374,6 +415,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // this 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 + // it may also be nil if its an snative (not a "real" account) outAcc = blockCache.GetAccount(tx.Address) } @@ -400,26 +442,31 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea } ) - // Maybe create a new callee account if - // this transaction is creating a new contract. + // get or create callee if !createAccount { + if 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 account and call with another - // you have to wait a block to avoid a re-ordering attack - // that will take your fees - inAcc.Balance -= tx.Fee - blockCache.UpdateAccount(inAcc) - if outAcc == nil { - log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) + // check if its an snative + if _, ok := vm.RegisteredSNativeContracts[LeftPadWord256(tx.Address)]; ok { + // set the outAcc (simply a placeholder until we reach the call) + outAcc = &account.Account{Address: tx.Address} } else { - log.Debug(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address)) + // 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 account and call with another + // you have to wait a block to avoid a re-ordering attack + // that will take your fees + inAcc.Balance -= tx.Fee + blockCache.UpdateAccount(inAcc) + if outAcc == nil { + log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) + } else { + log.Debug(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address)) + } + return types.ErrTxInvalidAddress } - return types.ErrTxInvalidAddress - } callee = toVMAccount(outAcc) code = callee.Code @@ -431,12 +478,16 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea } log.Debug(Fmt("Code for this contract: %X", code)) - txCache.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe. - txCache.UpdateAccount(callee) // because we adjusted by input above. + txCache.UpdateAccount(caller) // because we bumped nonce + txCache.UpdateAccount(callee) // so the txCache knows about the callee and the create and/or transfer takes effect + vmach := vm.NewVM(txCache, params, caller.Address, account.HashSignBytes(_s.ChainID, tx)) vmach.SetFireable(evc) - // NOTE: Call() transfers the value from caller to callee iff call succeeds. + vmach.EnablePermissions() // permission checks on CALL/CREATE + vmach.EnableSNatives() // allows calls to snatives (with permission checks) + + // NOTE: Call() transfers the value from caller to callee iff call succeeds. ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) exception := "" if err != nil { @@ -487,6 +538,10 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) return types.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.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) @@ -599,11 +654,32 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // add funds, merge UnbondTo outputs, and unbond validator. return errors.New("Adding coins to existing validators not yet supported") } - accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil) + + 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 := account.SignBytes(_s.ChainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { @@ -754,3 +830,73 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea panic("Unknown Tx type") } } + +//--------------------------------------------------------------- + +// Get permission on an account or fall back to global value +func HasPermission(state AccountGetter, acc *account.Account, perm ptypes.PermFlag) bool { + if perm > ptypes.AllBasePermissions { + panic("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 + } + + v, err := acc.Permissions.Base.Get(perm) + if _, ok := err.(ptypes.ErrValueNotSet); ok { + if state == nil { + panic("All known global permissions should be set!") + } + return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm) + } + return v +} + +// TODO: for debug log the failed accounts +func hasSendPermission(state AccountGetter, accs map[string]*account.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.Send) { + return false + } + } + return true +} + +func hasNamePermission(state AccountGetter, acc *account.Account) bool { + return HasPermission(state, acc, ptypes.Name) +} + +func hasCallPermission(state AccountGetter, acc *account.Account) bool { + return HasPermission(state, acc, ptypes.Call) +} + +func hasCreateContractPermission(state AccountGetter, acc *account.Account) bool { + return HasPermission(state, acc, ptypes.CreateContract) +} + +func hasCreateAccountPermission(state AccountGetter, accs map[string]*account.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.CreateAccount) { + return false + } + } + return true +} + +func hasBondPermission(state AccountGetter, acc *account.Account) bool { + return HasPermission(state, acc, ptypes.Bond) +} + +func hasBondOrSendPermission(state AccountGetter, accs map[string]*account.Account) bool { + for _, acc := range accs { + if !HasPermission(state, acc, ptypes.Bond) { + if !HasPermission(state, acc, ptypes.Send) { + return false + } + } + } + return true +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis.go index fcb51f08..2fda4ba0 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis.go @@ -9,27 +9,52 @@ import ( . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" dbm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/merkle" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" ) -type GenesisAccount struct { +//------------------------------------------------------------ +// 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 uint64 `json:"amount"` } +type GenesisAccount struct { + Address []byte `json:"address"` + Amount uint64 `json:"amount"` + Name string `json:"name"` + Permissions *ptypes.AccountPermissions `json:"permissions"` +} + type GenesisValidator struct { PubKey account.PubKeyEd25519 `json:"pub_key"` Amount uint64 `json:"amount"` - UnbondTo []GenesisAccount `json:"unbond_to"` + 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 binary.ReadJSON(&genState, jsonBlob, &err) @@ -39,13 +64,13 @@ func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) { return } -func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) *State { +func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*GenesisDoc, *State) { jsonBlob, err := ioutil.ReadFile(genDocFile) if err != nil { panic(Fmt("Couldn't read GenesisDoc file: %v", err)) } genDoc := GenesisDocFromJSON(jsonBlob) - return MakeGenesisState(db, genDoc) + return genDoc, MakeGenesisState(db, genDoc) } func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { @@ -60,15 +85,39 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { // Make accounts state tree accounts := merkle.NewIAVLTree(binary.BasicCodec, account.AccountCodec, defaultAccountsCacheCapacity, db) for _, genAcc := range genDoc.Accounts { + perm := ptypes.NewDefaultAccountPermissions() + if genAcc.Permissions != nil { + perm = genAcc.Permissions + } acc := &account.Account{ - Address: genAcc.Address, - PubKey: nil, - Sequence: 0, - Balance: genAcc.Amount, + Address: genAcc.Address, + PubKey: nil, + Sequence: 0, + Balance: genAcc.Amount, + Permissions: perm, } accounts.Set(acc.Address, acc) } + // global permissions are saved as the 0 address + // so they are included in the accounts tree + globalPerms := ptypes.NewDefaultAccountPermissions() + 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.AllSet + } + + permsAcc := &account.Account{ + Address: ptypes.GlobalPermissionsAddress, + PubKey: nil, + Sequence: 0, + Balance: 1337, + Permissions: globalPerms, + } + accounts.Set(permsAcc.Address, permsAcc) + // Make validatorInfos state tree && validators slice validatorInfos := merkle.NewIAVLTree(binary.BasicCodec, ValidatorInfoCodec, 0, db) validators := make([]*Validator, len(genDoc.Validators)) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis_test.go new file mode 100644 index 00000000..eadd1738 --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/genesis_test.go @@ -0,0 +1,86 @@ +package state + +import ( + "bytes" + "encoding/hex" + "fmt" + "testing" + + tdb "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" +) + +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 uint64 = 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": [1,"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/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go new file mode 100644 index 00000000..7b4a482d --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go @@ -0,0 +1,1259 @@ +package state + +import ( + "bytes" + "fmt" + "strconv" + "testing" + "time" + + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" + dbm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" + "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" +) + +/* +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) []*account.PrivAccount { + accounts := []*account.PrivAccount{} + for i := 0; i < n; i++ { + secret := []byte("mysecret" + strconv.Itoa(i)) + user := account.GenPrivAccountFromSecret(secret) + accounts = append(accounts, user) + } + return accounts +} + +var ( + PermsAllFalse = ptypes.NewAccountPermissions() +) + +func newBaseGenDoc(globalPerm, accountPerm *ptypes.AccountPermissions) GenesisDoc { + genAccounts := []GenesisAccount{} + for _, u := range user[:5] { + genAccounts = append(genAccounts, GenesisAccount{ + Address: u.Address, + Amount: 1000000, + Permissions: accountPerm.Copy(), + }) + } + + return GenesisDoc{ + GenesisTime: time.Now(), + ChainID: chainID, + Params: &GenesisParams{ + GlobalPermissions: globalPerm, + }, + Accounts: genAccounts, + Validators: []GenesisValidator{ + GenesisValidator{ + PubKey: user[0].PubKey.(account.PubKeyEd25519), + Amount: 10, + UnbondTo: []BasicAccount{ + BasicAccount{ + Address: user[0].Address, + }, + }, + }, + }, + } +} + +func TestSendFails(t *testing.T) { + stateDB := dbm.GetDB("state") + 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 := types.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 = types.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 = types.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 = types.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") + 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 := types.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 = types.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") + 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, _ := types.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, _ = types.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, _ = types.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, _ = types.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, _ = types.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, _ = types.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") + 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 := types.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 = types.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") + 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("##### SIMPLE CONTRACT") + + // create simple contract + simpleContractAddr := NewContractAddress(user[0].Address, 100) + simpleAcc := &account.Account{ + Address: simpleContractAddr, + Balance: 0, + Code: []byte{0x60}, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + st.UpdateAccount(simpleAcc) + + // A single input, having the permission, should succeed + tx, _ := types.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("##### CALL TO SIMPLE CONTRACT (FAIL)") + + // create contract that calls the simple contract + contractCode := callContractCode(simpleContractAddr) + caller1ContractAddr := NewContractAddress(user[0].Address, 101) + caller1Acc := &account.Account{ + Address: caller1ContractAddr, + Balance: 0, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // call to contract that calls simple contract - with perm + fmt.Println("##### 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, _ = types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(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("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") + + contractCode2 := callContractCode(caller1ContractAddr) + caller2ContractAddr := NewContractAddress(user[0].Address, 102) + caller2Acc := &account.Account{ + Address: caller2ContractAddr, + Balance: 1000, + Code: contractCode2, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + caller1Acc.Permissions.Base.Set(ptypes.Call, false) + caller2Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + blockCache.UpdateAccount(caller2Acc) + + tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(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("##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") + + caller1Acc.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(caller1Acc) + + tx, _ = types.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(chainID, user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } +} + +func TestCreatePermission(t *testing.T) { + stateDB := dbm.GetDB("state") + 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("##### CREATE SIMPLE CONTRACT") + + contractCode := []byte{0x60} + createCode := wrapContractForCreate(contractCode) + + // A single input, having the permission, should succeed + tx, _ := types.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, uint64(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("##### CREATE FACTORY") + + contractCode = []byte{0x60} + createCode = wrapContractForCreate(contractCode) + factoryCode := createContractCode() + createFactoryCode := wrapContractForCreate(factoryCode) + + // A single input, having the permission, should succeed + tx, _ = types.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, uint64(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("###### CALL THE FACTORY (FAIL)") + + // A single input, having the permission, should succeed + tx, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) + tx.Sign(chainID, user[0]) + // we need to subscribe to the Receive event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(contractAddr)) // + if exception == "" { + t.Fatal("expected exception") + } + + //------------------------------ + // call the contract (should PASS) + fmt.Println("###### CALL THE FACTORY (PASS)") + + contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) + blockCache.UpdateAccount(contractAcc) + + // A single input, having the permission, should succeed + tx, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) + tx.Sign(chainID, user[0]) + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(contractAddr)) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + + //-------------------------------- + fmt.Println("##### CALL to empty address") + zeroAddr := LeftPadBytes([]byte{}, 20) + code := callContractCode(zeroAddr) + + contractAddr = NewContractAddress(user[0].Address, 110) + contractAcc = &account.Account{ + Address: contractAddr, + Balance: 1000, + Code: code, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + 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, _ = types.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 10000, 100) + tx.Sign(chainID, user[0]) + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(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!") + } +} + +func TestBondPermission(t *testing.T) { + stateDB := dbm.GetDB("state") + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st := MakeGenesisState(stateDB, &genDoc) + blockCache := NewBlockCache(st) + var bondAcc *account.Account + + //------------------------------ + // one bonder without permission should fail + tx, _ := types.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, _ = types.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, _ = types.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, _ = types.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, _ = types.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") + 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 := types.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 = types.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 = types.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 = types.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 = types.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 := &account.Account{ + Address: caller1ContractAddr, + Balance: 0, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + txCall, _ := types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(chainID, user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception := execTxWaitEvent(t, blockCache, txCall, types.EventStringAccReceive(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, _ = types.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(chainID, user[0]) + + // we need to subscribe to the Receive event to detect the exception + _, exception = execTxWaitEvent(t, blockCache, txCall, types.EventStringAccReceive(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + +} + +func TestSNativeCALL(t *testing.T) { + stateDB := dbm.GetDB("state") + 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 := &account.Account{ + Address: ptypes.DougAddress, + Balance: 0, + Code: nil, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: ptypes.NewAccountPermissions(), + } + doug.Permissions.Base.Set(ptypes.Call, true) + blockCache.UpdateAccount(doug) + + fmt.Println("#### hasBasePerm") + // hasBasePerm + snativeAddress, data := snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### setBasePerm") + // setBasePerm + snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false) + testSNativeCALLExpectPass(t, blockCache, doug, 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, data = snativePermTestInput("setBasePerm", user[3], ptypes.CreateContract, true) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### unsetBasePerm") + // unsetBasePerm + snativeAddress, data = snativePermTestInput("unsetBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + + fmt.Println("#### setGlobalPerm") + // setGlobalPerm + snativeAddress, data = snativePermTestInput("setGlobalPerm", user[3], ptypes.CreateContract, true) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, 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 + }) + + // clearBasePerm + // TODO + + fmt.Println("#### hasRole") + // hasRole + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "bumble") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### addRole") + // addRole + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + snativeAddress, data = snativeRoleTestInput("addRole", user[3], "chuck") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### rmRole") + // rmRole + snativeAddress, data = snativeRoleTestInput("rmRole", user[3], "chuck") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) +} + +func TestSNativeCallTx(t *testing.T) { + stateDB := dbm.GetDB("state") + 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 CallTx to SNative contracts + var doug *account.Account = nil + + fmt.Println("#### hasBasePerm") + // hasBasePerm + snativeAddress, data := snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### setBasePerm") + // setBasePerm + snativeAddress, data = snativePermTestInput("setBasePerm", user[3], ptypes.Bond, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.Bond, false) + testSNativeCALLExpectPass(t, blockCache, doug, 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, data = snativePermTestInput("setBasePerm", user[3], ptypes.CreateContract, true) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### unsetBasePerm") + // unsetBasePerm + snativeAddress, data = snativePermTestInput("unsetBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + + fmt.Println("#### setGlobalPerm") + // setGlobalPerm + snativeAddress, data = snativePermTestInput("setGlobalPerm", user[3], ptypes.CreateContract, true) + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativePermTestInput("hasBasePerm", user[3], ptypes.CreateContract, false) + testSNativeCALLExpectPass(t, blockCache, doug, 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 + }) + + // clearBasePerm + // TODO + + fmt.Println("#### hasRole") + // hasRole + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "bumble") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### addRole") + // addRole + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + snativeAddress, data = snativeRoleTestInput("addRole", user[3], "chuck") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, 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("#### rmRole") + // rmRole + snativeAddress, data = snativeRoleTestInput("rmRole", user[3], "chuck") + testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, data = snativeRoleTestInput("hasRole", user[3], "chuck") + testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) +} + +//------------------------------------------------------------------------------------- +// helpers + +// run ExecTx and wait for the Receive event on given addr +// returns the msg data and an error/exception +func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx types.Tx, eventid string) (interface{}, string) { + evsw := new(events.EventSwitch) + evsw.Start() + ch := make(chan interface{}) + evsw.AddListenerForEvent("test", eventid, func(msg interface{}) { + ch <- msg + }) + evc := events.NewEventCache(evsw) + go func() { + if err := ExecTx(blockCache, tx, true, evc); err != nil { + ch <- err.Error() + } + evc.Flush() + }() + msg := <-ch + switch ev := msg.(type) { + case types.EventMsgCallTx: + return ev, ev.Exception + case types.EventMsgCall: + 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, ensure the check funciton (f) succeeds +func testSNativeCALLExpectPass(t *testing.T, blockCache *BlockCache, doug *account.Account, snativeAddress, data []byte, f func([]byte) error) { + perm := vm.RegisteredSNativePermissions[LeftPadWord256(snativeAddress)] + var addr []byte + if doug != nil { + contractCode := callContractCode(snativeAddress) + doug.Code = contractCode + doug.Permissions.Base.Set(perm, true) + blockCache.UpdateAccount(doug) + addr = doug.Address + } else { + acc := blockCache.GetAccount(user[0].Address) + acc.Permissions.Base.Set(perm, true) + blockCache.UpdateAccount(acc) + addr = snativeAddress + } + tx, _ := types.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100) + tx.Sign(chainID, user[0]) + ev, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + evv := ev.(types.EventMsgCall) + ret := evv.Return + if err := f(ret); err != nil { + t.Fatal(err) + } +} + +// assumes the contract has not been given the permission. calls the it, it calls the snative, expects to fail +func testSNativeCALLExpectFail(t *testing.T, blockCache *BlockCache, doug *account.Account, snativeAddress, data []byte) { + var addr []byte + if doug != nil { + contractCode := callContractCode(snativeAddress) + doug.Code = contractCode + blockCache.UpdateAccount(doug) + addr = doug.Address + } else { + addr = snativeAddress + } + tx, _ := types.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100) + tx.Sign(chainID, user[0]) + fmt.Println("subscribing to", types.EventStringAccReceive(snativeAddress)) + _, exception := execTxWaitEvent(t, blockCache, tx, types.EventStringAccReceive(snativeAddress)) + if exception == "" { + t.Fatal("Expected exception") + } +} + +func boolToWord256(v bool) Word256 { + var vint byte + if v { + vint = 0x1 + } else { + vint = 0x0 + } + return LeftPadWord256([]byte{vint}) +} + +func snativePermTestInput(name string, user *account.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, data []byte) { + addr = LeftPadWord256([]byte(name)).Postfix(20) + switch name { + case "hasBasePerm", "unsetBasePerm": + data = LeftPadBytes(user.Address, 32) + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + case "setBasePerm": + data = LeftPadBytes(user.Address, 32) + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + data = append(data, boolToWord256(val).Bytes()...) + case "setGlobalPerm": + data = Uint64ToWord256(uint64(perm)).Bytes() + data = append(data, boolToWord256(val).Bytes()...) + case "clearBasePerm": + } + return +} + +func snativeRoleTestInput(name string, user *account.PrivAccount, role string) (addr []byte, data []byte) { + addr = LeftPadWord256([]byte(name)).Postfix(20) + data = LeftPadBytes(user.Address, 32) + data = append(data, LeftPadBytes([]byte(role), 32)...) + 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/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go index 68f28a89..0743a002 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/state.go @@ -142,6 +142,10 @@ func (s *State) SetBlockStateHash(block *types.Block) error { return nil } +func (s *State) SetDB(db dbm.DB) { + s.DB = db +} + //------------------------------------- // State.accounts @@ -175,6 +179,11 @@ 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 @@ -196,6 +205,10 @@ func (s *State) SetValidatorInfo(valInfo *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 *Validator) { // Move validator to UnbondingValidators val, removed := s.BondedValidators.Remove(val.Address) @@ -232,7 +245,7 @@ func (s *State) releaseValidator(val *Validator) { s.SetValidatorInfo(valInfo) // Send coins back to UnbondTo outputs - accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo) + accounts, err := getOrMakeOutputs(s, nil, valInfo.UnbondTo) if err != nil { panic("Couldn't get or make unbondTo accounts") } @@ -269,6 +282,11 @@ func (s *State) destroyValidator(val *Validator) { } +// Set the validator infos tree +func (s *State) SetValidatorInfos(validatorInfos merkle.Tree) { + s.validatorInfos = validatorInfos +} + // State.validators //------------------------------------- // State.storage @@ -305,6 +323,11 @@ 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 *int64, err *error) { binary.WriteBinary(o.(*types.NameRegEntry), w, n, err) } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go index 38a4cdc9..b9fae886 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/test.go @@ -7,6 +7,7 @@ import ( "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" dbm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/db" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" "io/ioutil" @@ -24,11 +25,13 @@ func Tempfile(prefix string) (*os.File, string) { func RandAccount(randBalance bool, minBalance uint64) (*account.Account, *account.PrivAccount) { privAccount := account.GenPrivAccount() + perms := ptypes.NewDefaultAccountPermissions() acc := &account.Account{ - Address: privAccount.PubKey.Address(), - PubKey: privAccount.PubKey, - Sequence: RandUint(), - Balance: minBalance, + Address: privAccount.PubKey.Address(), + PubKey: privAccount.PubKey, + Sequence: RandUint(), + Balance: minBalance, + Permissions: perms, } if randBalance { acc.Balance += uint64(RandUint32()) @@ -73,8 +76,9 @@ func RandGenesisState(numAccounts int, randBalance bool, minBalance uint64, numV for i := 0; i < numAccounts; i++ { account, privAccount := RandAccount(randBalance, minBalance) accounts[i] = GenesisAccount{ - Address: account.Address, - Amount: account.Balance, + Address: account.Address, + Amount: account.Balance, + Permissions: ptypes.NewDefaultAccountPermissions(), } privAccounts[i] = privAccount } @@ -85,7 +89,7 @@ func RandGenesisState(numAccounts int, randBalance bool, minBalance uint64, numV validators[i] = GenesisValidator{ PubKey: valInfo.PubKey, Amount: valInfo.FirstBondAmount, - UnbondTo: []GenesisAccount{ + UnbondTo: []BasicAccount{ { Address: valInfo.PubKey.Address(), Amount: valInfo.FirstBondAmount, @@ -100,6 +104,9 @@ func RandGenesisState(numAccounts int, randBalance bool, minBalance uint64, numV ChainID: "tendermint_test", Accounts: accounts, Validators: validators, + Params: &GenesisParams{ + GlobalPermissions: ptypes.NewDefaultAccountPermissions(), + }, }) s0.Save() return s0, privAccounts, privValidators diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache.go index 6ff1a634..8b8c371a 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/tx_cache.go @@ -3,6 +3,7 @@ package state import ( ac "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ... "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/sha3" ) @@ -79,6 +80,8 @@ func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account { Code: nil, Nonce: 0, StorageRoot: Zero256, + Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress256).Permissions, + Other: nil, } cache.accounts[addr] = vmAccountInfo{account, false} return account @@ -159,6 +162,7 @@ func toVMAccount(acc *ac.Account) *vm.Account { Code: acc.Code, // This is crazy. Nonce: uint64(acc.Sequence), StorageRoot: LeftPadWord256(acc.StorageRoot), + Permissions: acc.Permissions.Copy(), Other: acc.PubKey, } } @@ -169,6 +173,7 @@ func toStateAccount(acc *vm.Account) *ac.Account { if !ok { pubKey = nil } + var storageRoot []byte if acc.StorageRoot.IsZero() { storageRoot = nil @@ -182,6 +187,7 @@ func toStateAccount(acc *vm.Account) *ac.Account { Code: acc.Code, Sequence: uint(acc.Nonce), StorageRoot: storageRoot, + Permissions: acc.Permissions, } } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/native.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/native.go index cc8c3005..9b9337f9 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/native.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/native.go @@ -52,10 +52,8 @@ func sha256Func(input []byte, gas *uint64) (output []byte, err error) { } // Hash hasher := sha256.New() - _, err = hasher.Write(input) - if err != nil { - panic(err) - } + // CONTRACT: this does not err + hasher.Write(input) return hasher.Sum(nil), nil } @@ -69,10 +67,8 @@ func ripemd160Func(input []byte, gas *uint64) (output []byte, err error) { } // Hash hasher := ripemd160.New() - _, err = hasher.Write(input) - if err != nil { - panic(err) - } + // CONTRACT: this does not err + hasher.Write(input) return LeftPadBytes(hasher.Sum(nil), 32), nil } diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go index a20f2a01..505d536c 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go @@ -7,8 +7,8 @@ import ( type OpCode byte -// Op codes const ( + // Op codes // 0x0 range - arithmetic ops STOP OpCode = iota ADD diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/snative.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/snative.go new file mode 100644 index 00000000..73165be4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/snative.go @@ -0,0 +1,259 @@ +package vm + +import ( + "fmt" + + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" +) + +//------------------------------------------------------------------------------------------------ +// Registered SNative contracts + +var RegisteredSNativeContracts = map[Word256]SNativeContract{ + LeftPadWord256([]byte("hasBasePerm")): hasBasePerm, + LeftPadWord256([]byte("setBasePerm")): setBasePerm, + LeftPadWord256([]byte("unsetBasePerm")): unsetBasePerm, + LeftPadWord256([]byte("setGlobalPerm")): setGlobalPerm, + LeftPadWord256([]byte("hasRole")): hasRole, + LeftPadWord256([]byte("addRole")): addRole, + LeftPadWord256([]byte("rmRole")): rmRole, +} + +// Takes an appState so it can lookup/update accounts, +// an account to check for permission to access the snative contract +// and some input bytes (presumably 32byte words) +type SNativeContract func(appState AppState, acc *Account, input []byte) (output []byte, err error) + +//----------------------------------------------------------------------------- +// snative are native contracts that can access and manipulate the chain state +// (in particular the permissions values) + +// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?) + +func hasBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.HasBasePerm) { + return nil, ErrInvalidPermission{acc.Address, "HasBasePerm"} + } + if len(args) != 2*32 { + return nil, fmt.Errorf("hasBasePerm() takes two arguments (address, permission number)") + } + 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) + } + var permInt byte + if HasPermission(appState, vmAcc, permN) { + permInt = 0x1 + } else { + permInt = 0x0 + } + dbg.Printf("snative.hasBasePerm(0x%X, %b) = %v\n", addr.Postfix(20), permN, permInt) + return LeftPadWord256([]byte{permInt}).Bytes(), nil +} + +func setBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.SetBasePerm) { + return nil, ErrInvalidPermission{acc.Address, "SetBasePerm"} + } + if len(args) != 3*32 { + return nil, fmt.Errorf("setBasePerm() takes three arguments (address, permission number, permission value)") + } + 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 unsetBasePerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.UnsetBasePerm) { + return nil, ErrInvalidPermission{acc.Address, "UnsetBasePerm"} + } + if len(args) != 2*32 { + return nil, fmt.Errorf("unsetBasePerm() takes two arguments (address, permission number)") + } + 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 setGlobalPerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.SetGlobalPerm) { + return nil, ErrInvalidPermission{acc.Address, "SetGlobalPerm"} + } + if len(args) != 2*32 { + return nil, fmt.Errorf("setGlobalPerm() takes two arguments (permission number, permission value)") + } + permNum, perm := returnTwoArgs(args) + vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) + if vmAcc == nil { + panic("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 +} + +// TODO: needs access to an iterator ... +func clearPerm(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.ClearBasePerm) { + return nil, ErrInvalidPermission{acc.Address, "ClearPerm"} + } + return nil, nil +} + +func hasRole(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.HasRole) { + return nil, ErrInvalidPermission{acc.Address, "HasRole"} + } + if len(args) != 2*32 { + return nil, fmt.Errorf("hasRole() takes two arguments (address, role)") + } + addr, role := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + roleS := string(role.Bytes()) + var permInt byte + if vmAcc.Permissions.HasRole(roleS) { + permInt = 0x1 + } else { + permInt = 0x0 + } + dbg.Printf("snative.hasRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + return LeftPadWord256([]byte{permInt}).Bytes(), nil +} + +func addRole(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.AddRole) { + return nil, ErrInvalidPermission{acc.Address, "AddRole"} + } + if len(args) != 2*32 { + return nil, fmt.Errorf("addRole() takes two arguments (address, role)") + } + addr, role := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + roleS := string(role.Bytes()) + var permInt byte + if vmAcc.Permissions.AddRole(roleS) { + permInt = 0x1 + } else { + permInt = 0x0 + } + 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 rmRole(appState AppState, acc *Account, args []byte) (output []byte, err error) { + if !HasPermission(appState, acc, ptypes.RmRole) { + return nil, ErrInvalidPermission{acc.Address, "RmRole"} + } + if len(args) != 2*32 { + return nil, fmt.Errorf("rmRole() takes two arguments (address, role)") + } + addr, role := returnTwoArgs(args) + vmAcc := appState.GetAccount(addr) + if vmAcc == nil { + return nil, fmt.Errorf("Unknown account %X", addr) + } + roleS := string(role.Bytes()) + var permInt byte + if vmAcc.Permissions.RmRole(roleS) { + permInt = 0x1 + } else { + permInt = 0x0 + } + 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.TopBasePermission && n < ptypes.FirstSNativePerm { + return false + } else if n > ptypes.TopSNativePermission { + return false + } + return true +} + +// assumes length has already been checked +func returnTwoArgs(args []byte) (a Word256, b Word256) { + copy(a[:], args[:32]) + copy(b[:], args[32:64]) + return +} + +// assumes 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 +} + +// mostly a convenience for testing +var RegisteredSNativePermissions = map[Word256]ptypes.PermFlag{ + LeftPadWord256([]byte("hasBasePerm")): ptypes.HasBasePerm, + LeftPadWord256([]byte("setBasePerm")): ptypes.SetBasePerm, + LeftPadWord256([]byte("unsetBasePerm")): ptypes.UnsetBasePerm, + LeftPadWord256([]byte("setGlobalPerm")): ptypes.SetGlobalPerm, + LeftPadWord256([]byte("hasRole")): ptypes.HasRole, + LeftPadWord256([]byte("addRole")): ptypes.AddRole, + LeftPadWord256([]byte("rmRole")): ptypes.RmRole, +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/stack.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/stack.go index 7dd7d174..2047a436 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/stack.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/stack.go @@ -47,6 +47,7 @@ func (st *Stack) Push(d Word256) { st.ptr++ } +// currently only called after Sha3 func (st *Stack) PushBytes(bz []byte) { if len(bz) != 32 { panic("Invalid bytes size: expected 32") diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go index 24678231..7dd79cc5 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go @@ -53,9 +53,7 @@ func TestVM(t *testing.T) { N := []byte{0x0f, 0x0f} // Loop N times code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} - for i := 0; i < len(N); i++ { - code = append(code, N[i]) - } + 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) @@ -120,6 +118,7 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // account2 has insufficient balance, should fail + fmt.Println("Should fail with insufficient balance") exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) if exception == "" { @@ -137,6 +136,7 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // insufficient gas, should fail + fmt.Println("Should fail with insufficient gas") account2.Balance = 100000 exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100) diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/types.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/types.go index 4aa0964b..6f744486 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/types.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/types.go @@ -2,6 +2,7 @@ package vm import ( . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" ) const ( @@ -15,6 +16,8 @@ type Account struct { Nonce uint64 StorageRoot Word256 Other interface{} // For holding all other data. + + Permissions *ptypes.AccountPermissions } func (acc *Account) String() string { diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go index abfd8552..1a1e8874 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go @@ -8,6 +8,7 @@ import ( . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" + ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/sha3" ) @@ -27,6 +28,14 @@ var ( 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) +} + type Debug bool const ( @@ -51,6 +60,9 @@ type VM struct { callDepth int evc events.Fireable + + perms bool // permission checking can be turned off + snative bool // access to snatives } func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { @@ -68,6 +80,32 @@ func (vm *VM) SetFireable(evc events.Fireable) { vm.evc = evc } +// to allow calls to native DougContracts (off by default) +func (vm *VM) EnableSNatives() { + vm.snative = true +} + +// run permission checks before call and create +func (vm *VM) EnablePermissions() { + vm.perms = true +} + +// XXX: 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) +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 { + fmt.Printf("\n\n***** Unknown permission %b! ********\n\n", perm) + return false + } + return HasPermission(nil, appState.GetAccount(ptypes.GlobalPermissionsAddress256), perm) + } + return v +} + // CONTRACT appState is aware of caller and callee, so we can just mutate them. // value: To be transferred from caller to callee. Refunded upon error. // gas: Available gas. No refunds for gas. @@ -87,6 +125,17 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga } }() + // if code is empty, callee may be snative contract + if vm.snative && len(code) == 0 { + if snativeContract, ok := RegisteredSNativeContracts[callee.Address]; ok { + output, err = snativeContract(vm.appState, caller, input) + if err != nil { + *exception = err.Error() + } + return + } + } + if err = transfer(caller, callee, value); err != nil { *exception = err.Error() return @@ -100,6 +149,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga *exception = err.Error() err := transfer(callee, caller, value) if err != nil { + // data has been corrupted in ram panic("Could not return value to caller") } } @@ -658,6 +708,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga dbg.Printf(" => %v\n", log) case CREATE: // 0xF0 + if vm.perms && !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) @@ -683,6 +736,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } case CALL, CALLCODE: // 0xF1, 0xF2 + if vm.perms && !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 @@ -726,10 +782,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga ret, err = vm.Call(callee, callee, acc.Code, args, value, gas) } else { if acc == nil { - // if we have not seen the account before, create it - // so we can send funds - acc = &Account{ - Address: addr, + if _, ok := RegisteredSNativeContracts[addr]; vm.snative && ok { + acc = &Account{Address: addr} + } else { + // if we have not seen the account before, create it + // so we can send funds + if vm.perms && !HasPermission(vm.appState, caller, ptypes.CreateAccount) { + return nil, ErrPermission{"create_account"} + } + acc = &Account{Address: addr} } vm.appState.UpdateAccount(acc) } @@ -739,8 +800,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga // Push result if err != nil { - dbg.Printf("error on call: %s", err.Error()) - // TODO: fire event + dbg.Printf("error on call: %s\n", err.Error()) stack.Push(Zero256) } else { stack.Push(One256) @@ -784,7 +844,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga default: dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) - panic(fmt.Errorf("Invalid opcode %X", op)) + return nil, fmt.Errorf("Invalid opcode %X", op) } pc++ diff --git a/circle.yml b/circle.yml index 717733a9..66977f2b 100644 --- a/circle.yml +++ b/circle.yml @@ -1,10 +1,30 @@ +machine: + post: + - rm -rf ${GOPATH%%:*}/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME} + - mkdir -p ${GOPATH%%:*}/src/github.com/${CIRCLE_PROJECT_USERNAME} + - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME} ${GOPATH%%:*}/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME} + - git config --global user.email "billings@erisindustries.com" + - git config --global user.name "Billings the Bot" + dependencies: pre: + - sudo curl -L -o /usr/bin/docker 'http://s3-external-1.amazonaws.com/circle-downloads/docker-1.6.0-circleci'; chmod 0755 /usr/bin/docker; true + - sudo service docker start - "sudo apt-get update && sudo apt-get install -y libgmp3-dev" + override: - "cd ./cmd/erisdb && go build" - "mv ~/eris-db/cmd/erisdb/erisdb ~/bin" - chmod +x ~/bin/erisdb + test: override: - go test -v ./... + +deployment: + master: + branch: master + commands: + - docker build -t eris/erisdb:0.10 -f DOCKER/Dockerfile . + - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS + - docker push eris/erisdb:0.10 diff --git a/erisdb/pipe/blockchain.go b/erisdb/pipe/blockchain.go index b0704d7c..19b11b89 100644 --- a/erisdb/pipe/blockchain.go +++ b/erisdb/pipe/blockchain.go @@ -37,7 +37,7 @@ func newBlockchain(blockStore *bc.BlockStore) *blockchain { func (this *blockchain) Info() (*BlockchainInfo, error) { chainId := config.GetString("chain_id") db := dbm.NewMemDB() - genesisState := state.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) + _, genesisState := state.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) genesisHash := genesisState.Hash() latestHeight := this.blockStore.Height() @@ -63,7 +63,7 @@ func (this *blockchain) ChainId() (string, error) { // Get the hash of the genesis block. func (this *blockchain) GenesisHash() ([]byte, error) { db := dbm.NewMemDB() - genesisState := state.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) + _, genesisState := state.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) return genesisState.Hash(), nil } -- GitLab