Merge pull request #5 from tendermint/blockpool

Blockpool
This commit is contained in:
Jae Kwon 2015-03-29 19:10:55 -07:00
commit ffa82b5c61
47 changed files with 2716 additions and 1454 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
.bak .bak
tendermint tendermint
.DS_Store .DS_Store
rpc/test/.tendermint

View File

@ -38,13 +38,13 @@ type Account struct {
StorageRoot []byte // VM storage merkle root. StorageRoot []byte // VM storage merkle root.
} }
func (account *Account) Copy() *Account { func (acc *Account) Copy() *Account {
accountCopy := *account accCopy := *acc
return &accountCopy return &accCopy
} }
func (account *Account) String() string { func (acc *Account) String() string {
return fmt.Sprintf("Account{%X:%v C:%v S:%X}", account.Address, account.PubKey, len(account.Code), account.StorageRoot) return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot)
} }
func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) { func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) {

View File

@ -25,6 +25,18 @@ func GenPrivAccount() *PrivAccount {
} }
} }
func GenPrivAccountFromKey(privKeyBytes [64]byte) *PrivAccount {
pubKeyBytes := ed25519.MakePublicKey(&privKeyBytes)
pubKey := PubKeyEd25519(pubKeyBytes[:])
privKey := PrivKeyEd25519(privKeyBytes[:])
return &PrivAccount{
Address: pubKey.Address(),
PubKey: pubKey,
PrivKey: privKey,
}
}
func (privAccount *PrivAccount) Sign(o Signable) Signature { func (privAccount *PrivAccount) Sign(o Signable) Signature {
return privAccount.PrivKey.Sign(SignBytes(o)) return privAccount.PrivKey.Sign(SignBytes(o))
} }

View File

@ -2,6 +2,7 @@ package binary
import ( import (
"bytes" "bytes"
"fmt"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -58,6 +59,35 @@ var _ = RegisterInterface(
ConcreteType{&Viper{}}, ConcreteType{&Viper{}},
) )
func TestAnimalInterface(t *testing.T) {
var foo Animal
// Type of pointer to Animal
rt := reflect.TypeOf(&foo)
fmt.Printf("rt: %v\n", rt)
// Type of Animal itself.
// NOTE: normally this is acquired through other means
// like introspecting on method signatures, or struct fields.
rte := rt.Elem()
fmt.Printf("rte: %v\n", rte)
// Get a new pointer to the interface
// NOTE: calling .Interface() is to get the actual value,
// instead of reflection values.
ptr := reflect.New(rte).Interface()
fmt.Printf("ptr: %v", ptr)
// Make a binary byteslice that represents a snake.
snakeBytes := BinaryBytes(Snake([]byte("snake")))
snakeReader := bytes.NewReader(snakeBytes)
// Now you can read it.
n, err := new(int64), new(error)
it := *ReadBinary(ptr, snakeReader, n, err).(*Animal)
fmt.Println(it, reflect.TypeOf(it))
}
//------------------------------------- //-------------------------------------
type Constructor func() interface{} type Constructor func() interface{}
@ -287,9 +317,9 @@ func validateComplexArray(o interface{}, t *testing.T) {
var testCases = []TestCase{} var testCases = []TestCase{}
func init() { func init() {
//testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic}) testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
//testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex}) testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
//testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2}) testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray}) testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray})
} }

View File

@ -194,7 +194,7 @@ FOR_LOOP:
break SYNC_LOOP break SYNC_LOOP
} else { } else {
bcR.pool.PopRequest() bcR.pool.PopRequest()
err := bcR.state.AppendBlock(first, firstPartsHeader) err := sm.ExecBlock(bcR.state, first, firstPartsHeader)
if err != nil { if err != nil {
// TODO This is bad, are we zombie? // TODO This is bad, are we zombie?
panic(Fmt("Failed to process committed block: %v", err)) panic(Fmt("Failed to process committed block: %v", err))

View File

@ -1,6 +1,7 @@
package common package common
import ( import (
"encoding/binary"
"sort" "sort"
) )
@ -18,3 +19,13 @@ func SearchUint64s(a []uint64, x uint64) int {
} }
func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) } func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) }
//-----------------------------------------------------------------------------
func PutUint64(dest []byte, i uint64) {
binary.LittleEndian.PutUint64(dest, i)
}
func GetUint64(src []byte) uint64 {
return binary.LittleEndian.Uint64(src)
}

78
common/word.go Normal file
View File

@ -0,0 +1,78 @@
package common
import (
"bytes"
"encoding/binary"
"sort"
)
var (
Zero256 = Word256{0}
One256 = Word256{1}
)
type Word256 [32]byte
func (w Word256) String() string { return string(w[:]) }
func (w Word256) Copy() Word256 { return w }
func (w Word256) Bytes() []byte { return w[:] } // copied.
func (w Word256) Prefix(n int) []byte { return w[:n] }
func (w Word256) IsZero() bool {
accum := byte(0)
for _, byt := range w {
accum |= byt
}
return accum == 0
}
func (w Word256) Compare(other Word256) int {
return bytes.Compare(w[:], other[:])
}
func Uint64ToWord256(i uint64) Word256 {
word := Word256{}
PutUint64(word[:], i)
return word
}
func RightPadWord256(bz []byte) (word Word256) {
copy(word[:], bz)
return
}
func LeftPadWord256(bz []byte) (word Word256) {
copy(word[32-len(bz):], bz)
return
}
func Uint64FromWord256(word Word256) uint64 {
return binary.LittleEndian.Uint64(word[:])
}
//-------------------------------------
type Tuple256 struct {
First Word256
Second Word256
}
func (tuple Tuple256) Compare(other Tuple256) int {
firstCompare := tuple.First.Compare(other.First)
if firstCompare == 0 {
return tuple.Second.Compare(other.Second)
} else {
return firstCompare
}
}
func Tuple256Split(t Tuple256) (Word256, Word256) {
return t.First, t.Second
}
type Tuple256Slice []Tuple256
func (p Tuple256Slice) Len() int { return len(p) }
func (p Tuple256Slice) Less(i, j int) bool {
return p[i].Compare(p[j]) < 0
}
func (p Tuple256Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Tuple256Slice) Sort() { sort.Sort(p) }

View File

@ -1015,7 +1015,8 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
} }
// Already staged? // Already staged?
if cs.stagedBlock == block { blockHash := block.Hash()
if cs.stagedBlock != nil && len(blockHash) != 0 && bytes.Equal(cs.stagedBlock.Hash(), blockHash) {
return nil return nil
} }
@ -1024,7 +1025,7 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
// Commit block onto the copied state. // Commit block onto the copied state.
// NOTE: Basic validation is done in state.AppendBlock(). // NOTE: Basic validation is done in state.AppendBlock().
err := stateCopy.AppendBlock(block, blockParts.Header()) err := sm.ExecBlock(stateCopy, block, blockParts.Header())
if err != nil { if err != nil {
return err return err
} else { } else {

View File

@ -12,6 +12,7 @@ import (
mempl "github.com/tendermint/tendermint/mempool" mempl "github.com/tendermint/tendermint/mempool"
"github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/rpc" "github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
sm "github.com/tendermint/tendermint/state" sm "github.com/tendermint/tendermint/state"
) )
@ -150,13 +151,17 @@ func (n *Node) DialSeed() {
} }
func (n *Node) StartRpc() { func (n *Node) StartRpc() {
rpc.SetRPCBlockStore(n.blockStore) core.SetBlockStore(n.blockStore)
rpc.SetRPCConsensusState(n.consensusState) core.SetConsensusState(n.consensusState)
rpc.SetRPCMempoolReactor(n.mempoolReactor) core.SetMempoolReactor(n.mempoolReactor)
rpc.SetRPCSwitch(n.sw) core.SetSwitch(n.sw)
rpc.StartHTTPServer() rpc.StartHTTPServer()
} }
func (n *Node) Switch() *p2p.Switch {
return n.sw
}
func (n *Node) ConsensusState() *consensus.ConsensusState { func (n *Node) ConsensusState() *consensus.ConsensusState {
return n.consensusState return n.consensusState
} }

View File

@ -19,12 +19,14 @@ import (
type Mempool struct { type Mempool struct {
mtx sync.Mutex mtx sync.Mutex
state *sm.State state *sm.State
cache *sm.BlockCache
txs []types.Tx txs []types.Tx
} }
func NewMempool(state *sm.State) *Mempool { func NewMempool(state *sm.State) *Mempool {
return &Mempool{ return &Mempool{
state: state, state: state,
cache: sm.NewBlockCache(state),
} }
} }
@ -32,11 +34,15 @@ func (mem *Mempool) GetState() *sm.State {
return mem.state return mem.state
} }
func (mem *Mempool) GetCache() *sm.BlockCache {
return mem.cache
}
// Apply tx to the state and remember it. // Apply tx to the state and remember it.
func (mem *Mempool) AddTx(tx types.Tx) (err error) { func (mem *Mempool) AddTx(tx types.Tx) (err error) {
mem.mtx.Lock() mem.mtx.Lock()
defer mem.mtx.Unlock() defer mem.mtx.Unlock()
err = mem.state.ExecTx(tx, false) err = sm.ExecTx(mem.cache, tx, false)
if err != nil { if err != nil {
log.Debug("AddTx() error", "tx", tx, "error", err) log.Debug("AddTx() error", "tx", tx, "error", err)
return err return err
@ -62,6 +68,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) {
mem.mtx.Lock() mem.mtx.Lock()
defer mem.mtx.Unlock() defer mem.mtx.Unlock()
mem.state = state.Copy() mem.state = state.Copy()
mem.cache = sm.NewBlockCache(mem.state)
// First, create a lookup map of txns in new block. // First, create a lookup map of txns in new block.
blockTxsMap := make(map[string]struct{}) blockTxsMap := make(map[string]struct{})
@ -86,7 +93,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) {
// Next, filter all txs that aren't valid given new state. // Next, filter all txs that aren't valid given new state.
validTxs := []types.Tx{} validTxs := []types.Tx{}
for _, tx := range txs { for _, tx := range txs {
err := mem.state.ExecTx(tx, false) err := sm.ExecTx(mem.cache, tx, false)
if err == nil { if err == nil {
log.Debug("Filter in, valid", "tx", tx) log.Debug("Filter in, valid", "tx", tx)
validTxs = append(validTxs, tx) validTxs = append(validTxs, tx)

View File

@ -1,61 +0,0 @@
package rpc
import (
"net/http"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
)
func GenPrivAccountHandler(w http.ResponseWriter, r *http.Request) {
privAccount := account.GenPrivAccount()
WriteAPIResponse(w, API_OK, struct {
PrivAccount *account.PrivAccount
}{privAccount})
}
//-----------------------------------------------------------------------------
func GetAccountHandler(w http.ResponseWriter, r *http.Request) {
addressStr := GetParam(r, "address")
var address []byte
var err error
binary.ReadJSON(&address, []byte(addressStr), &err)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid address: %v", err))
return
}
state := consensusState.GetState()
account_ := state.GetAccount(address)
if account_ == nil {
WriteAPIResponse(w, API_OK, struct{}{})
return
}
WriteAPIResponse(w, API_OK, struct {
Account *account.Account
}{account_})
}
//-----------------------------------------------------------------------------
func ListAccountsHandler(w http.ResponseWriter, r *http.Request) {
var blockHeight uint
var accounts []*account.Account
state := consensusState.GetState()
blockHeight = state.LastBlockHeight
state.GetAccounts().Iterate(func(key interface{}, value interface{}) bool {
accounts = append(accounts, value.(*account.Account))
return false
})
WriteAPIResponse(w, API_OK, struct {
BlockHeight uint
Accounts []*account.Account
}{blockHeight, accounts})
}

32
rpc/core/accounts.go Normal file
View File

@ -0,0 +1,32 @@
package core
import (
"github.com/tendermint/tendermint/account"
)
//-----------------------------------------------------------------------------
func GenPrivAccount() (*ResponseGenPrivAccount, error) {
return &ResponseGenPrivAccount{account.GenPrivAccount()}, nil
}
//-----------------------------------------------------------------------------
func GetAccount(address []byte) (*ResponseGetAccount, error) {
cache := mempoolReactor.Mempool.GetCache()
return &ResponseGetAccount{cache.GetAccount(address)}, nil
}
//-----------------------------------------------------------------------------
func ListAccounts() (*ResponseListAccounts, error) {
var blockHeight uint
var accounts []*account.Account
state := consensusState.GetState()
blockHeight = state.LastBlockHeight
state.GetAccounts().Iterate(func(key interface{}, value interface{}) bool {
accounts = append(accounts, value.(*account.Account))
return false
})
return &ResponseListAccounts{blockHeight, accounts}, nil
}

View File

@ -1,15 +1,14 @@
package rpc package core
import ( import (
"net/http" "fmt"
. "github.com/tendermint/tendermint/common" . "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) { //-----------------------------------------------------------------------------
minHeight, _ := GetParamUint(r, "min_height")
maxHeight, _ := GetParamUint(r, "max_height") func BlockchainInfo(minHeight, maxHeight uint) (*ResponseBlockchainInfo, error) {
if maxHeight == 0 { if maxHeight == 0 {
maxHeight = blockStore.Height() maxHeight = blockStore.Height()
} else { } else {
@ -26,30 +25,20 @@ func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) {
blockMetas = append(blockMetas, blockMeta) blockMetas = append(blockMetas, blockMeta)
} }
WriteAPIResponse(w, API_OK, struct { return &ResponseBlockchainInfo{blockStore.Height(), blockMetas}, nil
LastHeight uint
BlockMetas []*types.BlockMeta
}{blockStore.Height(), blockMetas})
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func GetBlockHandler(w http.ResponseWriter, r *http.Request) { func GetBlock(height uint) (*ResponseGetBlock, error) {
height, _ := GetParamUint(r, "height")
if height == 0 { if height == 0 {
WriteAPIResponse(w, API_INVALID_PARAM, "height must be greater than 1") return nil, fmt.Errorf("height must be greater than 1")
return
} }
if height > blockStore.Height() { if height > blockStore.Height() {
WriteAPIResponse(w, API_INVALID_PARAM, "height must be less than the current blockchain height") return nil, fmt.Errorf("height must be less than the current blockchain height")
return
} }
blockMeta := blockStore.LoadBlockMeta(height) blockMeta := blockStore.LoadBlockMeta(height)
block := blockStore.LoadBlock(height) block := blockStore.LoadBlock(height)
return &ResponseGetBlock{blockMeta, block}, nil
WriteAPIResponse(w, API_OK, struct {
BlockMeta *types.BlockMeta
Block *types.Block
}{blockMeta, block})
} }

7
rpc/core/log.go Normal file
View File

@ -0,0 +1,7 @@
package core
import (
"github.com/tendermint/log15"
)
var log = log15.New("module", "rpc")

41
rpc/core/mempool.go Normal file
View File

@ -0,0 +1,41 @@
package core
import (
"fmt"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
//-----------------------------------------------------------------------------
type Receipt struct {
TxHash []byte
CreatesContract uint8
ContractAddr []byte
}
// pass pointer?
// Note: tx must be signed
func BroadcastTx(tx types.Tx) (*ResponseBroadcastTx, error) {
err := mempoolReactor.BroadcastTx(tx)
if err != nil {
return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
}
txHash := types.TxId(tx)
var createsContract uint8
var contractAddr []byte
// check if creates new contract
if callTx, ok := tx.(*types.CallTx); ok {
if callTx.Address == nil {
createsContract = 1
contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence))
}
}
return &ResponseBroadcastTx{Receipt{txHash, createsContract, contractAddr}}, nil
}
/*
curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=...
*/

39
rpc/core/net.go Normal file
View File

@ -0,0 +1,39 @@
package core
import (
"github.com/tendermint/tendermint/config"
dbm "github.com/tendermint/tendermint/db"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
//-----------------------------------------------------------------------------
func Status() (*ResponseStatus, error) {
db := dbm.NewMemDB()
genesisState := sm.MakeGenesisStateFromFile(db, config.App().GetString("GenesisFile"))
genesisHash := genesisState.Hash()
latestHeight := blockStore.Height()
var (
latestBlockMeta *types.BlockMeta
latestBlockHash []byte
latestBlockTime int64
)
if latestHeight != 0 {
latestBlockMeta = blockStore.LoadBlockMeta(latestHeight)
latestBlockHash = latestBlockMeta.Hash
latestBlockTime = latestBlockMeta.Header.Time.UnixNano()
}
return &ResponseStatus{genesisHash, config.App().GetString("Network"), latestBlockHash, latestHeight, latestBlockTime}, nil
}
//-----------------------------------------------------------------------------
func NetInfo() (*ResponseNetInfo, error) {
o, i, _ := p2pSwitch.NumPeers()
numPeers := o + i
listening := p2pSwitch.IsListening()
network := config.App().GetString("Network")
return &ResponseNetInfo{numPeers, listening, network}, nil
}

View File

@ -1,4 +1,4 @@
package rpc package core
import ( import (
bc "github.com/tendermint/tendermint/blockchain" bc "github.com/tendermint/tendermint/blockchain"
@ -12,18 +12,18 @@ var consensusState *consensus.ConsensusState
var mempoolReactor *mempl.MempoolReactor var mempoolReactor *mempl.MempoolReactor
var p2pSwitch *p2p.Switch var p2pSwitch *p2p.Switch
func SetRPCBlockStore(bs *bc.BlockStore) { func SetBlockStore(bs *bc.BlockStore) {
blockStore = bs blockStore = bs
} }
func SetRPCConsensusState(cs *consensus.ConsensusState) { func SetConsensusState(cs *consensus.ConsensusState) {
consensusState = cs consensusState = cs
} }
func SetRPCMempoolReactor(mr *mempl.MempoolReactor) { func SetMempoolReactor(mr *mempl.MempoolReactor) {
mempoolReactor = mr mempoolReactor = mr
} }
func SetRPCSwitch(sw *p2p.Switch) { func SetSwitch(sw *p2p.Switch) {
p2pSwitch = sw p2pSwitch = sw
} }

59
rpc/core/responses.go Normal file
View File

@ -0,0 +1,59 @@
package core
import (
"github.com/tendermint/tendermint/account"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
type ResponseGenPrivAccount struct {
PrivAccount *account.PrivAccount
}
type ResponseGetAccount struct {
Account *account.Account
}
type ResponseListAccounts struct {
BlockHeight uint
Accounts []*account.Account
}
type ResponseBlockchainInfo struct {
LastHeight uint
BlockMetas []*types.BlockMeta
}
type ResponseGetBlock struct {
BlockMeta *types.BlockMeta
Block *types.Block
}
// curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=...
type ResponseBroadcastTx struct {
Receipt Receipt
}
type ResponseStatus struct {
GenesisHash []byte
Network string
LatestBlockHash []byte
LatestBlockHeight uint
LatestBlockTime int64 // nano
}
type ResponseNetInfo struct {
NumPeers int
Listening bool
Network string
}
type ResponseSignTx struct {
Tx types.Tx
}
type ResponseListValidators struct {
BlockHeight uint
BondedValidators []*sm.Validator
UnbondingValidators []*sm.Validator
}

View File

@ -1,37 +1,21 @@
package rpc package core
import ( import (
"net/http" "fmt"
"github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
func SignTxHandler(w http.ResponseWriter, r *http.Request) { //-----------------------------------------------------------------------------
txStr := GetParam(r, "tx")
privAccountsStr := GetParam(r, "privAccounts") func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ResponseSignTx, error) {
// more checks?
var err error
var tx types.Tx
binary.ReadJSON(&tx, []byte(txStr), &err)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid tx: %v", err))
return
}
privAccounts := binary.ReadJSON([]*account.PrivAccount{}, []byte(privAccountsStr), &err).([]*account.PrivAccount)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid privAccounts: %v", err))
return
}
for i, privAccount := range privAccounts { for i, privAccount := range privAccounts {
if privAccount == nil || privAccount.PrivKey == nil { if privAccount == nil || privAccount.PrivKey == nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid (empty) privAccount @%v", i)) return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i)
return
} }
} }
switch tx.(type) { switch tx.(type) {
case *types.SendTx: case *types.SendTx:
sendTx := tx.(*types.SendTx) sendTx := tx.(*types.SendTx)
@ -39,6 +23,10 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) {
input.PubKey = privAccounts[i].PubKey input.PubKey = privAccounts[i].PubKey
input.Signature = privAccounts[i].Sign(sendTx) input.Signature = privAccounts[i].Sign(sendTx)
} }
case *types.CallTx:
callTx := tx.(*types.CallTx)
callTx.Input.PubKey = privAccounts[0].PubKey
callTx.Input.Signature = privAccounts[0].Sign(callTx)
case *types.BondTx: case *types.BondTx:
bondTx := tx.(*types.BondTx) bondTx := tx.(*types.BondTx)
for i, input := range bondTx.Inputs { for i, input := range bondTx.Inputs {
@ -52,6 +40,5 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) {
rebondTx := tx.(*types.RebondTx) rebondTx := tx.(*types.RebondTx)
rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519) rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519)
} }
return &ResponseSignTx{tx}, nil
WriteAPIResponse(w, API_OK, struct{ types.Tx }{tx})
} }

View File

@ -1,12 +1,12 @@
package rpc package core
import ( import (
"net/http"
sm "github.com/tendermint/tendermint/state" sm "github.com/tendermint/tendermint/state"
) )
func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) { //-----------------------------------------------------------------------------
func ListValidators() (*ResponseListValidators, error) {
var blockHeight uint var blockHeight uint
var bondedValidators []*sm.Validator var bondedValidators []*sm.Validator
var unbondingValidators []*sm.Validator var unbondingValidators []*sm.Validator
@ -22,9 +22,5 @@ func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) {
return false return false
}) })
WriteAPIResponse(w, API_OK, struct { return &ResponseListValidators{blockHeight, bondedValidators, unbondingValidators}, nil
BlockHeight uint
BondedValidators []*sm.Validator
UnbondingValidators []*sm.Validator
}{blockHeight, bondedValidators, unbondingValidators})
} }

View File

@ -1,21 +1,277 @@
package rpc package rpc
import ( import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/rpc/core"
"io/ioutil"
"net/http" "net/http"
"reflect"
"strconv"
) )
// cache all type information about each function up front
// (func, responseStruct, argNames)
// XXX: response structs are allocated once and reused - will this cause an issue eg. if a field ever not overwritten?
var funcMap = map[string]*FuncWrapper{
"status": funcWrap(core.Status, []string{}),
"net_info": funcWrap(core.NetInfo, []string{}),
"blockchain": funcWrap(core.BlockchainInfo, []string{"min_height", "max_height"}),
"get_block": funcWrap(core.GetBlock, []string{"height"}),
"get_account": funcWrap(core.GetAccount, []string{"address"}),
"list_validators": funcWrap(core.ListValidators, []string{}),
"broadcast_tx": funcWrap(core.BroadcastTx, []string{"tx"}),
"list_accounts": funcWrap(core.ListAccounts, []string{}),
"unsafe/gen_priv_account": funcWrap(core.GenPrivAccount, []string{}),
"unsafe/sign_tx": funcWrap(core.SignTx, []string{"tx", "privAccounts"}),
}
// holds all type information for each function
type FuncWrapper struct {
f reflect.Value // function from "rpc/core"
args []reflect.Type // type of each function arg
returns []reflect.Type // type of each return arg
argNames []string // name of each argument
}
func funcWrap(f interface{}, args []string) *FuncWrapper {
return &FuncWrapper{
f: reflect.ValueOf(f),
args: funcArgTypes(f),
returns: funcReturnTypes(f),
argNames: args,
}
}
// convert from a function name to the http handler
func toHandler(funcName string) func(http.ResponseWriter, *http.Request) {
funcInfo := funcMap[funcName]
return func(w http.ResponseWriter, r *http.Request) {
values, err := queryToValues(funcInfo, r)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(values)
response, err := returnsToResponse(funcInfo, returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
}
// convert a (json) string to a given type
func jsonToArg(ty reflect.Type, arg string) (reflect.Value, error) {
v := reflect.New(ty).Elem()
kind := v.Kind()
var err error
switch kind {
case reflect.Interface:
v = reflect.New(ty)
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
v = v.Elem()
case reflect.Struct:
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
case reflect.Slice:
rt := ty.Elem()
if rt.Kind() == reflect.Uint8 {
// if hex, decode
if len(arg) > 2 && arg[:2] == "0x" {
arg = arg[2:]
b, err := hex.DecodeString(arg)
if err != nil {
return v, err
}
v = reflect.ValueOf(b)
} else {
v = reflect.ValueOf([]byte(arg))
}
} else {
v = reflect.New(ty)
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
v = v.Elem()
}
case reflect.Int64:
u, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
case reflect.Int32:
u, err := strconv.ParseInt(arg, 10, 32)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
case reflect.Uint64:
u, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
case reflect.Uint:
u, err := strconv.ParseUint(arg, 10, 32)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
default:
v = reflect.ValueOf(arg)
}
return v, nil
}
// covert an http query to a list of properly typed values.
// to be properly decoded the arg must be a concrete type from tendermint (if its an interface).
func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, error) {
argTypes := funcInfo.args
argNames := funcInfo.argNames
var err error
values := make([]reflect.Value, len(argNames))
for i, name := range argNames {
ty := argTypes[i]
arg := GetParam(r, name)
values[i], err = jsonToArg(ty, arg)
if err != nil {
return nil, err
}
}
return values, nil
}
// covert a list of interfaces to properly typed values
// TODO!
func paramsToValues(funcInfo *FuncWrapper, params []string) ([]reflect.Value, error) {
values := make([]reflect.Value, len(params))
for i, p := range params {
ty := funcInfo.args[i]
v, err := jsonToArg(ty, p)
if err != nil {
return nil, err
}
values[i] = v
}
return values, nil
}
// returns is Response struct and error. If error is not nil, return it
func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) {
errV := returns[1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
return returns[0].Interface(), nil
}
/*
// convert a list of values to a populated struct with the correct types
func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) {
returnTypes := funcInfo.returns
finalType := returnTypes[len(returnTypes)-1]
if finalType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
errV := returns[len(returnTypes)-1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
}
// copy the response struct (New returns a pointer so we have to Elem() twice)
v := reflect.New(funcInfo.response.Elem().Type()).Elem()
nFields := v.NumField()
for i := 0; i < nFields; i++ {
field := v.FieldByIndex([]int{i})
field.Set(returns[i])
}
return v.Interface(), nil
}*/
// jsonrpc calls grab the given method's function info and runs reflect.Call
func JsonRpcHandler(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
var jrpc JsonRpc
err := json.Unmarshal(b, &jrpc)
if err != nil {
// TODO
}
funcInfo := funcMap[jrpc.Method]
values, err := paramsToValues(funcInfo, jrpc.Params)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(values)
response, err := returnsToResponse(funcInfo, returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
func initHandlers() { func initHandlers() {
http.HandleFunc("/status", StatusHandler) // HTTP endpoints
http.HandleFunc("/net_info", NetInfoHandler) // toHandler runs once for each function and caches
http.HandleFunc("/blockchain", BlockchainInfoHandler) // all reflection data
http.HandleFunc("/get_block", GetBlockHandler) http.HandleFunc("/status", toHandler("status"))
http.HandleFunc("/get_account", GetAccountHandler) http.HandleFunc("/net_info", toHandler("net_info"))
http.HandleFunc("/list_validators", ListValidatorsHandler) http.HandleFunc("/blockchain", toHandler("blockchain"))
http.HandleFunc("/broadcast_tx", BroadcastTxHandler) http.HandleFunc("/get_block", toHandler("get_block"))
http.HandleFunc("/get_account", toHandler("get_account"))
http.HandleFunc("/list_validators", toHandler("list_validators"))
http.HandleFunc("/broadcast_tx", toHandler("broadcast_tx"))
http.HandleFunc("/list_accounts", toHandler("list_accounts"))
http.HandleFunc("/unsafe/gen_priv_account", toHandler("unsafe/gen_priv_account"))
http.HandleFunc("/unsafe/sign_tx", toHandler("unsafe/sign_tx"))
//http.HandleFunc("/call", CallHandler) //http.HandleFunc("/call", CallHandler)
//http.HandleFunc("/get_storage", GetStorageHandler) //http.HandleFunc("/get_storage", GetStorageHandler)
http.HandleFunc("/develop/gen_priv_account", GenPrivAccountHandler) // JsonRPC endpoints
http.HandleFunc("/develop/list_accounts", ListAccountsHandler) http.HandleFunc("/", JsonRpcHandler)
http.HandleFunc("/develop/sign_tx", SignTxHandler) // unsafe JsonRPC endpoints
//http.HandleFunc("/unsafe", UnsafeJsonRpcHandler)
}
type JsonRpc struct {
JsonRpc string `json:"jsonrpc"`
Method string `json:"method"`
Params []string `json:"params"`
Id int `json:"id"`
}
// this will panic if not passed a function
func funcArgTypes(f interface{}) []reflect.Type {
t := reflect.TypeOf(f)
n := t.NumIn()
types := make([]reflect.Type, n)
for i := 0; i < n; i++ {
types[i] = t.In(i)
}
return types
}
func funcReturnTypes(f interface{}) []reflect.Type {
t := reflect.TypeOf(f)
n := t.NumOut()
types := make([]reflect.Type, n)
for i := 0; i < n; i++ {
types[i] = t.Out(i)
}
return types
} }

View File

@ -24,7 +24,7 @@ var (
) )
func panicAPI(err error) { func panicAPI(err error) {
panic(APIResponse{API_INVALID_PARAM, err.Error()}) panic(APIResponse{API_INVALID_PARAM, nil, err.Error()})
} }
func GetParam(r *http.Request, param string) string { func GetParam(r *http.Request, param string) string {

View File

@ -38,16 +38,22 @@ const (
type APIResponse struct { type APIResponse struct {
Status APIStatus `json:"status"` Status APIStatus `json:"status"`
Data interface{} `json:"data"` Data interface{} `json:"data"`
Error string `json:"error"`
} }
func (res APIResponse) Error() string { func (res APIResponse) StatusError() string {
return fmt.Sprintf("Status(%v) %v", res.Status, res.Data) return fmt.Sprintf("Status(%v) %v", res.Status, res.Error)
} }
func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}) { func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}, responseErr string) {
res := APIResponse{} res := APIResponse{}
res.Status = status res.Status = status
if data == nil {
// so json doesn't vommit
data = struct{}{}
}
res.Data = data res.Data = data
res.Error = responseErr
buf, n, err := new(bytes.Buffer), new(int64), new(error) buf, n, err := new(bytes.Buffer), new(int64), new(error)
binary.WriteJSON(res, buf, n, err) binary.WriteJSON(res, buf, n, err)
@ -109,7 +115,7 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler {
// If APIResponse, // If APIResponse,
if res, ok := e.(APIResponse); ok { if res, ok := e.(APIResponse); ok {
WriteAPIResponse(rww, res.Status, res.Data) WriteAPIResponse(rww, res.Status, nil, res.Error)
} else { } else {
// For the rest, // For the rest,
rww.WriteHeader(http.StatusInternalServerError) rww.WriteHeader(http.StatusInternalServerError)

View File

@ -1,50 +0,0 @@
package rpc
import (
"net/http"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
func BroadcastTxHandler(w http.ResponseWriter, r *http.Request) {
txJSON := GetParam(r, "tx")
var err error
var tx types.Tx
binary.ReadJSON(&tx, []byte(txJSON), &err)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid tx: %v", err))
return
}
err = mempoolReactor.BroadcastTx(tx)
if err != nil {
WriteAPIResponse(w, API_ERROR, Fmt("Error broadcasting transaction: %v", err))
return
}
txHash := merkle.HashFromBinary(tx)
var createsContract bool
var contractAddr []byte
if callTx, ok := tx.(*types.CallTx); ok {
if callTx.Address == nil {
createsContract = true
contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence))
}
}
WriteAPIResponse(w, API_OK, struct {
TxHash []byte
CreatesContract bool
ContractAddr []byte
}{txHash, createsContract, contractAddr})
return
}
/*
curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=...
*/

View File

@ -1,33 +0,0 @@
package rpc
import (
"github.com/tendermint/tendermint/config"
"net/http"
)
func StatusHandler(w http.ResponseWriter, r *http.Request) {
genesisHash := blockStore.LoadBlockMeta(0).Hash
latestHeight := blockStore.Height()
latestBlockMeta := blockStore.LoadBlockMeta(latestHeight)
latestBlockHash := latestBlockMeta.Hash
latestBlockTime := latestBlockMeta.Header.Time.UnixNano()
WriteAPIResponse(w, API_OK, struct {
GenesisHash []byte
LatestBlockHash []byte
LatestBlockHeight uint
LatestBlockTime int64 // nano
Network string
}{genesisHash, latestBlockHash, latestHeight, latestBlockTime, config.App().GetString("Network")})
}
func NetInfoHandler(w http.ResponseWriter, r *http.Request) {
o, i, _ := p2pSwitch.NumPeers()
numPeers := o + i
listening := p2pSwitch.IsListening()
network := config.App().GetString("Network")
WriteAPIResponse(w, API_OK, struct {
NumPeers int
Listening bool
Network string
}{numPeers, listening, network})
}

View File

@ -0,0 +1,24 @@
{
"Accounts": [
{
"Address": "d7dff9806078899c8da3fe3633cc0bf3c6c2b1bb",
"Amount": 200000000
},
{
"Address": "AC89A6DDF4C309A89A2C4078CE409A5A7B282270",
"Amount": 200000000
}
],
"Validators": [
{
"PubKey": [1, "2239c21c81ea7173a6c489145490c015e05d4b97448933b708a7ec5b7b4921e3"],
"Amount": 1000000,
"UnbondTo": [
{
"Address": "d7dff9806078899c8da3fe3633cc0bf3c6c2b1bb",
"Amount": 100000
}
]
}
]
}

View File

@ -0,0 +1 @@
{"Address":"D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB","PubKey":[1,"2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"],"PrivKey":[1,"FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"],"LastHeight":3,"LastRound":0,"LastStep":2}

150
rpc/test/http_rpc_test.go Normal file
View File

@ -0,0 +1,150 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/rpc/core"
"github.com/tendermint/tendermint/types"
"io/ioutil"
"net/http"
"net/url"
"testing"
)
func TestHTTPStatus(t *testing.T) {
resp, err := http.Get(requestAddr + "status")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseStatus
Error string
}
err = json.Unmarshal(body, &status)
if err != nil {
t.Fatal(err)
}
data := status.Data
if data.Network != config.App().GetString("Network") {
t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", data.Network, config.App().Get("Network")))
}
}
func TestHTTPGenPriv(t *testing.T) {
resp, err := http.Get(requestAddr + "unsafe/gen_priv_account")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatal(resp)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseGenPrivAccount
Error string
}
binary.ReadJSON(&status, body, &err)
if err != nil {
t.Fatal(err)
}
if len(status.Data.PrivAccount.Address) == 0 {
t.Fatal("Failed to generate an address")
}
}
func TestHTTPGetAccount(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
acc := getAccount(t, "HTTP", byteAddr)
if bytes.Compare(acc.Address, byteAddr) != 0 {
t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr)
}
}
func TestHTTPSignedTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{20, 143, 24, 63, 16, 17, 83, 29, 90, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{0, 0, 4, 0, 0, 4, 0, 0, 4, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
}
func TestHTTPBroadcastTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
n, w := new(int64), new(bytes.Buffer)
var err error
binary.WriteJSON(tx, w, n, &err)
if err != nil {
t.Fatal(err)
}
b := w.Bytes()
var status struct {
Status string
Data core.ResponseBroadcastTx
Error string
}
requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status)
if status.Status == "ERROR" {
t.Fatal(status.Error)
}
receipt := status.Data.Receipt
if receipt.CreatesContract > 0 {
t.Fatal("This tx does not create a contract")
}
if len(receipt.TxHash) == 0 {
t.Fatal("Failed to compute tx hash")
}
pool := node.MempoolReactor().Mempool
txs := pool.GetProposalTxs()
if len(txs) != mempoolCount+1 {
t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount+1)
}
tx2 := txs[mempoolCount].(*types.SendTx)
mempoolCount += 1
if bytes.Compare(merkle.HashFromBinary(tx), merkle.HashFromBinary(tx2)) != 0 {
t.Fatal("inconsistent hashes for mempool tx and sent tx")
}
}
/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx))
err = mint.MempoolReactor.BroadcastTx(tx)
return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/

172
rpc/test/json_rpc_test.go Normal file
View File

@ -0,0 +1,172 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
"github.com/tendermint/tendermint/types"
"io/ioutil"
"net/http"
"net/url"
"testing"
)
func TestJSONStatus(t *testing.T) {
s := rpc.JsonRpc{
JsonRpc: "2.0",
Method: "status",
Params: []string{},
Id: 0,
}
b, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(b)
resp, err := http.Post(requestAddr, "text/json", buf)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
status := new(struct {
Status string
Data core.ResponseStatus
Error string
})
err = json.Unmarshal(body, status)
if err != nil {
t.Fatal(err)
}
if status.Data.Network != config.App().GetString("Network") {
t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", status.Data.Network, config.App().Get("Network")))
}
}
func TestJSONGenPriv(t *testing.T) {
s := rpc.JsonRpc{
JsonRpc: "2.0",
Method: "unsafe/gen_priv_account",
Params: []string{},
Id: 0,
}
b, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(b)
resp, err := http.Post(requestAddr, "text/json", buf)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatal(resp)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseGenPrivAccount
Error string
}
binary.ReadJSON(&status, body, &err)
if err != nil {
t.Fatal(err)
}
if len(status.Data.PrivAccount.Address) == 0 {
t.Fatal("Failed to generate an address")
}
}
func TestJSONGetAccount(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
acc := getAccount(t, "JSONRPC", byteAddr)
if bytes.Compare(acc.Address, byteAddr) != 0 {
t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr)
}
}
func TestJSONSignedTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{20, 143, 24, 63, 16, 17, 83, 29, 90, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{0, 0, 4, 0, 0, 4, 0, 0, 4, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
}
func TestJSONBroadcastTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
n, w := new(int64), new(bytes.Buffer)
var err error
binary.WriteJSON(tx, w, n, &err)
if err != nil {
t.Fatal(err)
}
b := w.Bytes()
var status struct {
Status string
Data core.ResponseBroadcastTx
Error string
}
requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status)
if status.Status == "ERROR" {
t.Fatal(status.Error)
}
receipt := status.Data.Receipt
if receipt.CreatesContract > 0 {
t.Fatal("This tx does not create a contract")
}
if len(receipt.TxHash) == 0 {
t.Fatal("Failed to compute tx hash")
}
pool := node.MempoolReactor().Mempool
txs := pool.GetProposalTxs()
if len(txs) != mempoolCount+1 {
t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount+1)
}
tx2 := txs[mempoolCount].(*types.SendTx)
mempoolCount += 1
if bytes.Compare(types.TxId(tx), types.TxId(tx2)) != 0 {
t.Fatal(Fmt("inconsistent hashes for mempool tx and sent tx: %v vs %v", tx, tx2))
}
}
/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx))
err = mint.MempoolReactor.BroadcastTx(tx)
return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/

216
rpc/test/test.go Normal file
View File

@ -0,0 +1,216 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/daemon"
"github.com/tendermint/tendermint/logger"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
"github.com/tendermint/tendermint/types"
"io/ioutil"
"net/http"
"net/url"
"testing"
)
var (
rpcAddr = "127.0.0.1:8089"
requestAddr = "http://" + rpcAddr + "/"
chainId string
node *daemon.Node
mempoolCount = 0
userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB"
userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
)
func newNode(ready chan struct{}) {
// Create & start node
node = daemon.NewNode()
l := p2p.NewDefaultListener("tcp", config.App().GetString("ListenAddr"), false)
node.AddListener(l)
node.Start()
// Run the RPC server.
node.StartRpc()
ready <- struct{}{}
// Sleep forever
ch := make(chan struct{})
<-ch
}
func init() {
rootDir := ".tendermint"
config.Init(rootDir)
app := config.App()
app.Set("SeedNode", "")
app.Set("DB.Backend", "memdb")
app.Set("RPC.HTTP.ListenAddr", rpcAddr)
app.Set("GenesisFile", rootDir+"/genesis.json")
app.Set("PrivValidatorFile", rootDir+"/priv_validator.json")
app.Set("Log.Stdout.Level", "debug")
config.SetApp(app)
logger.InitLog()
// start a node
ready := make(chan struct{})
go newNode(ready)
<-ready
}
func getAccount(t *testing.T, typ string, addr []byte) *account.Account {
var resp *http.Response
var err error
switch typ {
case "JSONRPC":
s := rpc.JsonRpc{
JsonRpc: "2.0",
Method: "get_account",
Params: []string{"0x" + hex.EncodeToString(addr)},
Id: 0,
}
b, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(b)
resp, err = http.Post(requestAddr, "text/json", buf)
case "HTTP":
resp, err = http.PostForm(requestAddr+"get_account",
url.Values{"address": {string(addr)}})
}
fmt.Println("RESPONSE:", resp)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseGetAccount
Error string
}
fmt.Println(string(body))
binary.ReadJSON(&status, body, &err)
if err != nil {
t.Fatal(err)
}
return status.Data.Account
}
func makeTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx {
acc := getAccount(t, typ, from)
nonce := 0
if acc != nil {
nonce = int(acc.Sequence) + 1
}
bytePub, err := hex.DecodeString(userPub)
if err != nil {
t.Fatal(err)
}
tx := &types.SendTx{
Inputs: []*types.TxInput{
&types.TxInput{
Address: from,
Amount: amt,
Sequence: uint(nonce),
Signature: account.SignatureEd25519{},
PubKey: account.PubKeyEd25519(bytePub),
},
},
Outputs: []*types.TxOutput{
&types.TxOutput{
Address: to,
Amount: amt,
},
},
}
return tx
}
func requestResponse(t *testing.T, method string, values url.Values, status interface{}) {
resp, err := http.PostForm(requestAddr+method, values)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
fmt.Println(string(body))
binary.ReadJSON(status, body, &err)
if err != nil {
t.Fatal(err)
}
}
func signTx(t *testing.T, typ string, fromAddr, toAddr []byte, key [64]byte, amt uint64) (*types.SendTx, *account.PrivAccount) {
tx := makeTx(t, typ, fromAddr, toAddr, amt)
n, w := new(int64), new(bytes.Buffer)
var err error
binary.WriteJSON(tx, w, n, &err)
if err != nil {
t.Fatal(err)
}
b := w.Bytes()
privAcc := account.GenPrivAccountFromKey(key)
if bytes.Compare(privAcc.PubKey.Address(), fromAddr) != 0 {
t.Fatal("Faield to generate correct priv acc")
}
w = new(bytes.Buffer)
binary.WriteJSON([]*account.PrivAccount{privAcc}, w, n, &err)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseSignTx
Error string
}
requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &status)
if status.Status == "ERROR" {
t.Fatal(status.Error)
}
response := status.Data
tx = response.Tx.(*types.SendTx)
return tx, privAcc
}
func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types.SendTx) {
if bytes.Compare(tx.Inputs[0].Address, fromAddr) != 0 {
t.Fatal("Tx input addresses don't match!")
}
signBytes := account.SignBytes(tx)
in := tx.Inputs[0] //(*types.SendTx).Inputs[0]
if err := in.ValidateBasic(); err != nil {
t.Fatal(err)
}
fmt.Println(priv.PubKey, in.PubKey)
// Check signatures
// acc := getAccount(t, byteAddr)
// NOTE: using the acc here instead of the in fails; its PubKeyNil ... ?
if !in.PubKey.VerifyBytes(signBytes, in.Signature) {
t.Fatal(types.ErrTxInvalidSignature)
}
}

221
state/block_cache.go Normal file
View File

@ -0,0 +1,221 @@
package state
import (
"bytes"
"sort"
ac "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
dbm "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle"
)
func makeStorage(db dbm.DB, root []byte) merkle.Tree {
storage := merkle.NewIAVLTree(
binary.BasicCodec,
binary.BasicCodec,
1024,
db,
)
storage.Load(root)
return storage
}
type BlockCache struct {
db dbm.DB
backend *State
accounts map[string]accountInfo
storages map[Tuple256]storageInfo
}
func NewBlockCache(backend *State) *BlockCache {
return &BlockCache{
db: backend.DB,
backend: backend,
accounts: make(map[string]accountInfo),
storages: make(map[Tuple256]storageInfo),
}
}
func (cache *BlockCache) State() *State {
return cache.backend
}
//-------------------------------------
// BlockCache.account
func (cache *BlockCache) GetAccount(addr []byte) *ac.Account {
acc, _, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
return nil
} else if acc != nil {
return acc
} else {
acc = cache.backend.GetAccount(addr)
cache.accounts[string(addr)] = accountInfo{acc, nil, false, false}
return acc
}
}
func (cache *BlockCache) UpdateAccount(acc *ac.Account) {
addr := acc.Address
// SANITY CHECK
_, storage, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
panic("UpdateAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[string(addr)] = accountInfo{acc, storage, false, true}
}
func (cache *BlockCache) RemoveAccount(addr []byte) {
// SANITY CHECK
_, _, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
panic("RemoveAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[string(addr)] = accountInfo{nil, nil, true, false}
}
// BlockCache.account
//-------------------------------------
// BlockCache.storage
func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) {
// Check cache
info, ok := cache.storages[Tuple256{addr, key}]
if ok {
return info.value
}
// Get or load storage
acc, storage, removed, dirty := cache.accounts[string(addr.Prefix(20))].unpack()
if removed {
panic("GetStorage() on removed account")
}
if storage == nil {
storage = makeStorage(cache.db, acc.StorageRoot)
cache.accounts[string(addr.Prefix(20))] = accountInfo{acc, storage, false, dirty}
}
// Load and set cache
_, val_ := storage.Get(key.Bytes())
value = Zero256
if val_ != nil {
value = RightPadWord256(val_.([]byte))
}
cache.storages[Tuple256{addr, key}] = storageInfo{value, false}
return value
}
// NOTE: Set value to zero to removed from the trie.
func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) {
_, _, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack()
if removed {
panic("SetStorage() on a removed account")
}
cache.storages[Tuple256{addr, key}] = storageInfo{value, true}
}
// BlockCache.storage
//-------------------------------------
// CONTRACT the updates are in deterministic order.
func (cache *BlockCache) Sync() {
// Determine order for storage updates
// The address comes first so it'll be grouped.
storageKeys := make([]Tuple256, 0, len(cache.storages))
for keyTuple := range cache.storages {
storageKeys = append(storageKeys, keyTuple)
}
Tuple256Slice(storageKeys).Sort()
// Update storage for all account/key.
// Later we'll iterate over all the users and save storage + update storage root.
var (
curAddr Word256
curAcc *ac.Account
curAccRemoved bool
curStorage merkle.Tree
)
for _, storageKey := range storageKeys {
addr, key := Tuple256Split(storageKey)
if addr != curAddr || curAcc == nil {
acc, storage, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack()
curAddr = addr
curAcc = acc
curAccRemoved = removed
curStorage = storage
}
if curAccRemoved {
continue
}
value, dirty := cache.storages[storageKey].unpack()
if !dirty {
continue
}
if value.IsZero() {
curStorage.Remove(key.Bytes())
} else {
curStorage.Set(key.Bytes(), value.Bytes())
}
}
// Determine order for accounts
addrStrs := []string{}
for addrStr := range cache.accounts {
addrStrs = append(addrStrs, addrStr)
}
sort.Strings(addrStrs)
// Update or delete accounts.
for _, addrStr := range addrStrs {
acc, storage, removed, dirty := cache.accounts[addrStr].unpack()
if removed {
removed := cache.backend.RemoveAccount(acc.Address)
if !removed {
panic(Fmt("Could not remove account to be removed: %X", acc.Address))
}
} else {
if acc == nil {
panic(Fmt("Account should not be nil for addr: %X", acc.Address))
}
if storage != nil {
newStorageRoot := storage.Save()
if !bytes.Equal(newStorageRoot, acc.StorageRoot) {
acc.StorageRoot = newStorageRoot
dirty = true
}
}
if dirty {
cache.backend.UpdateAccount(acc)
}
}
}
}
//-----------------------------------------------------------------------------
type accountInfo struct {
account *ac.Account
storage merkle.Tree
removed bool
dirty bool
}
func (accInfo accountInfo) unpack() (*ac.Account, merkle.Tree, bool, bool) {
return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty
}
type storageInfo struct {
value Word256
dirty bool
}
func (stjInfo storageInfo) unpack() (Word256, bool) {
return stjInfo.value, stjInfo.dirty
}

18
state/common.go Normal file
View File

@ -0,0 +1,18 @@
package state
import (
ac "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm"
)
type AccountGetter interface {
GetAccount(addr []byte) *ac.Account
}
type VMAccountState interface {
GetAccount(addr Word256) *vm.Account
UpdateAccount(acc *vm.Account)
RemoveAccount(acc *vm.Account)
CreateAccount(creator *vm.Account) *vm.Account
}

593
state/execution.go Normal file
View File

@ -0,0 +1,593 @@
package state
import (
"bytes"
"errors"
"github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
)
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling ExecBlock!
func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
err := execBlock(s, block, blockPartsHeader)
if err != nil {
return err
}
// State.Hash should match block.StateHash
stateHash := s.Hash()
if !bytes.Equal(stateHash, block.StateHash) {
return Errorf("Invalid state hash. Expected %X, got %X",
stateHash, block.StateHash)
}
return nil
}
// executes transactions of a block, does not check block.StateHash
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling execBlock!
func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
if err != nil {
return err
}
// Validate block Validation.
if block.Height == 1 {
if len(block.Validation.Commits) != 0 {
return errors.New("Block at height 1 (first block) should have no Validation commits")
}
} else {
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
}
var sumVotingPower uint64
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
vote := &types.Vote{
Height: block.Height - 1,
Round: commit.Round,
Type: types.VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
}
if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote))
err = errors.New("Invalid validation signature")
return true
}
}
})
if err != nil {
return err
}
if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 {
return errors.New("Insufficient validation voting power")
}
}
// Update Validator.LastCommitHeight as necessary.
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.LastBondedValidators.GetByIndex(uint(i))
if val == nil {
panic(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
panic("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
panic("Failed to update unbonding validator LastCommitHeight")
}
} else {
panic("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Create BlockCache to cache changes to state.
blockCache := NewBlockCache(s)
// Commit each tx
for _, tx := range block.Data.Txs {
err := ExecTx(blockCache, tx, true)
if err != nil {
return InvalidTxError{tx, err}
}
}
// Now sync the BlockCache to the backend.
blockCache.Sync()
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight)
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
log.Info("Validator timeout", "validator", val, "height", block.Height)
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val)
}
// Increment validator AccumPowers
s.BondedValidators.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockTime = block.Time
return nil
}
// The accounts from the TxInputs must either already have
// account.PubKey.(type) != PubKeyNil, (it must be known),
// or it must be specified in the TxInput. If redeclared,
// the TxInput is modified and input.PubKey set to PubKeyNil.
func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) {
accounts := map[string]*account.Account{}
for _, in := range ins {
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := state.GetAccount(in.Address)
if acc == nil {
return nil, types.ErrTxInvalidAddress
}
// PubKey should be present in either "account" or "in"
if err := checkInputPubKey(acc, in); err != nil {
return nil, err
}
accounts[string(in.Address)] = acc
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := state.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &account.Account{
Address: out.Address,
PubKey: account.PubKeyNil{},
Sequence: 0,
Balance: 0,
}
}
accounts[string(out.Address)] = acc
}
return accounts, nil
}
func checkInputPubKey(acc *account.Account, in *types.TxInput) error {
if _, isNil := acc.PubKey.(account.PubKeyNil); isNil {
if _, isNil := in.PubKey.(account.PubKeyNil); isNil {
return types.ErrTxUnknownPubKey
}
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
return types.ErrTxInvalidPubKey
}
acc.PubKey = in.PubKey
} else {
in.PubKey = account.PubKeyNil{}
}
return nil
}
func validateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("validateInputs() expects account in accounts")
}
err = validateInput(acc, signBytes, in)
if err != nil {
return
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
}
func validateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) {
// Check TxInput basic
if err := in.ValidateBasic(); err != nil {
return err
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return types.ErrTxInvalidSignature
}
// Check sequences
if acc.Sequence+1 != in.Sequence {
return types.ErrTxInvalidSequence{
Got: uint64(in.Sequence),
Expected: uint64(acc.Sequence + 1),
}
}
// Check amount
if acc.Balance < in.Amount {
return types.ErrTxInsufficientFunds
}
return nil
}
func validateOutputs(outs []*types.TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
}
// Good. Add amount to total
total += out.Amount
}
return total, nil
}
func adjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("adjustByInputs() expects account in accounts")
}
if acc.Balance < in.Amount {
panic("adjustByInputs() expects sufficient funds")
}
acc.Balance -= in.Amount
acc.Sequence += 1
}
}
func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) {
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
panic("adjustByOutputs() expects account in accounts")
}
acc.Balance += out.Amount
}
}
// If the tx is invalid, an error will be returned.
// Unlike ExecBlock(), state will not be altered.
func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
// TODO: do something with fees
fees := uint64(0)
_s := blockCache.State() // hack to access validators.
// Exec tx
switch tx := tx_.(type) {
case *types.SendTx:
accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := validateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
adjustByOutputs(accounts, tx.Outputs)
for _, acc := range accounts {
blockCache.UpdateAccount(acc)
}
return nil
case *types.CallTx:
var inAcc, outAcc *account.Account
// Validate input
inAcc = blockCache.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
return err
}
signBytes := account.SignBytes(tx)
err := validateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address))
return err
}
if tx.Input.Amount < tx.Fee {
log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return types.ErrTxInsufficientFunds
}
createAccount := len(tx.Address) == 0
if !createAccount {
// Validate output
if len(tx.Address) != 20 {
log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress
}
// 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
outAcc = blockCache.GetAccount(tx.Address)
}
log.Debug(Fmt("Out account: %v", outAcc))
// Good!
value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1
if runCall {
var (
gas uint64 = tx.GasLimit
err error = nil
caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil
code []byte = nil
txCache = NewTxCache(blockCache)
params = vm.Params{
BlockHeight: uint64(_s.LastBlockHeight),
BlockHash: RightPadWord256(_s.LastBlockHash),
BlockTime: _s.LastBlockTime.Unix(),
GasLimit: 10000000,
}
)
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
return types.ErrTxInvalidAddress
}
callee = toVMAccount(outAcc)
code = callee.Code
log.Debug(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
} else {
callee = txCache.CreateAccount(caller)
log.Debug(Fmt("Created new account %X", callee.Address))
code = tx.Data
}
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.
vmach := vm.NewVM(txCache, params, caller.Address)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
log.Debug(Fmt("Error on execution: %v", err))
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
// Throw away 'txCache' which holds incomplete updates (don't sync it).
} else {
log.Debug("Successful execution")
// Success
if createAccount {
callee.Code = ret
}
txCache.Sync()
}
// Create a receipt from the ret and whether errored.
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc.Balance -= value
if createAccount {
inAcc.Sequence += 1
}
blockCache.UpdateAccount(inAcc)
}
return nil
case *types.BondTx:
valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := validateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
for _, acc := range accounts {
blockCache.UpdateAccount(acc)
}
// Add ValidatorInfo
_s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: _s.LastBlockHeight + 1,
FirstBondAmount: outTotal,
})
// Add Validator
added := _s.BondedValidators.Add(&Validator{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: _s.LastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
panic("Failed to add validator")
}
return nil
case *types.UnbondTx:
// The validator must be active
_, val := _s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
_s.unbondValidator(val)
return nil
case *types.RebondTx:
// The validator must be inactive
_, val := _s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be equal to the next height
if tx.Height != _s.LastBlockHeight+1 {
return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", _s.LastBlockHeight+1, tx.Height))
}
// Good!
_s.rebondValidator(val)
return nil
case *types.DupeoutTx:
// Verify the signatures
_, accused := _s.BondedValidators.GetByAddress(tx.Address)
if accused == nil {
_, accused = _s.UnbondingValidators.GetByAddress(tx.Address)
if accused == nil {
return types.ErrTxInvalidAddress
}
}
voteASignBytes := account.SignBytes(&tx.VoteA)
voteBSignBytes := account.SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return types.ErrTxInvalidSignature
}
// Verify equivocation
// TODO: in the future, just require one vote from a previous height that
// doesn't exist on this chain.
if tx.VoteA.Height != tx.VoteB.Height {
return errors.New("DupeoutTx heights don't match")
}
if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case (not an error, validator must be slashed!)
// Validators should not sign another vote after committing.
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
// We need to check both orderings of the votes
} else {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if tx.VoteA.Type != tx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes shouldn't match")
}
}
// Good! (Bad validator!)
_s.destroyValidator(accused)
return nil
default:
panic("Unknown Tx type")
}
}

View File

@ -2,17 +2,14 @@ package state
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary" "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
dbm "github.com/tendermint/tendermint/db" dbm "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle" "github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
) )
var ( var (
@ -25,17 +22,6 @@ var (
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
type InvalidTxError struct {
Tx types.Tx
Reason error
}
func (txErr InvalidTxError) Error() string {
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
}
//-----------------------------------------------------------------------------
// NOTE: not goroutine-safe. // NOTE: not goroutine-safe.
type State struct { type State struct {
DB dbm.DB DB dbm.DB
@ -78,7 +64,6 @@ func LoadState(db dbm.DB) *State {
return s return s
} }
// Save this state into the db.
func (s *State) Save() { func (s *State) Save() {
s.accounts.Save() s.accounts.Save()
s.validatorInfos.Save() s.validatorInfos.Save()
@ -98,6 +83,9 @@ func (s *State) Save() {
s.DB.Set(stateKey, buf.Bytes()) s.DB.Set(stateKey, buf.Bytes())
} }
// CONTRACT:
// Copy() is a cheap way to take a snapshot,
// as if State were copied by value.
func (s *State) Copy() *State { func (s *State) Copy() *State {
return &State{ return &State{
DB: s.DB, DB: s.DB,
@ -113,437 +101,81 @@ func (s *State) Copy() *State {
} }
} }
// The accounts from the TxInputs must either already have // Returns a hash that represents the state data, excluding Last*
// account.PubKey.(type) != PubKeyNil, (it must be known), func (s *State) Hash() []byte {
// or it must be specified in the TxInput. If redeclared, hashables := []merkle.Hashable{
// the TxInput is modified and input.PubKey set to PubKeyNil. s.BondedValidators,
func (s *State) GetOrMakeAccounts(ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) { s.UnbondingValidators,
accounts := map[string]*account.Account{} s.accounts,
for _, in := range ins { s.validatorInfos,
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
} }
acc := s.GetAccount(in.Address) return merkle.HashFromHashables(hashables)
}
// Mutates the block in place and updates it with new state hash.
func (s *State) SetBlockStateHash(block *types.Block) error {
sCopy := s.Copy()
err := execBlock(sCopy, block, types.PartSetHeader{})
if err != nil {
return err
}
// Set block.StateHash
block.StateHash = sCopy.Hash()
return nil
}
//-------------------------------------
// State.accounts
// The returned Account is a copy, so mutating it
// has no side effects.
// Implements Statelike
func (s *State) GetAccount(address []byte) *account.Account {
_, acc := s.accounts.Get(address)
if acc == nil { if acc == nil {
return nil, types.ErrTxInvalidAddress
}
// PubKey should be present in either "account" or "in"
if err := checkInputPubKey(acc, in); err != nil {
return nil, err
}
accounts[string(in.Address)] = acc
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := s.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &account.Account{
Address: out.Address,
PubKey: account.PubKeyNil{},
Sequence: 0,
Balance: 0,
}
}
accounts[string(out.Address)] = acc
}
return accounts, nil
}
func checkInputPubKey(acc *account.Account, in *types.TxInput) error {
if _, isNil := acc.PubKey.(account.PubKeyNil); isNil {
if _, isNil := in.PubKey.(account.PubKeyNil); isNil {
return types.ErrTxUnknownPubKey
}
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
return types.ErrTxInvalidPubKey
}
acc.PubKey = in.PubKey
} else {
in.PubKey = account.PubKeyNil{}
}
return nil return nil
} }
return acc.(*account.Account).Copy()
func (s *State) ValidateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("ValidateInputs() expects account in accounts")
}
err = s.ValidateInput(acc, signBytes, in)
if err != nil {
return
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
} }
func (s *State) ValidateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) { // The account is copied before setting, so mutating it
// Check TxInput basic // afterwards has no side effects.
if err := in.ValidateBasic(); err != nil { // Implements Statelike
return err func (s *State) UpdateAccount(account *account.Account) bool {
return s.accounts.Set(account.Address, account.Copy())
} }
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { // Implements Statelike
return types.ErrTxInvalidSignature func (s *State) RemoveAccount(address []byte) bool {
_, removed := s.accounts.Remove(address)
return removed
} }
// Check sequences
if acc.Sequence+1 != in.Sequence { // The returned Account is a copy, so mutating it
return types.ErrTxInvalidSequence{ // has no side effects.
Got: uint64(in.Sequence), func (s *State) GetAccounts() merkle.Tree {
Expected: uint64(acc.Sequence + 1), return s.accounts.Copy()
}
}
// Check amount
if acc.Balance < in.Amount {
return types.ErrTxInsufficientFunds
} }
// State.accounts
//-------------------------------------
// State.validators
// The returned ValidatorInfo is a copy, so mutating it
// has no side effects.
func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo {
_, valInfo := s.validatorInfos.Get(address)
if valInfo == nil {
return nil return nil
} }
return valInfo.(*ValidatorInfo).Copy()
func (s *State) ValidateOutputs(outs []*types.TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
}
// Good. Add amount to total
total += out.Amount
}
return total, nil
} }
func (s *State) AdjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) { // Returns false if new, true if updated.
for _, in := range ins { // The valInfo is copied before setting, so mutating it
acc := accounts[string(in.Address)] // afterwards has no side effects.
if acc == nil { func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
panic("AdjustByInputs() expects account in accounts") return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
if acc.Balance < in.Amount {
panic("AdjustByInputs() expects sufficient funds")
}
acc.Balance -= in.Amount
acc.Sequence += 1
}
}
func (s *State) AdjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) {
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
panic("AdjustByOutputs() expects account in accounts")
}
acc.Balance += out.Amount
}
}
// If the tx is invalid, an error will be returned.
// Unlike AppendBlock(), state will not be altered.
func (s *State) ExecTx(tx_ types.Tx, runCall bool) error {
// TODO: do something with fees
fees := uint64(0)
// Exec tx
switch tx := tx_.(type) {
case *types.SendTx:
accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.Outputs)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.AdjustByOutputs(accounts, tx.Outputs)
s.UpdateAccounts(accounts)
return nil
case *types.CallTx:
var inAcc, outAcc *account.Account
// Validate input
inAcc = s.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
return err
}
signBytes := account.SignBytes(tx)
err := s.ValidateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("ValidateInput failed on %X:", tx.Input.Address))
return err
}
if tx.Input.Amount < tx.Fee {
log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return types.ErrTxInsufficientFunds
}
createAccount := len(tx.Address) == 0
if !createAccount {
// Validate output
if len(tx.Address) != 20 {
log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress
}
// 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
outAcc = s.GetAccount(tx.Address)
}
log.Debug(Fmt("Out account: %v", outAcc))
// Good!
value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1
if runCall {
var (
gas uint64 = tx.GasLimit
err error = nil
caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil
code []byte = nil
appState = NewVMAppState(s) // TODO: confusing.
params = vm.Params{
BlockHeight: uint64(s.LastBlockHeight),
BlockHash: vm.BytesToWord(s.LastBlockHash),
BlockTime: s.LastBlockTime.Unix(),
GasLimit: 10000000,
}
)
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
inAcc.Balance -= tx.Fee
s.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
return types.ErrTxInvalidAddress
}
callee = toVMAccount(outAcc)
code = callee.Code
log.Debug(Fmt("Calling contract %X with code %X", callee.Address.Address(), callee.Code))
} else {
callee, err = appState.CreateAccount(caller)
if err != nil {
log.Debug(Fmt("Error creating account"))
return err
}
log.Debug(Fmt("Created new account %X", callee.Address.Address()))
code = tx.Data
}
log.Debug(Fmt("Code for this contract: %X", code))
appState.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe.
appState.UpdateAccount(callee) // because we adjusted by input above.
vmach := vm.NewVM(appState, params, caller.Address)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
log.Debug(Fmt("Error on execution: %v", err))
inAcc.Balance -= tx.Fee
s.UpdateAccount(inAcc)
// Throw away 'appState' which holds incomplete updates (don't sync it).
} else {
log.Debug("Successful execution")
// Success
if createAccount {
callee.Code = ret
}
appState.Sync()
}
// Create a receipt from the ret and whether errored.
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc.Balance -= value
if createAccount {
inAcc.Sequence += 1
}
s.UpdateAccount(inAcc)
}
return nil
case *types.BondTx:
valInfo := s.GetValidatorInfo(tx.PubKey.Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
accounts, err := s.GetOrMakeAccounts(tx.Inputs, nil)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.UpdateAccounts(accounts)
// Add ValidatorInfo
s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: s.LastBlockHeight + 1,
FirstBondAmount: outTotal,
})
// Add Validator
added := s.BondedValidators.Add(&Validator{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: s.LastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
panic("Failed to add validator")
}
return nil
case *types.UnbondTx:
// The validator must be active
_, val := s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
s.unbondValidator(val)
return nil
case *types.RebondTx:
// The validator must be inactive
_, val := s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be equal to the next height
if tx.Height != s.LastBlockHeight+1 {
return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", s.LastBlockHeight+1, tx.Height))
}
// Good!
s.rebondValidator(val)
return nil
case *types.DupeoutTx:
// Verify the signatures
_, accused := s.BondedValidators.GetByAddress(tx.Address)
if accused == nil {
_, accused = s.UnbondingValidators.GetByAddress(tx.Address)
if accused == nil {
return types.ErrTxInvalidAddress
}
}
voteASignBytes := account.SignBytes(&tx.VoteA)
voteBSignBytes := account.SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return types.ErrTxInvalidSignature
}
// Verify equivocation
// TODO: in the future, just require one vote from a previous height that
// doesn't exist on this chain.
if tx.VoteA.Height != tx.VoteB.Height {
return errors.New("DupeoutTx heights don't match")
}
if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case (not an error, validator must be slashed!)
// Validators should not sign another vote after committing.
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
// We need to check both orderings of the votes
} else {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if tx.VoteA.Type != tx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes shouldn't match")
}
}
// Good! (Bad validator!)
s.destroyValidator(accused)
return nil
default:
panic("Unknown Tx type")
}
} }
func (s *State) unbondValidator(val *Validator) { func (s *State) unbondValidator(val *Validator) {
@ -582,12 +214,14 @@ func (s *State) releaseValidator(val *Validator) {
s.SetValidatorInfo(valInfo) s.SetValidatorInfo(valInfo)
// Send coins back to UnbondTo outputs // Send coins back to UnbondTo outputs
accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo) accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo)
if err != nil { if err != nil {
panic("Couldn't get or make unbondTo accounts") panic("Couldn't get or make unbondTo accounts")
} }
s.AdjustByOutputs(accounts, valInfo.UnbondTo) adjustByOutputs(accounts, valInfo.UnbondTo)
s.UpdateAccounts(accounts) for _, acc := range accounts {
s.UpdateAccount(acc)
}
// Remove validator from UnbondingValidators // Remove validator from UnbondingValidators
_, removed := s.UnbondingValidators.Remove(val.Address) _, removed := s.UnbondingValidators.Remove(val.Address)
@ -617,219 +251,26 @@ func (s *State) destroyValidator(val *Validator) {
} }
// NOTE: If an error occurs during block execution, state will be left // State.validators
// at an invalid state. Copy the state before calling AppendBlock! //-------------------------------------
func (s *State) AppendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error { // State.storage
err := s.appendBlock(block, blockPartsHeader)
if err != nil { func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) {
return err storage = merkle.NewIAVLTree(binary.BasicCodec, binary.BasicCodec, 1024, s.DB)
} storage.Load(hash)
// State.Hash should match block.StateHash return storage
stateHash := s.Hash()
if !bytes.Equal(stateHash, block.StateHash) {
return Errorf("Invalid state hash. Expected %X, got %X",
stateHash, block.StateHash)
}
return nil
} }
func (s *State) SetBlockStateHash(block *types.Block) error { // State.storage
sCopy := s.Copy() //-------------------------------------
err := sCopy.appendBlock(block, types.PartSetHeader{})
if err != nil { //-----------------------------------------------------------------------------
return err
} type InvalidTxError struct {
// Set block.StateHash Tx types.Tx
block.StateHash = sCopy.Hash() Reason error
return nil
} }
// Appends the block, does not check block.StateHash func (txErr InvalidTxError) Error() string {
// NOTE: If an error occurs during block execution, state will be left return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
// at an invalid state. Copy the state before calling appendBlock!
func (s *State) appendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
if err != nil {
return err
}
// Validate block Validation.
if block.Height == 1 {
if len(block.Validation.Commits) != 0 {
return errors.New("Block at height 1 (first block) should have no Validation commits")
}
} else {
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
}
var sumVotingPower uint64
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
vote := &types.Vote{
Height: block.Height - 1,
Round: commit.Round,
Type: types.VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
}
if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote))
err = errors.New("Invalid validation signature")
return true
}
}
})
if err != nil {
return err
}
if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 {
return errors.New("Insufficient validation voting power")
}
}
// Update Validator.LastCommitHeight as necessary.
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.LastBondedValidators.GetByIndex(uint(i))
if val == nil {
panic(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
panic("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
panic("Failed to update unbonding validator LastCommitHeight")
}
} else {
panic("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Commit each tx
for _, tx := range block.Data.Txs {
err := s.ExecTx(tx, true)
if err != nil {
return InvalidTxError{tx, err}
}
}
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight)
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
log.Info("Validator timeout", "validator", val, "height", block.Height)
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val)
}
// Increment validator AccumPowers
s.BondedValidators.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockTime = block.Time
return nil
}
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccount(address []byte) *account.Account {
_, acc := s.accounts.Get(address)
if acc == nil {
return nil
}
return acc.(*account.Account).Copy()
}
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccounts() merkle.Tree {
return s.accounts.Copy()
}
// The account is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) UpdateAccount(account *account.Account) {
s.accounts.Set(account.Address, account.Copy())
}
// The accounts are copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) UpdateAccounts(accounts map[string]*account.Account) {
for _, acc := range accounts {
s.accounts.Set(acc.Address, acc.Copy())
}
}
func (s *State) RemoveAccount(address []byte) bool {
_, removed := s.accounts.Remove(address)
return removed
}
// The returned ValidatorInfo is a copy, so mutating it
// has no side effects.
func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo {
_, valInfo := s.validatorInfos.Get(address)
if valInfo == nil {
return nil
}
return valInfo.(*ValidatorInfo).Copy()
}
// Returns false if new, true if updated.
// The valInfo is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
// Returns a hash that represents the state data,
// excluding Last*
func (s *State) Hash() []byte {
hashables := []merkle.Hashable{
s.BondedValidators,
s.UnbondingValidators,
s.accounts,
s.validatorInfos,
}
return merkle.HashFromHashables(hashables)
} }

View File

@ -10,6 +10,17 @@ import (
"time" "time"
) )
func execTxWithState(state *State, tx types.Tx, runCall bool) error {
cache := NewBlockCache(state)
err := ExecTx(cache, tx, runCall)
if err != nil {
return err
} else {
cache.Sync()
return nil
}
}
func TestCopyState(t *testing.T) { func TestCopyState(t *testing.T) {
// Generate a random state // Generate a random state
s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000) s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000)
@ -93,7 +104,7 @@ func TestGenesisSaveLoad(t *testing.T) {
blockParts := block.MakePartSet() blockParts := block.MakePartSet()
// Now append the block to s0. // Now append the block to s0.
err := s0.AppendBlock(block, blockParts.Header()) err := ExecBlock(s0, block, blockParts.Header())
if err != nil { if err != nil {
t.Error("Error appending initial block:", err) t.Error("Error appending initial block:", err)
} }
@ -182,7 +193,7 @@ func TestTxSequence(t *testing.T) {
tx := makeSendTx(sequence) tx := makeSendTx(sequence)
tx.Inputs[0].Signature = privAccounts[0].Sign(tx) tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
stateCopy := state.Copy() stateCopy := state.Copy()
err := stateCopy.ExecTx(tx, true) err := execTxWithState(stateCopy, tx, true)
if i == 1 { if i == 1 {
// Sequence is good. // Sequence is good.
if err != nil { if err != nil {
@ -241,7 +252,7 @@ func TestTxs(t *testing.T) {
} }
tx.Inputs[0].Signature = privAccounts[0].Sign(tx) tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
err := state.ExecTx(tx, true) err := execTxWithState(state, tx, true)
if err != nil { if err != nil {
t.Errorf("Got error in executing send transaction, %v", err) t.Errorf("Got error in executing send transaction, %v", err)
} }
@ -278,7 +289,7 @@ func TestTxs(t *testing.T) {
}, },
} }
tx.Inputs[0].Signature = privAccounts[0].Sign(tx) tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
err := state.ExecTx(tx, true) err := execTxWithState(state, tx, true)
if err != nil { if err != nil {
t.Errorf("Got error in executing bond transaction, %v", err) t.Errorf("Got error in executing bond transaction, %v", err)
} }
@ -345,7 +356,7 @@ func TestAddValidator(t *testing.T) {
} }
// Now append the block to s0. // Now append the block to s0.
err := s0.AppendBlock(block0, block0Parts.Header()) err := ExecBlock(s0, block0, block0Parts.Header())
if err != nil { if err != nil {
t.Error("Error appending initial block:", err) t.Error("Error appending initial block:", err)
} }
@ -379,7 +390,7 @@ func TestAddValidator(t *testing.T) {
}, nil, }, nil,
) )
block1Parts := block1.MakePartSet() block1Parts := block1.MakePartSet()
err = s0.AppendBlock(block1, block1Parts.Header()) err = ExecBlock(s0, block1, block1Parts.Header())
if err != nil { if err != nil {
t.Error("Error appending secondary block:", err) t.Error("Error appending secondary block:", err)
} }

191
state/tx_cache.go Normal file
View File

@ -0,0 +1,191 @@
package state
import (
ac "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
)
type TxCache struct {
backend *BlockCache
accounts map[Word256]vmAccountInfo
storages map[Tuple256]Word256
logs []*vm.Log
}
func NewTxCache(backend *BlockCache) *TxCache {
return &TxCache{
backend: backend,
accounts: make(map[Word256]vmAccountInfo),
storages: make(map[Tuple256]Word256),
logs: make([]*vm.Log, 0),
}
}
//-------------------------------------
// TxCache.account
func (cache *TxCache) GetAccount(addr Word256) *vm.Account {
acc, removed := vmUnpack(cache.accounts[addr])
if removed {
return nil
} else {
return acc
}
}
func (cache *TxCache) UpdateAccount(acc *vm.Account) {
addr := acc.Address
// SANITY CHECK
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("UpdateAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[addr] = vmAccountInfo{acc, false}
}
func (cache *TxCache) RemoveAccount(acc *vm.Account) {
addr := acc.Address
// SANITY CHECK
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("RemoveAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[addr] = vmAccountInfo{acc, true}
}
// Creates a 20 byte address and bumps the creator's nonce.
func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account {
// Generate an address
nonce := creator.Nonce
creator.Nonce += 1
addr := RightPadWord256(NewContractAddress(creator.Address.Prefix(20), nonce))
// Create account from address.
account, removed := vmUnpack(cache.accounts[addr])
if removed || account == nil {
account = &vm.Account{
Address: addr,
Balance: 0,
Code: nil,
Nonce: 0,
StorageRoot: Zero256,
}
cache.accounts[addr] = vmAccountInfo{account, false}
return account
} else {
panic(Fmt("Could not create account, address already exists: %X", addr))
}
}
// TxCache.account
//-------------------------------------
// TxCache.storage
func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 {
// Check cache
value, ok := cache.storages[Tuple256{addr, key}]
if ok {
return value
}
// Load from backend
return cache.backend.GetStorage(addr, key)
}
// NOTE: Set value to zero to removed from the trie.
func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) {
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("SetStorage() on a removed account")
}
cache.storages[Tuple256{addr, key}] = value
}
// TxCache.storage
//-------------------------------------
// These updates do not have to be in deterministic order,
// the backend is responsible for ordering updates.
func (cache *TxCache) Sync() {
// Remove or update storage
for addrKey, value := range cache.storages {
addr, key := Tuple256Split(addrKey)
cache.backend.SetStorage(addr, key, value)
}
// Remove or update accounts
for addr, accInfo := range cache.accounts {
acc, removed := vmUnpack(accInfo)
if removed {
cache.backend.RemoveAccount(addr.Prefix(20))
} else {
cache.backend.UpdateAccount(toStateAccount(acc))
}
}
// TODO support logs, add them to the cache somehow.
}
func (cache *TxCache) AddLog(log *vm.Log) {
cache.logs = append(cache.logs, log)
}
//-----------------------------------------------------------------------------
// Convenience function to return address of new contract
func NewContractAddress(caller []byte, nonce uint64) []byte {
temp := make([]byte, 32+8)
copy(temp, caller)
PutUint64(temp[32:], nonce)
return sha3.Sha3(temp)[:20]
}
// Converts backend.Account to vm.Account struct.
func toVMAccount(acc *ac.Account) *vm.Account {
return &vm.Account{
Address: RightPadWord256(acc.Address),
Balance: acc.Balance,
Code: acc.Code, // This is crazy.
Nonce: uint64(acc.Sequence),
StorageRoot: RightPadWord256(acc.StorageRoot),
Other: acc.PubKey,
}
}
// Converts vm.Account to backend.Account struct.
func toStateAccount(acc *vm.Account) *ac.Account {
pubKey, ok := acc.Other.(ac.PubKey)
if !ok {
pubKey = ac.PubKeyNil{}
}
var storageRoot []byte
if acc.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = acc.StorageRoot.Bytes()
}
return &ac.Account{
Address: acc.Address.Prefix(20),
PubKey: pubKey,
Balance: acc.Balance,
Code: acc.Code,
Sequence: uint(acc.Nonce),
StorageRoot: storageRoot,
}
}
type vmAccountInfo struct {
account *vm.Account
removed bool
}
func vmUnpack(accInfo vmAccountInfo) (*vm.Account, bool) {
return accInfo.account, accInfo.removed
}

View File

@ -1,265 +0,0 @@
package state
import (
"bytes"
"sort"
ac "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
)
// Converts state.Account to vm.Account struct.
func toVMAccount(acc *ac.Account) *vm.Account {
return &vm.Account{
Address: vm.BytesToWord(acc.Address),
Balance: acc.Balance,
Code: acc.Code, // This is crazy.
Nonce: uint64(acc.Sequence),
StorageRoot: vm.BytesToWord(acc.StorageRoot),
Other: acc.PubKey,
}
}
// Converts vm.Account to state.Account struct.
func toStateAccount(acc *vm.Account) *ac.Account {
pubKey, ok := acc.Other.(ac.PubKey)
if !ok {
pubKey = ac.PubKeyNil{}
}
var storageRoot []byte
if acc.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = acc.StorageRoot.Bytes()
}
return &ac.Account{
Address: acc.Address.Address(),
PubKey: pubKey,
Balance: acc.Balance,
Code: acc.Code,
Sequence: uint(acc.Nonce),
StorageRoot: storageRoot,
}
}
//-----------------------------------------------------------------------------
type AccountInfo struct {
account *vm.Account
deleted bool
}
type VMAppState struct {
state *State
accounts map[string]AccountInfo
storage map[string]vm.Word
logs []*vm.Log
}
func NewVMAppState(state *State) *VMAppState {
return &VMAppState{
state: state,
accounts: make(map[string]AccountInfo),
storage: make(map[string]vm.Word),
logs: make([]*vm.Log, 0),
}
}
func unpack(accInfo AccountInfo) (*vm.Account, bool) {
return accInfo.account, accInfo.deleted
}
func (vas *VMAppState) GetAccount(addr vm.Word) (*vm.Account, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if deleted {
return nil, Errorf("Account was deleted: %X", addr)
} else if account != nil {
return account, nil
} else {
acc := vas.state.GetAccount(addr.Address())
if acc == nil {
return nil, Errorf("Invalid account addr: %X", addr)
}
return toVMAccount(acc), nil
}
}
func (vas *VMAppState) UpdateAccount(account *vm.Account) error {
accountInfo, ok := vas.accounts[account.Address.String()]
if !ok {
vas.accounts[account.Address.String()] = AccountInfo{account, false}
return nil
}
account, deleted := unpack(accountInfo)
if deleted {
return Errorf("Account was deleted: %X", account.Address)
} else {
vas.accounts[account.Address.String()] = AccountInfo{account, false}
return nil
}
}
func (vas *VMAppState) DeleteAccount(account *vm.Account) error {
accountInfo, ok := vas.accounts[account.Address.String()]
if !ok {
vas.accounts[account.Address.String()] = AccountInfo{account, true}
return nil
}
account, deleted := unpack(accountInfo)
if deleted {
return Errorf("Account was already deleted: %X", account.Address)
} else {
vas.accounts[account.Address.String()] = AccountInfo{account, true}
return nil
}
}
// Creates a 20 byte address and bumps the creator's nonce.
func (vas *VMAppState) CreateAccount(creator *vm.Account) (*vm.Account, error) {
// Generate an address
nonce := creator.Nonce
creator.Nonce += 1
addr := vm.RightPadWord(NewContractAddress(creator.Address.Address(), nonce))
// Create account from address.
account, deleted := unpack(vas.accounts[addr.String()])
if deleted || account == nil {
account = &vm.Account{
Address: addr,
Balance: 0,
Code: nil,
Nonce: 0,
StorageRoot: vm.Zero,
}
vas.accounts[addr.String()] = AccountInfo{account, false}
return account, nil
} else {
panic(Fmt("Could not create account, address already exists: %X", addr))
// return nil, Errorf("Account already exists: %X", addr)
}
}
func (vas *VMAppState) GetStorage(addr vm.Word, key vm.Word) (vm.Word, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if account == nil {
return vm.Zero, Errorf("Invalid account addr: %X", addr)
} else if deleted {
return vm.Zero, Errorf("Account was deleted: %X", addr)
}
value, ok := vas.storage[addr.String()+key.String()]
if ok {
return value, nil
} else {
return vm.Zero, nil
}
}
// NOTE: Set value to zero to delete from the trie.
func (vas *VMAppState) SetStorage(addr vm.Word, key vm.Word, value vm.Word) (bool, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if account == nil {
return false, Errorf("Invalid account addr: %X", addr)
} else if deleted {
return false, Errorf("Account was deleted: %X", addr)
}
_, ok := vas.storage[addr.String()+key.String()]
vas.storage[addr.String()+key.String()] = value
return ok, nil
}
// CONTRACT the updates are in deterministic order.
func (vas *VMAppState) Sync() {
// Determine order for accounts
addrStrs := []string{}
for addrStr := range vas.accounts {
addrStrs = append(addrStrs, addrStr)
}
sort.Strings(addrStrs)
// Update or delete accounts.
for _, addrStr := range addrStrs {
account, deleted := unpack(vas.accounts[addrStr])
if deleted {
removed := vas.state.RemoveAccount(account.Address.Address())
if !removed {
panic(Fmt("Could not remove account to be deleted: %X", account.Address))
}
} else {
if account == nil {
panic(Fmt("Account should not be nil for addr: %X", account.Address))
}
vas.state.UpdateAccount(toStateAccount(account))
}
}
// Determine order for storage updates
// The address comes first so it'll be grouped.
storageKeyStrs := []string{}
for keyStr := range vas.storage {
storageKeyStrs = append(storageKeyStrs, keyStr)
}
sort.Strings(storageKeyStrs)
// Update storage for all account/key.
storage := merkle.NewIAVLTree(
binary.BasicCodec, // TODO change
binary.BasicCodec, // TODO change
1024, // TODO change.
vas.state.DB,
)
var currentAccount *vm.Account
var deleted bool
for _, storageKey := range storageKeyStrs {
value := vas.storage[storageKey]
addrKeyBytes := []byte(storageKey)
addr := addrKeyBytes[:32]
key := addrKeyBytes[32:]
if currentAccount == nil || !bytes.Equal(currentAccount.Address[:], addr) {
currentAccount, deleted = unpack(vas.accounts[string(addr)])
if deleted {
continue
}
var storageRoot []byte
if currentAccount.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = currentAccount.StorageRoot.Bytes()
}
storage.Load(storageRoot)
}
if value.IsZero() {
_, removed := storage.Remove(key)
if !removed {
panic(Fmt("Storage could not be removed for addr: %X @ %X", addr, key))
}
} else {
storage.Set(key, value)
}
}
// TODO support logs, add them to the state somehow.
}
func (vas *VMAppState) AddLog(log *vm.Log) {
vas.logs = append(vas.logs, log)
}
//-----------------------------------------------------------------------------
// Convenience function to return address of new contract
func NewContractAddress(caller []byte, nonce uint64) []byte {
temp := make([]byte, 32+8)
copy(temp, caller)
vm.PutUint64(temp[32:], nonce)
return sha3.Sha3(temp)[:20]
}

View File

@ -55,16 +55,24 @@ func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte,
return nil return nil
} }
// Computes and returns the block hash.
// If the block is incomplete (e.g. missing Header.StateHash)
// then the hash is nil, to prevent the usage of that hash.
func (b *Block) Hash() []byte { func (b *Block) Hash() []byte {
if b.Header == nil || b.Validation == nil || b.Data == nil { if b.Header == nil || b.Validation == nil || b.Data == nil {
return nil return nil
} }
hashes := [][]byte{ hashHeader := b.Header.Hash()
b.Header.Hash(), hashValidation := b.Validation.Hash()
b.Validation.Hash(), hashData := b.Data.Hash()
b.Data.Hash(),
// If hashHeader is nil, required fields are missing.
if len(hashHeader) == 0 {
return nil
} }
// Merkle hash from sub-hashes.
// Merkle hash from subhashes.
hashes := [][]byte{hashHeader, hashValidation, hashData}
return merkle.HashFromHashes(hashes) return merkle.HashFromHashes(hashes)
} }
@ -125,7 +133,12 @@ type Header struct {
StateHash []byte StateHash []byte
} }
// NOTE: hash is nil if required fields are missing.
func (h *Header) Hash() []byte { func (h *Header) Hash() []byte {
if len(h.StateHash) == 0 {
return nil
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
hasher, n, err := sha256.New(), new(int64), new(error) hasher, n, err := sha256.New(), new(int64), new(error)
binary.WriteBinary(h, buf, n, err) binary.WriteBinary(h, buf, n, err)

View File

@ -254,3 +254,10 @@ func (tx *DupeoutTx) WriteSignBytes(w io.Writer, n *int64, err *error) {
func (tx *DupeoutTx) String() string { func (tx *DupeoutTx) String() string {
return Fmt("DupeoutTx{%X,%v,%v}", tx.Address, tx.VoteA, tx.VoteB) return Fmt("DupeoutTx{%X,%v,%v}", tx.Address, tx.VoteA, tx.VoteB)
} }
//-----------------------------------------------------------------------------
func TxId(tx Tx) []byte {
signBytes := account.SignBytes(tx)
return binary.BinaryRipemd160(signBytes)
}

View File

@ -1,35 +0,0 @@
package vm
import (
"encoding/binary"
)
func Uint64ToWord(i uint64) Word {
word := Word{}
PutUint64(word[:], i)
return word
}
func BytesToWord(bz []byte) Word {
word := Word{}
copy(word[:], bz)
return word
}
func LeftPadWord(bz []byte) (word Word) {
copy(word[32-len(bz):], bz)
return
}
func RightPadWord(bz []byte) (word Word) {
copy(word[:], bz)
return
}
func GetUint64(word Word) uint64 {
return binary.LittleEndian.Uint64(word[:])
}
func PutUint64(dest []byte, i uint64) {
binary.LittleEndian.PutUint64(dest, i)
}

View File

@ -3,7 +3,6 @@ package vm
const ( const (
GasSha3 uint64 = 1 GasSha3 uint64 = 1
GasGetAccount uint64 = 1 GasGetAccount uint64 = 1
GasStorageCreate uint64 = 1
GasStorageUpdate uint64 = 1 GasStorageUpdate uint64 = 1
GasStackOp uint64 = 1 GasStackOp uint64 = 1

View File

@ -3,19 +3,18 @@ package vm
import ( import (
"code.google.com/p/go.crypto/ripemd160" "code.google.com/p/go.crypto/ripemd160"
"crypto/sha256" "crypto/sha256"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm/secp256k1" "github.com/tendermint/tendermint/vm/secp256k1"
"github.com/tendermint/tendermint/vm/sha3" "github.com/tendermint/tendermint/vm/sha3"
. "github.com/tendermint/tendermint/common"
) )
var nativeContracts = make(map[Word]NativeContract) var nativeContracts = make(map[Word256]NativeContract)
func init() { func init() {
nativeContracts[Uint64ToWord(1)] = ecrecoverFunc nativeContracts[Uint64ToWord256(1)] = ecrecoverFunc
nativeContracts[Uint64ToWord(2)] = sha256Func nativeContracts[Uint64ToWord256(2)] = sha256Func
nativeContracts[Uint64ToWord(3)] = ripemd160Func nativeContracts[Uint64ToWord256(3)] = ripemd160Func
nativeContracts[Uint64ToWord(4)] = identityFunc nativeContracts[Uint64ToWord256(4)] = identityFunc
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@ -2,11 +2,12 @@ package vm
import ( import (
"fmt" "fmt"
. "github.com/tendermint/tendermint/common"
) )
// Not goroutine safe // Not goroutine safe
type Stack struct { type Stack struct {
data []Word data []Word256
ptr int ptr int
gas *uint64 gas *uint64
@ -15,7 +16,7 @@ type Stack struct {
func NewStack(capacity int, gas *uint64, err *error) *Stack { func NewStack(capacity int, gas *uint64, err *error) *Stack {
return &Stack{ return &Stack{
data: make([]Word, capacity), data: make([]Word256, capacity),
ptr: 0, ptr: 0,
gas: gas, gas: gas,
err: err, err: err,
@ -36,7 +37,7 @@ func (st *Stack) setErr(err error) {
} }
} }
func (st *Stack) Push(d Word) { func (st *Stack) Push(d Word256) {
st.useGas(GasStackOp) st.useGas(GasStackOp)
if st.ptr == cap(st.data) { if st.ptr == cap(st.data) {
st.setErr(ErrDataStackOverflow) st.setErr(ErrDataStackOverflow)
@ -50,18 +51,18 @@ func (st *Stack) PushBytes(bz []byte) {
if len(bz) != 32 { if len(bz) != 32 {
panic("Invalid bytes size: expected 32") panic("Invalid bytes size: expected 32")
} }
st.Push(BytesToWord(bz)) st.Push(RightPadWord256(bz))
} }
func (st *Stack) Push64(i uint64) { func (st *Stack) Push64(i uint64) {
st.Push(Uint64ToWord(i)) st.Push(Uint64ToWord256(i))
} }
func (st *Stack) Pop() Word { func (st *Stack) Pop() Word256 {
st.useGas(GasStackOp) st.useGas(GasStackOp)
if st.ptr == 0 { if st.ptr == 0 {
st.setErr(ErrDataStackUnderflow) st.setErr(ErrDataStackUnderflow)
return Zero return Zero256
} }
st.ptr-- st.ptr--
return st.data[st.ptr] return st.data[st.ptr]
@ -72,7 +73,7 @@ func (st *Stack) PopBytes() []byte {
} }
func (st *Stack) Pop64() uint64 { func (st *Stack) Pop64() uint64 {
return GetUint64(st.Pop()) return GetUint64(st.Pop().Bytes())
} }
func (st *Stack) Len() int { func (st *Stack) Len() int {
@ -100,7 +101,7 @@ func (st *Stack) Dup(n int) {
} }
// Not an opcode, costs no gas. // Not an opcode, costs no gas.
func (st *Stack) Peek() Word { func (st *Stack) Peek() Word256 {
return st.data[st.ptr-1] return st.data[st.ptr-1]
} }

View File

@ -1,8 +1,6 @@
package main package vm
import ( import (
"fmt"
. "github.com/tendermint/tendermint/common" . "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/vm" . "github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3" "github.com/tendermint/tendermint/vm/sha3"
@ -10,41 +8,39 @@ import (
type FakeAppState struct { type FakeAppState struct {
accounts map[string]*Account accounts map[string]*Account
storage map[string]Word storage map[string]Word256
logs []*Log logs []*Log
} }
func (fas *FakeAppState) GetAccount(addr Word) (*Account, error) { func (fas *FakeAppState) GetAccount(addr Word256) *Account {
account := fas.accounts[addr.String()] account := fas.accounts[addr.String()]
if account != nil { if account != nil {
return account, nil return account
} else { } else {
return nil, Errorf("Invalid account addr: %v", addr) panic(Fmt("Invalid account addr: %X", addr))
} }
} }
func (fas *FakeAppState) UpdateAccount(account *Account) error { func (fas *FakeAppState) UpdateAccount(account *Account) {
_, ok := fas.accounts[account.Address.String()] _, ok := fas.accounts[account.Address.String()]
if !ok { if !ok {
return Errorf("Invalid account addr: %v", account.Address.String()) panic(Fmt("Invalid account addr: %X", account.Address))
} else { } else {
// Nothing to do // Nothing to do
return nil
} }
} }
func (fas *FakeAppState) DeleteAccount(account *Account) error { func (fas *FakeAppState) RemoveAccount(account *Account) {
_, ok := fas.accounts[account.Address.String()] _, ok := fas.accounts[account.Address.String()]
if !ok { if !ok {
return Errorf("Invalid account addr: %v", account.Address.String()) panic(Fmt("Invalid account addr: %X", account.Address))
} else { } else {
// Delete account // Remove account
delete(fas.accounts, account.Address.String()) delete(fas.accounts, account.Address.String())
return nil
} }
} }
func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) { func (fas *FakeAppState) CreateAccount(creator *Account) *Account {
addr := createAddress(creator) addr := createAddress(creator)
account := fas.accounts[addr.String()] account := fas.accounts[addr.String()]
if account == nil { if account == nil {
@ -53,75 +49,46 @@ func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) {
Balance: 0, Balance: 0,
Code: nil, Code: nil,
Nonce: 0, Nonce: 0,
StorageRoot: Zero, StorageRoot: Zero256,
}, nil }
} else { } else {
return nil, Errorf("Invalid account addr: %v", addr) panic(Fmt("Invalid account addr: %X", addr))
} }
} }
func (fas *FakeAppState) GetStorage(addr Word, key Word) (Word, error) { func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 {
_, ok := fas.accounts[addr.String()] _, ok := fas.accounts[addr.String()]
if !ok { if !ok {
return Zero, Errorf("Invalid account addr: %v", addr) panic(Fmt("Invalid account addr: %X", addr))
} }
value, ok := fas.storage[addr.String()+key.String()] value, ok := fas.storage[addr.String()+key.String()]
if ok { if ok {
return value, nil return value
} else { } else {
return Zero, nil return Zero256
} }
} }
func (fas *FakeAppState) SetStorage(addr Word, key Word, value Word) (bool, error) { func (fas *FakeAppState) SetStorage(addr Word256, key Word256, value Word256) {
_, ok := fas.accounts[addr.String()] _, ok := fas.accounts[addr.String()]
if !ok { if !ok {
return false, Errorf("Invalid account addr: %v", addr) panic(Fmt("Invalid account addr: %X", addr))
} }
_, ok = fas.storage[addr.String()+key.String()]
fas.storage[addr.String()+key.String()] = value fas.storage[addr.String()+key.String()] = value
return ok, nil
} }
func (fas *FakeAppState) AddLog(log *Log) { func (fas *FakeAppState) AddLog(log *Log) {
fas.logs = append(fas.logs, log) fas.logs = append(fas.logs, log)
} }
func main() {
appState := &FakeAppState{
accounts: make(map[string]*Account),
storage: make(map[string]Word),
logs: nil,
}
params := Params{
BlockHeight: 0,
BlockHash: Zero,
BlockTime: 0,
GasLimit: 0,
}
ourVm := NewVM(appState, params, Zero)
// Create accounts
account1 := &Account{
Address: Uint64ToWord(100),
}
account2 := &Account{
Address: Uint64ToWord(101),
}
var gas uint64 = 1000
output, err := ourVm.Call(account1, account2, []byte{0x5B, 0x60, 0x00, 0x56}, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
}
// Creates a 20 byte address and bumps the nonce. // Creates a 20 byte address and bumps the nonce.
func createAddress(creator *Account) Word { func createAddress(creator *Account) Word256 {
nonce := creator.Nonce nonce := creator.Nonce
creator.Nonce += 1 creator.Nonce += 1
temp := make([]byte, 32+8) temp := make([]byte, 32+8)
copy(temp, creator.Address[:]) copy(temp, creator.Address[:])
PutUint64(temp[32:], nonce) PutUint64(temp[32:], nonce)
return RightPadWord(sha3.Sha3(temp)[:20]) return RightPadWord256(sha3.Sha3(temp)[:20])
} }

99
vm/test/vm_test.go Normal file
View File

@ -0,0 +1,99 @@
package vm
import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
"testing"
"time"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/vm"
)
func newAppState() *FakeAppState {
return &FakeAppState{
accounts: make(map[string]*Account),
storage: make(map[string]Word256),
logs: nil,
}
}
func newParams() Params {
return Params{
BlockHeight: 0,
BlockHash: Zero256,
BlockTime: 0,
GasLimit: 0,
}
}
func makeBytes(n int) []byte {
b := make([]byte, n)
rand.Read(b)
return b
}
func TestVM(t *testing.T) {
ourVm := NewVM(newAppState(), newParams(), Zero256)
// Create accounts
account1 := &Account{
Address: Uint64ToWord256(100),
}
account2 := &Account{
Address: Uint64ToWord256(101),
}
var gas uint64 = 1000
N := []byte{0xff, 0xff}
// 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, []byte{0x60, 0x20, 0x51, 0x12, 0x15, 0x60, byte(0x1b + len(N)), 0x57, 0x60, 0x01, 0x60, 0x20, 0x51, 0x01, 0x60, 0x20, 0x52, 0x60, 0x05, 0x56, 0x5B}...)
start := time.Now()
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
fmt.Println("Call took:", time.Since(start))
}
func TestSubcurrency(t *testing.T) {
st := newAppState()
// Create accounts
account1 := &Account{
Address: RightPadWord256(makeBytes(20)),
}
account2 := &Account{
Address: RightPadWord256(makeBytes(20)),
}
st.accounts[account1.Address.String()] = account1
st.accounts[account2.Address.String()] = account2
ourVm := NewVM(st, newParams(), Zero256)
var gas uint64 = 1000
code_parts := []string{"620f42403355",
"7c0100000000000000000000000000000000000000000000000000000000",
"600035046315cf268481141561004657",
"6004356040526040515460605260206060f35b63693200ce81141561008757",
"60043560805260243560a052335460c0523360e05260a05160c05112151561008657",
"60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"}
code, _ := hex.DecodeString(strings.Join(code_parts, ""))
fmt.Printf("Code: %x\n", code)
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
}
/*
// infinite loop
code := []byte{0x5B, 0x60, 0x00, 0x56}
// mstore
code := []byte{0x60, 0x00, 0x60, 0x20}
// mstore, mload
code := []byte{0x60, 0x01, 0x60, 0x20, 0x52, 0x60, 0x20, 0x51}
*/

View File

@ -1,44 +1,25 @@
package vm package vm
import () import (
. "github.com/tendermint/tendermint/common"
)
const ( const (
defaultDataStackCapacity = 10 defaultDataStackCapacity = 10
) )
var (
Zero = Word{0}
One = Word{1}
)
type Word [32]byte
func (w Word) String() string { return string(w[:]) }
func (w Word) Copy() Word { return w }
func (w Word) Bytes() []byte { return w[:] } // copied.
func (w Word) Address() []byte { return w[:20] }
func (w Word) IsZero() bool {
accum := byte(0)
for _, byt := range w {
accum |= byt
}
return accum == 0
}
//-----------------------------------------------------------------------------
type Account struct { type Account struct {
Address Word Address Word256
Balance uint64 Balance uint64
Code []byte Code []byte
Nonce uint64 Nonce uint64
StorageRoot Word StorageRoot Word256
Other interface{} // For holding all other data. Other interface{} // For holding all other data.
} }
type Log struct { type Log struct {
Address Word Address Word256
Topics []Word Topics []Word256
Data []byte Data []byte
Height uint64 Height uint64
} }
@ -46,14 +27,14 @@ type Log struct {
type AppState interface { type AppState interface {
// Accounts // Accounts
GetAccount(addr Word) (*Account, error) GetAccount(addr Word256) *Account
UpdateAccount(*Account) error UpdateAccount(*Account)
DeleteAccount(*Account) error RemoveAccount(*Account)
CreateAccount(*Account) (*Account, error) CreateAccount(*Account) *Account
// Storage // Storage
GetStorage(Word, Word) (Word, error) GetStorage(Word256, Word256) Word256
SetStorage(Word, Word, Word) (bool, error) // Setting to Zero is deleting. SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting.
// Logs // Logs
AddLog(*Log) AddLog(*Log)
@ -61,7 +42,7 @@ type AppState interface {
type Params struct { type Params struct {
BlockHeight uint64 BlockHeight uint64
BlockHash Word BlockHash Word256
BlockTime int64 BlockTime int64
GasLimit uint64 GasLimit uint64
} }

349
vm/vm.go
View File

@ -3,13 +3,14 @@ package vm
import ( import (
"errors" "errors"
"fmt" "fmt"
"math" "math/big"
. "github.com/tendermint/tendermint/common" . "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm/sha3" "github.com/tendermint/tendermint/vm/sha3"
) )
var ( var (
ErrUnknownAddress = errors.New("Unknown address")
ErrInsufficientBalance = errors.New("Insufficient balance") ErrInsufficientBalance = errors.New("Insufficient balance")
ErrInvalidJumpDest = errors.New("Invalid jump dest") ErrInvalidJumpDest = errors.New("Invalid jump dest")
ErrInsufficientGas = errors.New("Insuffient gas") ErrInsufficientGas = errors.New("Insuffient gas")
@ -23,21 +24,30 @@ var (
ErrInvalidContract = errors.New("Invalid contract") ErrInvalidContract = errors.New("Invalid contract")
) )
type Debug bool
const ( const (
dataStackCapacity = 1024 dataStackCapacity = 1024
callStackCapacity = 100 // TODO ensure usage. callStackCapacity = 100 // TODO ensure usage.
memoryCapacity = 1024 * 1024 // 1 MB memoryCapacity = 1024 * 1024 // 1 MB
dbg Debug = true
) )
func (d Debug) Printf(s string, a ...interface{}) {
if d {
fmt.Printf(s, a...)
}
}
type VM struct { type VM struct {
appState AppState appState AppState
params Params params Params
origin Word origin Word256
callDepth int callDepth int
} }
func NewVM(appState AppState, params Params, origin Word) *VM { func NewVM(appState AppState, params Params, origin Word256) *VM {
return &VM{ return &VM{
appState: appState, appState: appState,
params: params, params: params,
@ -73,7 +83,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga
// Just like Call() but does not transfer 'value' or modify the callDepth. // Just like Call() but does not transfer 'value' or modify the callDepth.
func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) {
fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input)
var ( var (
pc uint64 = 0 pc uint64 = 0
@ -89,7 +99,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
} }
var op = codeGetOp(code, pc) var op = codeGetOp(code, pc)
fmt.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) dbg.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len())
switch op { switch op {
@ -97,164 +107,197 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, nil return nil, nil
case ADD: // 0x01 case ADD: // 0x01
x, y := stack.Pop64(), stack.Pop64() //x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x + y) //stack.Push64(x + y)
fmt.Printf(" %v + %v = %v\n", x, y, x+y) x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
sum := new(big.Int).Add(xb, yb)
stack.Push(RightPadWord256(flip(sum.Bytes())))
dbg.Printf(" %v + %v = %v\n", xb, yb, sum)
case MUL: // 0x02 case MUL: // 0x02
x, y := stack.Pop64(), stack.Pop64() //x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x * y) //stack.Push64(x * y)
fmt.Printf(" %v * %v = %v\n", x, y, x*y) x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
prod := new(big.Int).Mul(xb, yb)
stack.Push(RightPadWord256(flip(prod.Bytes())))
dbg.Printf(" %v * %v = %v\n", xb, yb, prod)
case SUB: // 0x03 case SUB: // 0x03
x, y := stack.Pop64(), stack.Pop64() //x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x - y) //stack.Push64(x - y)
fmt.Printf(" %v - %v = %v\n", x, y, x-y) x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
diff := new(big.Int).Sub(xb, yb)
stack.Push(RightPadWord256(flip(diff.Bytes())))
dbg.Printf(" %v - %v = %v\n", xb, yb, diff)
case DIV: // 0x04 case DIV: // 0x04
x, y := stack.Pop64(), stack.Pop64() //x, y := stack.Pop64(), stack.Pop64()
if y == 0 { // TODO //stack.Push64(x / y)
stack.Push(Zero) x, y := stack.Pop(), stack.Pop()
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) if y.IsZero() { // TODO
stack.Push(Zero256)
dbg.Printf(" %x / %x = %v (TODO)\n", x, y, 0)
} else { } else {
stack.Push64(x / y) xb := new(big.Int).SetBytes(flip(x[:]))
fmt.Printf(" %v / %v = %v\n", x, y, x/y) yb := new(big.Int).SetBytes(flip(y[:]))
div := new(big.Int).Div(xb, yb)
stack.Push(RightPadWord256(flip(div.Bytes())))
dbg.Printf(" %v / %v = %v\n", xb, yb, div)
} }
case SDIV: // 0x05 case SDIV: // 0x05
// TODO ... big?
x, y := int64(stack.Pop64()), int64(stack.Pop64()) x, y := int64(stack.Pop64()), int64(stack.Pop64())
if y == 0 { // TODO if y == 0 { // TODO
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) dbg.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
} else { } else {
stack.Push64(uint64(x / y)) stack.Push64(uint64(x / y))
fmt.Printf(" %v / %v = %v\n", x, y, x/y) dbg.Printf(" %v / %v = %v\n", x, y, x/y)
} }
case MOD: // 0x06 case MOD: // 0x06
x, y := stack.Pop64(), stack.Pop64() //x, y := stack.Pop64(), stack.Pop64()
if y == 0 { // TODO x, y := stack.Pop(), stack.Pop()
stack.Push(Zero) if y.IsZero() { // TODO
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) stack.Push(Zero256)
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
} else { } else {
stack.Push64(x % y) xb := new(big.Int).SetBytes(flip(x[:]))
fmt.Printf(" %v %% %v = %v\n", x, y, x%y) yb := new(big.Int).SetBytes(flip(y[:]))
mod := new(big.Int).Mod(xb, yb)
stack.Push(RightPadWord256(flip(mod.Bytes())))
dbg.Printf(" %v %% %v = %v\n", xb, yb, mod)
} }
case SMOD: // 0x07 case SMOD: // 0x07
// TODO ... big?
x, y := int64(stack.Pop64()), int64(stack.Pop64()) x, y := int64(stack.Pop64()), int64(stack.Pop64())
if y == 0 { // TODO if y == 0 { // TODO
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
} else { } else {
stack.Push64(uint64(x % y)) stack.Push64(uint64(x % y))
fmt.Printf(" %v %% %v = %v\n", x, y, x%y) dbg.Printf(" %v %% %v = %v\n", x, y, x%y)
} }
case ADDMOD: // 0x08 case ADDMOD: // 0x08
// TODO ... big?
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64() x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
if z == 0 { // TODO if z == 0 { // TODO
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
} else { } else {
stack.Push64(x % y) stack.Push64((x + y) % z)
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z) dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
} }
case MULMOD: // 0x09 case MULMOD: // 0x09
// TODO ... big?
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64() x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
if z == 0 { // TODO if z == 0 { // TODO
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
} else { } else {
stack.Push64(x % y) stack.Push64((x * y) % z)
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z) dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
} }
case EXP: // 0x0A case EXP: // 0x0A
x, y := stack.Pop64(), stack.Pop64() //x, y := stack.Pop64(), stack.Pop64()
stack.Push64(ExpUint64(x, y)) //stack.Push64(ExpUint64(x, y))
fmt.Printf(" %v ** %v = %v\n", x, y, uint64(math.Pow(float64(x), float64(y)))) x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
pow := new(big.Int).Exp(xb, yb, big.NewInt(0))
stack.Push(RightPadWord256(flip(pow.Bytes())))
dbg.Printf(" %v ** %v = %v\n", xb, yb, pow)
case SIGNEXTEND: // 0x0B case SIGNEXTEND: // 0x0B
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
res := (y << uint(x)) >> x res := (y << uint(x)) >> x
stack.Push64(res) stack.Push64(res)
fmt.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res) dbg.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res)
case LT: // 0x10 case LT: // 0x10
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
if x < y { if x < y {
stack.Push64(1) stack.Push64(1)
} else { } else {
stack.Push(Zero) stack.Push(Zero256)
} }
fmt.Printf(" %v < %v = %v\n", x, y, x < y) dbg.Printf(" %v < %v = %v\n", x, y, x < y)
case GT: // 0x11 case GT: // 0x11
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
if x > y { if x > y {
stack.Push64(1) stack.Push64(1)
} else { } else {
stack.Push(Zero) stack.Push(Zero256)
} }
fmt.Printf(" %v > %v = %v\n", x, y, x > y) dbg.Printf(" %v > %v = %v\n", x, y, x > y)
case SLT: // 0x12 case SLT: // 0x12
x, y := int64(stack.Pop64()), int64(stack.Pop64()) x, y := int64(stack.Pop64()), int64(stack.Pop64())
if x < y { if x < y {
stack.Push64(1) stack.Push64(1)
} else { } else {
stack.Push(Zero) stack.Push(Zero256)
} }
fmt.Printf(" %v < %v = %v\n", x, y, x < y) dbg.Printf(" %v < %v = %v\n", x, y, x < y)
case SGT: // 0x13 case SGT: // 0x13
x, y := int64(stack.Pop64()), int64(stack.Pop64()) x, y := int64(stack.Pop64()), int64(stack.Pop64())
if x > y { if x > y {
stack.Push64(1) stack.Push64(1)
} else { } else {
stack.Push(Zero) stack.Push(Zero256)
} }
fmt.Printf(" %v > %v = %v\n", x, y, x > y) dbg.Printf(" %v > %v = %v\n", x, y, x > y)
case EQ: // 0x14 case EQ: // 0x14
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
if x > y { if x == y {
stack.Push64(1) stack.Push64(1)
} else { } else {
stack.Push(Zero) stack.Push(Zero256)
} }
fmt.Printf(" %v == %v = %v\n", x, y, x == y) dbg.Printf(" %v == %v = %v\n", x, y, x == y)
case ISZERO: // 0x15 case ISZERO: // 0x15
x := stack.Pop64() x := stack.Pop64()
if x == 0 { if x == 0 {
stack.Push64(1) stack.Push64(1)
} else { } else {
stack.Push(Zero) stack.Push(Zero256)
} }
fmt.Printf(" %v == 0 = %v\n", x, x == 0) dbg.Printf(" %v == 0 = %v\n", x, x == 0)
case AND: // 0x16 case AND: // 0x16
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x & y) stack.Push64(x & y)
fmt.Printf(" %v & %v = %v\n", x, y, x&y) dbg.Printf(" %v & %v = %v\n", x, y, x&y)
case OR: // 0x17 case OR: // 0x17
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x | y) stack.Push64(x | y)
fmt.Printf(" %v | %v = %v\n", x, y, x|y) dbg.Printf(" %v | %v = %v\n", x, y, x|y)
case XOR: // 0x18 case XOR: // 0x18
x, y := stack.Pop64(), stack.Pop64() x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x ^ y) stack.Push64(x ^ y)
fmt.Printf(" %v ^ %v = %v\n", x, y, x^y) dbg.Printf(" %v ^ %v = %v\n", x, y, x^y)
case NOT: // 0x19 case NOT: // 0x19
x := stack.Pop64() x := stack.Pop64()
stack.Push64(^x) stack.Push64(^x)
fmt.Printf(" !%v = %v\n", x, ^x) dbg.Printf(" !%v = %v\n", x, ^x)
case BYTE: // 0x1A case BYTE: // 0x1A
idx, val := stack.Pop64(), stack.Pop() idx, val := stack.Pop64(), stack.Pop()
@ -263,7 +306,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
res = val[idx] res = val[idx]
} }
stack.Push64(uint64(res)) stack.Push64(uint64(res))
fmt.Printf(" => 0x%X\n", res) dbg.Printf(" => 0x%X\n", res)
case SHA3: // 0x20 case SHA3: // 0x20
if ok = useGas(gas, GasSha3); !ok { if ok = useGas(gas, GasSha3); !ok {
@ -276,36 +319,36 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
} }
data = sha3.Sha3(data) data = sha3.Sha3(data)
stack.PushBytes(data) stack.PushBytes(data)
fmt.Printf(" => (%v) %X\n", size, data) dbg.Printf(" => (%v) %X\n", size, data)
case ADDRESS: // 0x30 case ADDRESS: // 0x30
stack.Push(callee.Address) stack.Push(callee.Address)
fmt.Printf(" => %X\n", callee.Address) dbg.Printf(" => %X\n", callee.Address)
case BALANCE: // 0x31 case BALANCE: // 0x31
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas) return nil, firstErr(err, ErrInsufficientGas)
} }
account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported. acc := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
if err_ != nil { if acc == nil {
return nil, firstErr(err, err_) return nil, firstErr(err, ErrUnknownAddress)
} }
balance := account.Balance balance := acc.Balance
stack.Push64(balance) stack.Push64(balance)
fmt.Printf(" => %v (%X)\n", balance, addr) dbg.Printf(" => %v (%X)\n", balance, addr)
case ORIGIN: // 0x32 case ORIGIN: // 0x32
stack.Push(vm.origin) stack.Push(vm.origin)
fmt.Printf(" => %X\n", vm.origin) dbg.Printf(" => %X\n", vm.origin)
case CALLER: // 0x33 case CALLER: // 0x33
stack.Push(caller.Address) stack.Push(caller.Address)
fmt.Printf(" => %X\n", caller.Address) dbg.Printf(" => %X\n", caller.Address)
case CALLVALUE: // 0x34 case CALLVALUE: // 0x34
stack.Push64(value) stack.Push64(value)
fmt.Printf(" => %v\n", value) dbg.Printf(" => %v\n", value)
case CALLDATALOAD: // 0x35 case CALLDATALOAD: // 0x35
offset := stack.Pop64() offset := stack.Pop64()
@ -313,12 +356,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok { if !ok {
return nil, firstErr(err, ErrInputOutOfBounds) return nil, firstErr(err, ErrInputOutOfBounds)
} }
stack.Push(RightPadWord(data)) stack.Push(RightPadWord256(data))
fmt.Printf(" => 0x%X\n", data) dbg.Printf(" => 0x%X\n", data)
case CALLDATASIZE: // 0x36 case CALLDATASIZE: // 0x36
stack.Push64(uint64(len(input))) stack.Push64(uint64(len(input)))
fmt.Printf(" => %d\n", len(input)) dbg.Printf(" => %d\n", len(input))
case CALLDATACOPY: // 0x37 case CALLDATACOPY: // 0x37
memOff := stack.Pop64() memOff := stack.Pop64()
@ -333,18 +376,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
copy(dest, data) copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data)
case CODESIZE: // 0x38 case CODESIZE: // 0x38
l := uint64(len(code)) l := uint64(len(code))
stack.Push64(l) stack.Push64(l)
fmt.Printf(" => %d\n", l) dbg.Printf(" => %d\n", l)
case CODECOPY: // 0x39 case CODECOPY: // 0x39
memOff := stack.Pop64() memOff := stack.Pop64()
codeOff := stack.Pop64() codeOff := stack.Pop64()
length := stack.Pop64() length := stack.Pop64()
fmt.Println("CODECOPY: codeOff, length, codelength", codeOff, length, len(code))
data, ok := subslice(code, codeOff, length, false) data, ok := subslice(code, codeOff, length, false)
if !ok { if !ok {
return nil, firstErr(err, ErrCodeOutOfBounds) return nil, firstErr(err, ErrCodeOutOfBounds)
@ -354,36 +396,36 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
copy(dest, data) copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
case GASPRICE_DEPRECATED: // 0x3A case GASPRICE_DEPRECATED: // 0x3A
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n") dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
case EXTCODESIZE: // 0x3B case EXTCODESIZE: // 0x3B
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas) return nil, firstErr(err, ErrInsufficientGas)
} }
account, err_ := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
if err_ != nil { if acc == nil {
return nil, firstErr(err, err_) return nil, firstErr(err, ErrUnknownAddress)
} }
code := account.Code code := acc.Code
l := uint64(len(code)) l := uint64(len(code))
stack.Push64(l) stack.Push64(l)
fmt.Printf(" => %d\n", l) dbg.Printf(" => %d\n", l)
case EXTCODECOPY: // 0x3C case EXTCODECOPY: // 0x3C
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas) return nil, firstErr(err, ErrInsufficientGas)
} }
account, err_ := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
if err_ != nil { if acc == nil {
return nil, firstErr(err, err_) return nil, firstErr(err, ErrUnknownAddress)
} }
code := account.Code code := acc.Code
memOff := stack.Pop64() memOff := stack.Pop64()
codeOff := stack.Pop64() codeOff := stack.Pop64()
length := stack.Pop64() length := stack.Pop64()
@ -396,33 +438,33 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
copy(dest, data) copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
case BLOCKHASH: // 0x40 case BLOCKHASH: // 0x40
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
case COINBASE: // 0x41 case COINBASE: // 0x41
stack.Push(Zero) stack.Push(Zero256)
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
case TIMESTAMP: // 0x42 case TIMESTAMP: // 0x42
time := vm.params.BlockTime time := vm.params.BlockTime
stack.Push64(uint64(time)) stack.Push64(uint64(time))
fmt.Printf(" => 0x%X\n", time) dbg.Printf(" => 0x%X\n", time)
case BLOCKHEIGHT: // 0x43 case BLOCKHEIGHT: // 0x43
number := uint64(vm.params.BlockHeight) number := uint64(vm.params.BlockHeight)
stack.Push64(number) stack.Push64(number)
fmt.Printf(" => 0x%X\n", number) dbg.Printf(" => 0x%X\n", number)
case GASLIMIT: // 0x45 case GASLIMIT: // 0x45
stack.Push64(vm.params.GasLimit) stack.Push64(vm.params.GasLimit)
fmt.Printf(" => %v\n", vm.params.GasLimit) dbg.Printf(" => %v\n", vm.params.GasLimit)
case POP: // 0x50 case POP: // 0x50
stack.Pop() stack.Pop()
fmt.Printf(" => %v\n", vm.params.GasLimit) dbg.Printf(" => %v\n", vm.params.GasLimit)
case MLOAD: // 0x51 case MLOAD: // 0x51
offset := stack.Pop64() offset := stack.Pop64()
@ -430,17 +472,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok { if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
stack.Push(RightPadWord(data)) stack.Push(RightPadWord256(data))
fmt.Printf(" => 0x%X\n", data) dbg.Printf(" => 0x%X\n", data)
case MSTORE: // 0x52 case MSTORE: // 0x52
offset, data := stack.Pop64(), stack.Pop() offset, data := stack.Pop64(), stack.Pop()
dest, ok := subslice(memory, offset, 32, true) dest, ok := subslice(memory, offset, 32, false)
if !ok { if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
copy(dest, data[:]) copy(dest, flip(data[:]))
fmt.Printf(" => 0x%X\n", data) dbg.Printf(" => 0x%X\n", data)
case MSTORE8: // 0x53 case MSTORE8: // 0x53
offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF)
@ -448,26 +490,19 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
memory[offset] = val memory[offset] = val
fmt.Printf(" => [%v] 0x%X\n", offset, val) dbg.Printf(" => [%v] 0x%X\n", offset, val)
case SLOAD: // 0x54 case SLOAD: // 0x54
loc := stack.Pop() loc := stack.Pop()
data, _ := vm.appState.GetStorage(callee.Address, loc) data := vm.appState.GetStorage(callee.Address, loc)
stack.Push(data) stack.Push(data)
fmt.Printf(" {0x%X : 0x%X}\n", loc, data) dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
case SSTORE: // 0x55 case SSTORE: // 0x55
loc, data := stack.Pop(), stack.Pop() loc, data := stack.Pop(), stack.Pop()
updated, err_ := vm.appState.SetStorage(callee.Address, loc, data) vm.appState.SetStorage(callee.Address, loc, data)
if err = firstErr(err, err_); err != nil {
return nil, err
}
if updated {
useGas(gas, GasStorageUpdate) useGas(gas, GasStorageUpdate)
} else { dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
useGas(gas, GasStorageCreate)
}
fmt.Printf(" {0x%X : 0x%X}\n", loc, data)
case JUMP: // 0x56 case JUMP: // 0x56
err = jump(code, stack.Pop64(), &pc) err = jump(code, stack.Pop64(), &pc)
@ -479,7 +514,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
err = jump(code, pos, &pc) err = jump(code, pos, &pc)
continue continue
} }
fmt.Printf(" ~> false\n") dbg.Printf(" ~> false\n")
case PC: // 0x58 case PC: // 0x58
stack.Push64(pc) stack.Push64(pc)
@ -489,10 +524,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
case GAS: // 0x5A case GAS: // 0x5A
stack.Push64(*gas) stack.Push64(*gas)
fmt.Printf(" => %X\n", *gas) dbg.Printf(" => %X\n", *gas)
case JUMPDEST: // 0x5B case JUMPDEST: // 0x5B
fmt.Printf("\n") dbg.Printf("\n")
// Do nothing // Do nothing
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
@ -501,24 +536,24 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok { if !ok {
return nil, firstErr(err, ErrCodeOutOfBounds) return nil, firstErr(err, ErrCodeOutOfBounds)
} }
res := RightPadWord(codeSegment) res := RightPadWord256(codeSegment)
stack.Push(res) stack.Push(res)
pc += a pc += a
fmt.Printf(" => 0x%X\n", res) dbg.Printf(" => 0x%X\n", res)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1) n := int(op - DUP1 + 1)
stack.Dup(n) stack.Dup(n)
fmt.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) dbg.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes())
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
n := int(op - SWAP1 + 2) n := int(op - SWAP1 + 2)
stack.Swap(n) stack.Swap(n)
fmt.Printf(" => [%d]\n", n) dbg.Printf(" => [%d]\n", n)
case LOG0, LOG1, LOG2, LOG3, LOG4: case LOG0, LOG1, LOG2, LOG3, LOG4:
n := int(op - LOG0) n := int(op - LOG0)
topics := make([]Word, n) topics := make([]Word256, n)
offset, size := stack.Pop64(), stack.Pop64() offset, size := stack.Pop64(), stack.Pop64()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
topics[i] = stack.Pop() topics[i] = stack.Pop()
@ -534,7 +569,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
vm.params.BlockHeight, vm.params.BlockHeight,
} }
vm.appState.AddLog(log) vm.appState.AddLog(log)
fmt.Printf(" => %v\n", log) dbg.Printf(" => %v\n", log)
case CREATE: // 0xF0 case CREATE: // 0xF0
contractValue := stack.Pop64() contractValue := stack.Pop64()
@ -551,27 +586,22 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
// TODO charge for gas to create account _ the code length * GasCreateByte // TODO charge for gas to create account _ the code length * GasCreateByte
newAccount, err := vm.appState.CreateAccount(callee) newAccount := vm.appState.CreateAccount(callee)
if err != nil {
stack.Push(Zero)
fmt.Printf(" (*) 0x0 %v\n", err)
} else {
// Run the input to get the contract code. // Run the input to get the contract code.
ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas) ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas)
if err_ != nil { if err_ != nil {
stack.Push(Zero) stack.Push(Zero256)
} else { } else {
newAccount.Code = ret // Set the code newAccount.Code = ret // Set the code
stack.Push(newAccount.Address) stack.Push(newAccount.Address)
} }
}
case CALL, CALLCODE: // 0xF1, 0xF2 case CALL, CALLCODE: // 0xF1, 0xF2
gasLimit := stack.Pop64() gasLimit := stack.Pop64()
addr, value := stack.Pop(), stack.Pop64() addr, value := stack.Pop(), stack.Pop64()
inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs
retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs
fmt.Printf(" => %X\n", addr) dbg.Printf(" => %X\n", addr)
// Get the arguments from the memory // Get the arguments from the memory
args, ok := subslice(memory, inOffset, inSize, false) args, ok := subslice(memory, inOffset, inSize, false)
@ -598,22 +628,22 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if ok = useGas(gas, GasGetAccount); !ok { if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas) return nil, firstErr(err, ErrInsufficientGas)
} }
account, err_ := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
if err = firstErr(err, err_); err != nil { if acc == nil {
return nil, err return nil, firstErr(err, ErrUnknownAddress)
} }
if op == CALLCODE { if op == CALLCODE {
ret, err = vm.Call(callee, callee, account.Code, args, value, gas) ret, err = vm.Call(callee, callee, acc.Code, args, value, gas)
} else { } else {
ret, err = vm.Call(callee, account, account.Code, args, value, gas) ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
} }
} }
// Push result // Push result
if err != nil { if err != nil {
stack.Push(Zero) stack.Push(Zero256)
} else { } else {
stack.Push(One) stack.Push(One256)
dest, ok := subslice(memory, retOffset, retSize, false) dest, ok := subslice(memory, retOffset, retSize, false)
if !ok { if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
@ -624,7 +654,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
// Handle remaining gas. // Handle remaining gas.
*gas += gasLimit *gas += gasLimit
fmt.Printf("resume %X (%v)\n", callee.Address, gas) dbg.Printf("resume %X (%v)\n", callee.Address, gas)
case RETURN: // 0xF3 case RETURN: // 0xF3
offset, size := stack.Pop64(), stack.Pop64() offset, size := stack.Pop64(), stack.Pop64()
@ -632,7 +662,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok { if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds) return nil, firstErr(err, ErrMemoryOutOfBounds)
} }
fmt.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret)
return ret, nil return ret, nil
case SUICIDE: // 0xFF case SUICIDE: // 0xFF
@ -640,20 +670,20 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if ok = useGas(gas, GasGetAccount); !ok { if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas) return nil, firstErr(err, ErrInsufficientGas)
} }
// TODO if the receiver is Zero, then make it the fee. // TODO if the receiver is , then make it the fee.
receiver, err_ := vm.appState.GetAccount(addr) receiver := vm.appState.GetAccount(addr)
if err = firstErr(err, err_); err != nil { if receiver == nil {
return nil, err return nil, firstErr(err, ErrUnknownAddress)
} }
balance := callee.Balance balance := callee.Balance
receiver.Balance += balance receiver.Balance += balance
vm.appState.UpdateAccount(receiver) vm.appState.UpdateAccount(receiver)
vm.appState.DeleteAccount(callee) vm.appState.RemoveAccount(callee)
fmt.Printf(" => (%X) %v\n", addr[:4], balance) dbg.Printf(" => (%X) %v\n", addr[:4], balance)
fallthrough fallthrough
default: default:
fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op)
panic(fmt.Errorf("Invalid opcode %X", op)) panic(fmt.Errorf("Invalid opcode %X", op))
} }
@ -688,10 +718,10 @@ func codeGetOp(code []byte, n uint64) OpCode {
func jump(code []byte, to uint64, pc *uint64) (err error) { func jump(code []byte, to uint64, pc *uint64) (err error) {
dest := codeGetOp(code, to) dest := codeGetOp(code, to)
if dest != JUMPDEST { if dest != JUMPDEST {
fmt.Printf(" ~> %v invalid jump dest %v\n", to, dest) dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest)
return ErrInvalidJumpDest return ErrInvalidJumpDest
} }
fmt.Printf(" ~> %v\n", to) dbg.Printf(" ~> %v\n", to)
*pc = to *pc = to
return nil return nil
} }
@ -724,10 +754,25 @@ func transfer(from, to *Account, amount uint64) error {
} }
func flip(in []byte) []byte { func flip(in []byte) []byte {
l2 := len(in) / 2
flipped := make([]byte, len(in)) flipped := make([]byte, len(in))
for i := 0; i < len(flipped)/2; i++ { // copy the middle bit (if its even it will get overwritten)
if len(in) != 0 {
flipped[l2] = in[l2]
}
for i := 0; i < l2; i++ {
flipped[i] = in[len(in)-1-i] flipped[i] = in[len(in)-1-i]
flipped[len(in)-1-i] = in[i] flipped[len(in)-1-i] = in[i]
} }
return flipped return flipped
} }
func flipWord(in Word256) Word256 {
word := Word256{}
// copy the middle bit (if its even it will get overwritten)
for i := 0; i < 16; i++ {
word[i] = in[len(in)-1-i]
word[len(in)-1-i] = in[i]
}
return word
}