mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-04 19:12:14 +00:00
commit
ffa82b5c61
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
.bak
|
||||
tendermint
|
||||
.DS_Store
|
||||
rpc/test/.tendermint
|
||||
|
@ -38,13 +38,13 @@ type Account struct {
|
||||
StorageRoot []byte // VM storage merkle root.
|
||||
}
|
||||
|
||||
func (account *Account) Copy() *Account {
|
||||
accountCopy := *account
|
||||
return &accountCopy
|
||||
func (acc *Account) Copy() *Account {
|
||||
accCopy := *acc
|
||||
return &accCopy
|
||||
}
|
||||
|
||||
func (account *Account) String() string {
|
||||
return fmt.Sprintf("Account{%X:%v C:%v S:%X}", account.Address, account.PubKey, len(account.Code), account.StorageRoot)
|
||||
func (acc *Account) String() string {
|
||||
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) {
|
||||
|
@ -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 {
|
||||
return privAccount.PrivKey.Sign(SignBytes(o))
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@ -58,6 +59,35 @@ var _ = RegisterInterface(
|
||||
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{}
|
||||
@ -287,9 +317,9 @@ func validateComplexArray(o interface{}, t *testing.T) {
|
||||
var testCases = []TestCase{}
|
||||
|
||||
func init() {
|
||||
//testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
|
||||
//testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
|
||||
//testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
|
||||
testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
|
||||
testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
|
||||
testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
|
||||
testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray})
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,7 @@ FOR_LOOP:
|
||||
break SYNC_LOOP
|
||||
} else {
|
||||
bcR.pool.PopRequest()
|
||||
err := bcR.state.AppendBlock(first, firstPartsHeader)
|
||||
err := sm.ExecBlock(bcR.state, first, firstPartsHeader)
|
||||
if err != nil {
|
||||
// TODO This is bad, are we zombie?
|
||||
panic(Fmt("Failed to process committed block: %v", err))
|
||||
|
@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@ -18,3 +19,13 @@ func SearchUint64s(a []uint64, x uint64) int {
|
||||
}
|
||||
|
||||
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
78
common/word.go
Normal 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) }
|
@ -1015,7 +1015,8 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
|
||||
}
|
||||
|
||||
// Already staged?
|
||||
if cs.stagedBlock == block {
|
||||
blockHash := block.Hash()
|
||||
if cs.stagedBlock != nil && len(blockHash) != 0 && bytes.Equal(cs.stagedBlock.Hash(), blockHash) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1024,7 +1025,7 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
|
||||
|
||||
// Commit block onto the copied state.
|
||||
// NOTE: Basic validation is done in state.AppendBlock().
|
||||
err := stateCopy.AppendBlock(block, blockParts.Header())
|
||||
err := sm.ExecBlock(stateCopy, block, blockParts.Header())
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/rpc"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
)
|
||||
|
||||
@ -150,13 +151,17 @@ func (n *Node) DialSeed() {
|
||||
}
|
||||
|
||||
func (n *Node) StartRpc() {
|
||||
rpc.SetRPCBlockStore(n.blockStore)
|
||||
rpc.SetRPCConsensusState(n.consensusState)
|
||||
rpc.SetRPCMempoolReactor(n.mempoolReactor)
|
||||
rpc.SetRPCSwitch(n.sw)
|
||||
core.SetBlockStore(n.blockStore)
|
||||
core.SetConsensusState(n.consensusState)
|
||||
core.SetMempoolReactor(n.mempoolReactor)
|
||||
core.SetSwitch(n.sw)
|
||||
rpc.StartHTTPServer()
|
||||
}
|
||||
|
||||
func (n *Node) Switch() *p2p.Switch {
|
||||
return n.sw
|
||||
}
|
||||
|
||||
func (n *Node) ConsensusState() *consensus.ConsensusState {
|
||||
return n.consensusState
|
||||
}
|
||||
|
@ -19,12 +19,14 @@ import (
|
||||
type Mempool struct {
|
||||
mtx sync.Mutex
|
||||
state *sm.State
|
||||
cache *sm.BlockCache
|
||||
txs []types.Tx
|
||||
}
|
||||
|
||||
func NewMempool(state *sm.State) *Mempool {
|
||||
return &Mempool{
|
||||
state: state,
|
||||
cache: sm.NewBlockCache(state),
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,11 +34,15 @@ func (mem *Mempool) GetState() *sm.State {
|
||||
return mem.state
|
||||
}
|
||||
|
||||
func (mem *Mempool) GetCache() *sm.BlockCache {
|
||||
return mem.cache
|
||||
}
|
||||
|
||||
// Apply tx to the state and remember it.
|
||||
func (mem *Mempool) AddTx(tx types.Tx) (err error) {
|
||||
mem.mtx.Lock()
|
||||
defer mem.mtx.Unlock()
|
||||
err = mem.state.ExecTx(tx, false)
|
||||
err = sm.ExecTx(mem.cache, tx, false)
|
||||
if err != nil {
|
||||
log.Debug("AddTx() error", "tx", tx, "error", err)
|
||||
return err
|
||||
@ -62,6 +68,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) {
|
||||
mem.mtx.Lock()
|
||||
defer mem.mtx.Unlock()
|
||||
mem.state = state.Copy()
|
||||
mem.cache = sm.NewBlockCache(mem.state)
|
||||
|
||||
// First, create a lookup map of txns in new block.
|
||||
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.
|
||||
validTxs := []types.Tx{}
|
||||
for _, tx := range txs {
|
||||
err := mem.state.ExecTx(tx, false)
|
||||
err := sm.ExecTx(mem.cache, tx, false)
|
||||
if err == nil {
|
||||
log.Debug("Filter in, valid", "tx", tx)
|
||||
validTxs = append(validTxs, tx)
|
||||
|
@ -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
32
rpc/core/accounts.go
Normal 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
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
package rpc
|
||||
package core
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"fmt"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"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 {
|
||||
maxHeight = blockStore.Height()
|
||||
} else {
|
||||
@ -26,30 +25,20 @@ func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
blockMetas = append(blockMetas, blockMeta)
|
||||
}
|
||||
|
||||
WriteAPIResponse(w, API_OK, struct {
|
||||
LastHeight uint
|
||||
BlockMetas []*types.BlockMeta
|
||||
}{blockStore.Height(), blockMetas})
|
||||
return &ResponseBlockchainInfo{blockStore.Height(), blockMetas}, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func GetBlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
height, _ := GetParamUint(r, "height")
|
||||
func GetBlock(height uint) (*ResponseGetBlock, error) {
|
||||
if height == 0 {
|
||||
WriteAPIResponse(w, API_INVALID_PARAM, "height must be greater than 1")
|
||||
return
|
||||
return nil, fmt.Errorf("height must be greater than 1")
|
||||
}
|
||||
if height > blockStore.Height() {
|
||||
WriteAPIResponse(w, API_INVALID_PARAM, "height must be less than the current blockchain height")
|
||||
return
|
||||
return nil, fmt.Errorf("height must be less than the current blockchain height")
|
||||
}
|
||||
|
||||
blockMeta := blockStore.LoadBlockMeta(height)
|
||||
block := blockStore.LoadBlock(height)
|
||||
|
||||
WriteAPIResponse(w, API_OK, struct {
|
||||
BlockMeta *types.BlockMeta
|
||||
Block *types.Block
|
||||
}{blockMeta, block})
|
||||
return &ResponseGetBlock{blockMeta, block}, nil
|
||||
}
|
7
rpc/core/log.go
Normal file
7
rpc/core/log.go
Normal 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
41
rpc/core/mempool.go
Normal 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
39
rpc/core/net.go
Normal 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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package rpc
|
||||
package core
|
||||
|
||||
import (
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
@ -12,18 +12,18 @@ var consensusState *consensus.ConsensusState
|
||||
var mempoolReactor *mempl.MempoolReactor
|
||||
var p2pSwitch *p2p.Switch
|
||||
|
||||
func SetRPCBlockStore(bs *bc.BlockStore) {
|
||||
func SetBlockStore(bs *bc.BlockStore) {
|
||||
blockStore = bs
|
||||
}
|
||||
|
||||
func SetRPCConsensusState(cs *consensus.ConsensusState) {
|
||||
func SetConsensusState(cs *consensus.ConsensusState) {
|
||||
consensusState = cs
|
||||
}
|
||||
|
||||
func SetRPCMempoolReactor(mr *mempl.MempoolReactor) {
|
||||
func SetMempoolReactor(mr *mempl.MempoolReactor) {
|
||||
mempoolReactor = mr
|
||||
}
|
||||
|
||||
func SetRPCSwitch(sw *p2p.Switch) {
|
||||
func SetSwitch(sw *p2p.Switch) {
|
||||
p2pSwitch = sw
|
||||
}
|
59
rpc/core/responses.go
Normal file
59
rpc/core/responses.go
Normal 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
|
||||
}
|
@ -1,37 +1,21 @@
|
||||
package rpc
|
||||
package core
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"fmt"
|
||||
"github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"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 {
|
||||
if privAccount == nil || privAccount.PrivKey == nil {
|
||||
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid (empty) privAccount @%v", i))
|
||||
return
|
||||
return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i)
|
||||
}
|
||||
}
|
||||
|
||||
switch tx.(type) {
|
||||
case *types.SendTx:
|
||||
sendTx := tx.(*types.SendTx)
|
||||
@ -39,6 +23,10 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
input.PubKey = privAccounts[i].PubKey
|
||||
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:
|
||||
bondTx := tx.(*types.BondTx)
|
||||
for i, input := range bondTx.Inputs {
|
||||
@ -52,6 +40,5 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rebondTx := tx.(*types.RebondTx)
|
||||
rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519)
|
||||
}
|
||||
|
||||
WriteAPIResponse(w, API_OK, struct{ types.Tx }{tx})
|
||||
return &ResponseSignTx{tx}, nil
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package rpc
|
||||
package core
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
)
|
||||
|
||||
func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func ListValidators() (*ResponseListValidators, error) {
|
||||
var blockHeight uint
|
||||
var bondedValidators []*sm.Validator
|
||||
var unbondingValidators []*sm.Validator
|
||||
@ -22,9 +22,5 @@ func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return false
|
||||
})
|
||||
|
||||
WriteAPIResponse(w, API_OK, struct {
|
||||
BlockHeight uint
|
||||
BondedValidators []*sm.Validator
|
||||
UnbondingValidators []*sm.Validator
|
||||
}{blockHeight, bondedValidators, unbondingValidators})
|
||||
return &ResponseListValidators{blockHeight, bondedValidators, unbondingValidators}, nil
|
||||
}
|
@ -1,21 +1,277 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/tendermint/tendermint/binary"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
"io/ioutil"
|
||||
"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() {
|
||||
http.HandleFunc("/status", StatusHandler)
|
||||
http.HandleFunc("/net_info", NetInfoHandler)
|
||||
http.HandleFunc("/blockchain", BlockchainInfoHandler)
|
||||
http.HandleFunc("/get_block", GetBlockHandler)
|
||||
http.HandleFunc("/get_account", GetAccountHandler)
|
||||
http.HandleFunc("/list_validators", ListValidatorsHandler)
|
||||
http.HandleFunc("/broadcast_tx", BroadcastTxHandler)
|
||||
// HTTP endpoints
|
||||
// toHandler runs once for each function and caches
|
||||
// all reflection data
|
||||
http.HandleFunc("/status", toHandler("status"))
|
||||
http.HandleFunc("/net_info", toHandler("net_info"))
|
||||
http.HandleFunc("/blockchain", toHandler("blockchain"))
|
||||
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("/get_storage", GetStorageHandler)
|
||||
|
||||
http.HandleFunc("/develop/gen_priv_account", GenPrivAccountHandler)
|
||||
http.HandleFunc("/develop/list_accounts", ListAccountsHandler)
|
||||
http.HandleFunc("/develop/sign_tx", SignTxHandler)
|
||||
// JsonRPC endpoints
|
||||
http.HandleFunc("/", JsonRpcHandler)
|
||||
// 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
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ var (
|
||||
)
|
||||
|
||||
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 {
|
||||
|
@ -38,16 +38,22 @@ const (
|
||||
type APIResponse struct {
|
||||
Status APIStatus `json:"status"`
|
||||
Data interface{} `json:"data"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func (res APIResponse) Error() string {
|
||||
return fmt.Sprintf("Status(%v) %v", res.Status, res.Data)
|
||||
func (res APIResponse) StatusError() string {
|
||||
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.Status = status
|
||||
if data == nil {
|
||||
// so json doesn't vommit
|
||||
data = struct{}{}
|
||||
}
|
||||
res.Data = data
|
||||
res.Error = responseErr
|
||||
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
binary.WriteJSON(res, buf, n, err)
|
||||
@ -109,7 +115,7 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler {
|
||||
|
||||
// If APIResponse,
|
||||
if res, ok := e.(APIResponse); ok {
|
||||
WriteAPIResponse(rww, res.Status, res.Data)
|
||||
WriteAPIResponse(rww, res.Status, nil, res.Error)
|
||||
} else {
|
||||
// For the rest,
|
||||
rww.WriteHeader(http.StatusInternalServerError)
|
||||
|
@ -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=...
|
||||
*/
|
33
rpc/net.go
33
rpc/net.go
@ -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})
|
||||
}
|
24
rpc/test/.tendermint/genesis.json
Normal file
24
rpc/test/.tendermint/genesis.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
1
rpc/test/.tendermint/priv_validator.json
Executable file
1
rpc/test/.tendermint/priv_validator.json
Executable 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
150
rpc/test/http_rpc_test.go
Normal 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
172
rpc/test/json_rpc_test.go
Normal 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
216
rpc/test/test.go
Normal 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
221
state/block_cache.go
Normal 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
18
state/common.go
Normal 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
593
state/execution.go
Normal 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")
|
||||
}
|
||||
}
|
723
state/state.go
723
state/state.go
@ -2,17 +2,14 @@ package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
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.
|
||||
type State struct {
|
||||
DB dbm.DB
|
||||
@ -78,7 +64,6 @@ func LoadState(db dbm.DB) *State {
|
||||
return s
|
||||
}
|
||||
|
||||
// Save this state into the db.
|
||||
func (s *State) Save() {
|
||||
s.accounts.Save()
|
||||
s.validatorInfos.Save()
|
||||
@ -98,6 +83,9 @@ func (s *State) Save() {
|
||||
s.DB.Set(stateKey, buf.Bytes())
|
||||
}
|
||||
|
||||
// CONTRACT:
|
||||
// Copy() is a cheap way to take a snapshot,
|
||||
// as if State were copied by value.
|
||||
func (s *State) Copy() *State {
|
||||
return &State{
|
||||
DB: s.DB,
|
||||
@ -113,437 +101,81 @@ func (s *State) Copy() *State {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (s *State) GetOrMakeAccounts(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 := s.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
|
||||
// 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,
|
||||
}
|
||||
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
|
||||
return merkle.HashFromHashables(hashables)
|
||||
}
|
||||
|
||||
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 (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) {
|
||||
// Check TxInput basic
|
||||
if err := in.ValidateBasic(); err != nil {
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// Set block.StateHash
|
||||
block.StateHash = sCopy.Hash()
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
//-------------------------------------
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
return total, nil
|
||||
return acc.(*account.Account).Copy()
|
||||
}
|
||||
|
||||
func (s *State) 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
|
||||
}
|
||||
// The account is copied before setting, so mutating it
|
||||
// afterwards has no side effects.
|
||||
// Implements Statelike
|
||||
func (s *State) UpdateAccount(account *account.Account) bool {
|
||||
return s.accounts.Set(account.Address, account.Copy())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// Implements Statelike
|
||||
func (s *State) RemoveAccount(address []byte) bool {
|
||||
_, removed := s.accounts.Remove(address)
|
||||
return removed
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// The returned Account is a copy, so mutating it
|
||||
// has no side effects.
|
||||
func (s *State) GetAccounts() merkle.Tree {
|
||||
return s.accounts.Copy()
|
||||
}
|
||||
|
||||
// TODO: do something with fees
|
||||
fees := uint64(0)
|
||||
// State.accounts
|
||||
//-------------------------------------
|
||||
// State.validators
|
||||
|
||||
// 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)
|
||||
// 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
|
||||
|
||||
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")
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
func (s *State) unbondValidator(val *Validator) {
|
||||
@ -582,12 +214,14 @@ func (s *State) releaseValidator(val *Validator) {
|
||||
s.SetValidatorInfo(valInfo)
|
||||
|
||||
// Send coins back to UnbondTo outputs
|
||||
accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo)
|
||||
accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo)
|
||||
if err != nil {
|
||||
panic("Couldn't get or make unbondTo accounts")
|
||||
}
|
||||
s.AdjustByOutputs(accounts, valInfo.UnbondTo)
|
||||
s.UpdateAccounts(accounts)
|
||||
adjustByOutputs(accounts, valInfo.UnbondTo)
|
||||
for _, acc := range accounts {
|
||||
s.UpdateAccount(acc)
|
||||
}
|
||||
|
||||
// Remove validator from UnbondingValidators
|
||||
_, 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
|
||||
// at an invalid state. Copy the state before calling AppendBlock!
|
||||
func (s *State) AppendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error {
|
||||
err := s.appendBlock(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
|
||||
// State.validators
|
||||
//-------------------------------------
|
||||
// State.storage
|
||||
|
||||
func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) {
|
||||
storage = merkle.NewIAVLTree(binary.BasicCodec, binary.BasicCodec, 1024, s.DB)
|
||||
storage.Load(hash)
|
||||
return storage
|
||||
}
|
||||
|
||||
func (s *State) SetBlockStateHash(block *types.Block) error {
|
||||
sCopy := s.Copy()
|
||||
err := sCopy.appendBlock(block, types.PartSetHeader{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Set block.StateHash
|
||||
block.StateHash = sCopy.Hash()
|
||||
return nil
|
||||
// State.storage
|
||||
//-------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type InvalidTxError struct {
|
||||
Tx types.Tx
|
||||
Reason error
|
||||
}
|
||||
|
||||
// Appends the 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 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)
|
||||
func (txErr InvalidTxError) Error() string {
|
||||
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
|
||||
}
|
||||
|
@ -10,6 +10,17 @@ import (
|
||||
"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) {
|
||||
// Generate a random state
|
||||
s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000)
|
||||
@ -93,7 +104,7 @@ func TestGenesisSaveLoad(t *testing.T) {
|
||||
blockParts := block.MakePartSet()
|
||||
|
||||
// Now append the block to s0.
|
||||
err := s0.AppendBlock(block, blockParts.Header())
|
||||
err := ExecBlock(s0, block, blockParts.Header())
|
||||
if err != nil {
|
||||
t.Error("Error appending initial block:", err)
|
||||
}
|
||||
@ -182,7 +193,7 @@ func TestTxSequence(t *testing.T) {
|
||||
tx := makeSendTx(sequence)
|
||||
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
|
||||
stateCopy := state.Copy()
|
||||
err := stateCopy.ExecTx(tx, true)
|
||||
err := execTxWithState(stateCopy, tx, true)
|
||||
if i == 1 {
|
||||
// Sequence is good.
|
||||
if err != nil {
|
||||
@ -241,7 +252,7 @@ func TestTxs(t *testing.T) {
|
||||
}
|
||||
|
||||
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
|
||||
err := state.ExecTx(tx, true)
|
||||
err := execTxWithState(state, tx, true)
|
||||
if err != nil {
|
||||
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)
|
||||
err := state.ExecTx(tx, true)
|
||||
err := execTxWithState(state, tx, true)
|
||||
if err != nil {
|
||||
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.
|
||||
err := s0.AppendBlock(block0, block0Parts.Header())
|
||||
err := ExecBlock(s0, block0, block0Parts.Header())
|
||||
if err != nil {
|
||||
t.Error("Error appending initial block:", err)
|
||||
}
|
||||
@ -379,7 +390,7 @@ func TestAddValidator(t *testing.T) {
|
||||
}, nil,
|
||||
)
|
||||
block1Parts := block1.MakePartSet()
|
||||
err = s0.AppendBlock(block1, block1Parts.Header())
|
||||
err = ExecBlock(s0, block1, block1Parts.Header())
|
||||
if err != nil {
|
||||
t.Error("Error appending secondary block:", err)
|
||||
}
|
||||
|
191
state/tx_cache.go
Normal file
191
state/tx_cache.go
Normal 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
|
||||
}
|
@ -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]
|
||||
}
|
@ -55,16 +55,24 @@ func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte,
|
||||
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 {
|
||||
if b.Header == nil || b.Validation == nil || b.Data == nil {
|
||||
return nil
|
||||
}
|
||||
hashes := [][]byte{
|
||||
b.Header.Hash(),
|
||||
b.Validation.Hash(),
|
||||
b.Data.Hash(),
|
||||
hashHeader := b.Header.Hash()
|
||||
hashValidation := b.Validation.Hash()
|
||||
hashData := 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)
|
||||
}
|
||||
|
||||
@ -125,7 +133,12 @@ type Header struct {
|
||||
StateHash []byte
|
||||
}
|
||||
|
||||
// NOTE: hash is nil if required fields are missing.
|
||||
func (h *Header) Hash() []byte {
|
||||
if len(h.StateHash) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
hasher, n, err := sha256.New(), new(int64), new(error)
|
||||
binary.WriteBinary(h, buf, n, err)
|
||||
|
@ -254,3 +254,10 @@ func (tx *DupeoutTx) WriteSignBytes(w io.Writer, n *int64, err *error) {
|
||||
func (tx *DupeoutTx) String() string {
|
||||
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)
|
||||
}
|
||||
|
35
vm/common.go
35
vm/common.go
@ -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)
|
||||
}
|
@ -3,7 +3,6 @@ package vm
|
||||
const (
|
||||
GasSha3 uint64 = 1
|
||||
GasGetAccount uint64 = 1
|
||||
GasStorageCreate uint64 = 1
|
||||
GasStorageUpdate uint64 = 1
|
||||
|
||||
GasStackOp uint64 = 1
|
||||
|
13
vm/native.go
13
vm/native.go
@ -3,19 +3,18 @@ package vm
|
||||
import (
|
||||
"code.google.com/p/go.crypto/ripemd160"
|
||||
"crypto/sha256"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/vm/secp256k1"
|
||||
"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() {
|
||||
nativeContracts[Uint64ToWord(1)] = ecrecoverFunc
|
||||
nativeContracts[Uint64ToWord(2)] = sha256Func
|
||||
nativeContracts[Uint64ToWord(3)] = ripemd160Func
|
||||
nativeContracts[Uint64ToWord(4)] = identityFunc
|
||||
nativeContracts[Uint64ToWord256(1)] = ecrecoverFunc
|
||||
nativeContracts[Uint64ToWord256(2)] = sha256Func
|
||||
nativeContracts[Uint64ToWord256(3)] = ripemd160Func
|
||||
nativeContracts[Uint64ToWord256(4)] = identityFunc
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
19
vm/stack.go
19
vm/stack.go
@ -2,11 +2,12 @@ package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
// Not goroutine safe
|
||||
type Stack struct {
|
||||
data []Word
|
||||
data []Word256
|
||||
ptr int
|
||||
|
||||
gas *uint64
|
||||
@ -15,7 +16,7 @@ type Stack struct {
|
||||
|
||||
func NewStack(capacity int, gas *uint64, err *error) *Stack {
|
||||
return &Stack{
|
||||
data: make([]Word, capacity),
|
||||
data: make([]Word256, capacity),
|
||||
ptr: 0,
|
||||
gas: gas,
|
||||
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)
|
||||
if st.ptr == cap(st.data) {
|
||||
st.setErr(ErrDataStackOverflow)
|
||||
@ -50,18 +51,18 @@ func (st *Stack) PushBytes(bz []byte) {
|
||||
if len(bz) != 32 {
|
||||
panic("Invalid bytes size: expected 32")
|
||||
}
|
||||
st.Push(BytesToWord(bz))
|
||||
st.Push(RightPadWord256(bz))
|
||||
}
|
||||
|
||||
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)
|
||||
if st.ptr == 0 {
|
||||
st.setErr(ErrDataStackUnderflow)
|
||||
return Zero
|
||||
return Zero256
|
||||
}
|
||||
st.ptr--
|
||||
return st.data[st.ptr]
|
||||
@ -72,7 +73,7 @@ func (st *Stack) PopBytes() []byte {
|
||||
}
|
||||
|
||||
func (st *Stack) Pop64() uint64 {
|
||||
return GetUint64(st.Pop())
|
||||
return GetUint64(st.Pop().Bytes())
|
||||
}
|
||||
|
||||
func (st *Stack) Len() int {
|
||||
@ -100,7 +101,7 @@ func (st *Stack) Dup(n int) {
|
||||
}
|
||||
|
||||
// Not an opcode, costs no gas.
|
||||
func (st *Stack) Peek() Word {
|
||||
func (st *Stack) Peek() Word256 {
|
||||
return st.data[st.ptr-1]
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
package main
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
. "github.com/tendermint/tendermint/vm"
|
||||
"github.com/tendermint/tendermint/vm/sha3"
|
||||
@ -10,41 +8,39 @@ import (
|
||||
|
||||
type FakeAppState struct {
|
||||
accounts map[string]*Account
|
||||
storage map[string]Word
|
||||
storage map[string]Word256
|
||||
logs []*Log
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) GetAccount(addr Word) (*Account, error) {
|
||||
func (fas *FakeAppState) GetAccount(addr Word256) *Account {
|
||||
account := fas.accounts[addr.String()]
|
||||
if account != nil {
|
||||
return account, nil
|
||||
return account
|
||||
} 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()]
|
||||
if !ok {
|
||||
return Errorf("Invalid account addr: %v", account.Address.String())
|
||||
panic(Fmt("Invalid account addr: %X", account.Address))
|
||||
} else {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) DeleteAccount(account *Account) error {
|
||||
func (fas *FakeAppState) RemoveAccount(account *Account) {
|
||||
_, ok := fas.accounts[account.Address.String()]
|
||||
if !ok {
|
||||
return Errorf("Invalid account addr: %v", account.Address.String())
|
||||
panic(Fmt("Invalid account addr: %X", account.Address))
|
||||
} else {
|
||||
// Delete account
|
||||
// Remove account
|
||||
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)
|
||||
account := fas.accounts[addr.String()]
|
||||
if account == nil {
|
||||
@ -53,75 +49,46 @@ func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) {
|
||||
Balance: 0,
|
||||
Code: nil,
|
||||
Nonce: 0,
|
||||
StorageRoot: Zero,
|
||||
}, nil
|
||||
StorageRoot: Zero256,
|
||||
}
|
||||
} 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()]
|
||||
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()]
|
||||
if ok {
|
||||
return value, nil
|
||||
return value
|
||||
} 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()]
|
||||
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
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) AddLog(log *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.
|
||||
func createAddress(creator *Account) Word {
|
||||
func createAddress(creator *Account) Word256 {
|
||||
nonce := creator.Nonce
|
||||
creator.Nonce += 1
|
||||
temp := make([]byte, 32+8)
|
||||
copy(temp, creator.Address[:])
|
||||
PutUint64(temp[32:], nonce)
|
||||
return RightPadWord(sha3.Sha3(temp)[:20])
|
||||
return RightPadWord256(sha3.Sha3(temp)[:20])
|
||||
}
|
||||
|
99
vm/test/vm_test.go
Normal file
99
vm/test/vm_test.go
Normal 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}
|
||||
*/
|
47
vm/types.go
47
vm/types.go
@ -1,44 +1,25 @@
|
||||
package vm
|
||||
|
||||
import ()
|
||||
import (
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
const (
|
||||
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 {
|
||||
Address Word
|
||||
Address Word256
|
||||
Balance uint64
|
||||
Code []byte
|
||||
Nonce uint64
|
||||
StorageRoot Word
|
||||
StorageRoot Word256
|
||||
Other interface{} // For holding all other data.
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Address Word
|
||||
Topics []Word
|
||||
Address Word256
|
||||
Topics []Word256
|
||||
Data []byte
|
||||
Height uint64
|
||||
}
|
||||
@ -46,14 +27,14 @@ type Log struct {
|
||||
type AppState interface {
|
||||
|
||||
// Accounts
|
||||
GetAccount(addr Word) (*Account, error)
|
||||
UpdateAccount(*Account) error
|
||||
DeleteAccount(*Account) error
|
||||
CreateAccount(*Account) (*Account, error)
|
||||
GetAccount(addr Word256) *Account
|
||||
UpdateAccount(*Account)
|
||||
RemoveAccount(*Account)
|
||||
CreateAccount(*Account) *Account
|
||||
|
||||
// Storage
|
||||
GetStorage(Word, Word) (Word, error)
|
||||
SetStorage(Word, Word, Word) (bool, error) // Setting to Zero is deleting.
|
||||
GetStorage(Word256, Word256) Word256
|
||||
SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting.
|
||||
|
||||
// Logs
|
||||
AddLog(*Log)
|
||||
@ -61,7 +42,7 @@ type AppState interface {
|
||||
|
||||
type Params struct {
|
||||
BlockHeight uint64
|
||||
BlockHash Word
|
||||
BlockHash Word256
|
||||
BlockTime int64
|
||||
GasLimit uint64
|
||||
}
|
||||
|
367
vm/vm.go
367
vm/vm.go
@ -3,13 +3,14 @@ package vm
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/vm/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownAddress = errors.New("Unknown address")
|
||||
ErrInsufficientBalance = errors.New("Insufficient balance")
|
||||
ErrInvalidJumpDest = errors.New("Invalid jump dest")
|
||||
ErrInsufficientGas = errors.New("Insuffient gas")
|
||||
@ -23,21 +24,30 @@ var (
|
||||
ErrInvalidContract = errors.New("Invalid contract")
|
||||
)
|
||||
|
||||
type Debug bool
|
||||
|
||||
const (
|
||||
dataStackCapacity = 1024
|
||||
callStackCapacity = 100 // TODO ensure usage.
|
||||
memoryCapacity = 1024 * 1024 // 1 MB
|
||||
dataStackCapacity = 1024
|
||||
callStackCapacity = 100 // TODO ensure usage.
|
||||
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 {
|
||||
appState AppState
|
||||
params Params
|
||||
origin Word
|
||||
origin Word256
|
||||
|
||||
callDepth int
|
||||
}
|
||||
|
||||
func NewVM(appState AppState, params Params, origin Word) *VM {
|
||||
func NewVM(appState AppState, params Params, origin Word256) *VM {
|
||||
return &VM{
|
||||
appState: appState,
|
||||
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.
|
||||
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 (
|
||||
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)
|
||||
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 {
|
||||
|
||||
@ -97,164 +107,197 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
return nil, nil
|
||||
|
||||
case ADD: // 0x01
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
stack.Push64(x + y)
|
||||
fmt.Printf(" %v + %v = %v\n", x, y, x+y)
|
||||
//x, y := stack.Pop64(), stack.Pop64()
|
||||
//stack.Push64(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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
stack.Push64(x * y)
|
||||
fmt.Printf(" %v * %v = %v\n", x, y, x*y)
|
||||
//x, y := stack.Pop64(), stack.Pop64()
|
||||
//stack.Push64(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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
stack.Push64(x - y)
|
||||
fmt.Printf(" %v - %v = %v\n", x, y, x-y)
|
||||
//x, y := stack.Pop64(), stack.Pop64()
|
||||
//stack.Push64(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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
if y == 0 { // TODO
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
|
||||
//x, y := stack.Pop64(), stack.Pop64()
|
||||
//stack.Push64(x / y)
|
||||
x, y := stack.Pop(), stack.Pop()
|
||||
if y.IsZero() { // TODO
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" %x / %x = %v (TODO)\n", x, y, 0)
|
||||
} else {
|
||||
stack.Push64(x / y)
|
||||
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
|
||||
xb := new(big.Int).SetBytes(flip(x[:]))
|
||||
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
|
||||
// TODO ... big?
|
||||
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
||||
if y == 0 { // TODO
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
|
||||
} else {
|
||||
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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
if y == 0 { // TODO
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
|
||||
//x, y := stack.Pop64(), stack.Pop64()
|
||||
x, y := stack.Pop(), stack.Pop()
|
||||
if y.IsZero() { // TODO
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
|
||||
} else {
|
||||
stack.Push64(x % y)
|
||||
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
|
||||
xb := new(big.Int).SetBytes(flip(x[:]))
|
||||
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
|
||||
// TODO ... big?
|
||||
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
||||
if y == 0 { // TODO
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
|
||||
} else {
|
||||
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
|
||||
// TODO ... big?
|
||||
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
|
||||
if z == 0 { // TODO
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
|
||||
} else {
|
||||
stack.Push64(x % y)
|
||||
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
|
||||
stack.Push64((x + y) % z)
|
||||
dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
|
||||
}
|
||||
|
||||
case MULMOD: // 0x09
|
||||
// TODO ... big?
|
||||
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
|
||||
if z == 0 { // TODO
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
|
||||
} else {
|
||||
stack.Push64(x % y)
|
||||
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
|
||||
stack.Push64((x * y) % z)
|
||||
dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
|
||||
}
|
||||
|
||||
case EXP: // 0x0A
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
stack.Push64(ExpUint64(x, y))
|
||||
fmt.Printf(" %v ** %v = %v\n", x, y, uint64(math.Pow(float64(x), float64(y))))
|
||||
//x, y := stack.Pop64(), stack.Pop64()
|
||||
//stack.Push64(ExpUint64(x, 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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
res := (y << uint(x)) >> x
|
||||
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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
if x < y {
|
||||
stack.Push64(1)
|
||||
} 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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
if x > y {
|
||||
stack.Push64(1)
|
||||
} 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
|
||||
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
||||
if x < y {
|
||||
stack.Push64(1)
|
||||
} 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
|
||||
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
||||
if x > y {
|
||||
stack.Push64(1)
|
||||
} 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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
if x > y {
|
||||
if x == y {
|
||||
stack.Push64(1)
|
||||
} 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
|
||||
x := stack.Pop64()
|
||||
if x == 0 {
|
||||
stack.Push64(1)
|
||||
} 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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
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
|
||||
x, y := stack.Pop64(), stack.Pop64()
|
||||
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
|
||||
x := stack.Pop64()
|
||||
stack.Push64(^x)
|
||||
fmt.Printf(" !%v = %v\n", x, ^x)
|
||||
dbg.Printf(" !%v = %v\n", x, ^x)
|
||||
|
||||
case BYTE: // 0x1A
|
||||
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]
|
||||
}
|
||||
stack.Push64(uint64(res))
|
||||
fmt.Printf(" => 0x%X\n", res)
|
||||
dbg.Printf(" => 0x%X\n", res)
|
||||
|
||||
case SHA3: // 0x20
|
||||
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)
|
||||
stack.PushBytes(data)
|
||||
fmt.Printf(" => (%v) %X\n", size, data)
|
||||
dbg.Printf(" => (%v) %X\n", size, data)
|
||||
|
||||
case ADDRESS: // 0x30
|
||||
stack.Push(callee.Address)
|
||||
fmt.Printf(" => %X\n", callee.Address)
|
||||
dbg.Printf(" => %X\n", callee.Address)
|
||||
|
||||
case BALANCE: // 0x31
|
||||
addr := stack.Pop()
|
||||
if ok = useGas(gas, GasGetAccount); !ok {
|
||||
return nil, firstErr(err, ErrInsufficientGas)
|
||||
}
|
||||
account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
|
||||
if err_ != nil {
|
||||
return nil, firstErr(err, err_)
|
||||
acc := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
|
||||
if acc == nil {
|
||||
return nil, firstErr(err, ErrUnknownAddress)
|
||||
}
|
||||
balance := account.Balance
|
||||
balance := acc.Balance
|
||||
stack.Push64(balance)
|
||||
fmt.Printf(" => %v (%X)\n", balance, addr)
|
||||
dbg.Printf(" => %v (%X)\n", balance, addr)
|
||||
|
||||
case ORIGIN: // 0x32
|
||||
stack.Push(vm.origin)
|
||||
fmt.Printf(" => %X\n", vm.origin)
|
||||
dbg.Printf(" => %X\n", vm.origin)
|
||||
|
||||
case CALLER: // 0x33
|
||||
stack.Push(caller.Address)
|
||||
fmt.Printf(" => %X\n", caller.Address)
|
||||
dbg.Printf(" => %X\n", caller.Address)
|
||||
|
||||
case CALLVALUE: // 0x34
|
||||
stack.Push64(value)
|
||||
fmt.Printf(" => %v\n", value)
|
||||
dbg.Printf(" => %v\n", value)
|
||||
|
||||
case CALLDATALOAD: // 0x35
|
||||
offset := stack.Pop64()
|
||||
@ -313,12 +356,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
if !ok {
|
||||
return nil, firstErr(err, ErrInputOutOfBounds)
|
||||
}
|
||||
stack.Push(RightPadWord(data))
|
||||
fmt.Printf(" => 0x%X\n", data)
|
||||
stack.Push(RightPadWord256(data))
|
||||
dbg.Printf(" => 0x%X\n", data)
|
||||
|
||||
case CALLDATASIZE: // 0x36
|
||||
stack.Push64(uint64(len(input)))
|
||||
fmt.Printf(" => %d\n", len(input))
|
||||
dbg.Printf(" => %d\n", len(input))
|
||||
|
||||
case CALLDATACOPY: // 0x37
|
||||
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)
|
||||
}
|
||||
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
|
||||
l := uint64(len(code))
|
||||
stack.Push64(l)
|
||||
fmt.Printf(" => %d\n", l)
|
||||
dbg.Printf(" => %d\n", l)
|
||||
|
||||
case CODECOPY: // 0x39
|
||||
memOff := stack.Pop64()
|
||||
codeOff := stack.Pop64()
|
||||
length := stack.Pop64()
|
||||
fmt.Println("CODECOPY: codeOff, length, codelength", codeOff, length, len(code))
|
||||
data, ok := subslice(code, codeOff, length, false)
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
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
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
|
||||
|
||||
case EXTCODESIZE: // 0x3B
|
||||
addr := stack.Pop()
|
||||
if ok = useGas(gas, GasGetAccount); !ok {
|
||||
return nil, firstErr(err, ErrInsufficientGas)
|
||||
}
|
||||
account, err_ := vm.appState.GetAccount(addr)
|
||||
if err_ != nil {
|
||||
return nil, firstErr(err, err_)
|
||||
acc := vm.appState.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, firstErr(err, ErrUnknownAddress)
|
||||
}
|
||||
code := account.Code
|
||||
code := acc.Code
|
||||
l := uint64(len(code))
|
||||
stack.Push64(l)
|
||||
fmt.Printf(" => %d\n", l)
|
||||
dbg.Printf(" => %d\n", l)
|
||||
|
||||
case EXTCODECOPY: // 0x3C
|
||||
addr := stack.Pop()
|
||||
if ok = useGas(gas, GasGetAccount); !ok {
|
||||
return nil, firstErr(err, ErrInsufficientGas)
|
||||
}
|
||||
account, err_ := vm.appState.GetAccount(addr)
|
||||
if err_ != nil {
|
||||
return nil, firstErr(err, err_)
|
||||
acc := vm.appState.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, firstErr(err, ErrUnknownAddress)
|
||||
}
|
||||
code := account.Code
|
||||
code := acc.Code
|
||||
memOff := stack.Pop64()
|
||||
codeOff := 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)
|
||||
}
|
||||
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
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
|
||||
|
||||
case COINBASE: // 0x41
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
|
||||
stack.Push(Zero256)
|
||||
dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
|
||||
|
||||
case TIMESTAMP: // 0x42
|
||||
time := vm.params.BlockTime
|
||||
stack.Push64(uint64(time))
|
||||
fmt.Printf(" => 0x%X\n", time)
|
||||
dbg.Printf(" => 0x%X\n", time)
|
||||
|
||||
case BLOCKHEIGHT: // 0x43
|
||||
number := uint64(vm.params.BlockHeight)
|
||||
stack.Push64(number)
|
||||
fmt.Printf(" => 0x%X\n", number)
|
||||
dbg.Printf(" => 0x%X\n", number)
|
||||
|
||||
case GASLIMIT: // 0x45
|
||||
stack.Push64(vm.params.GasLimit)
|
||||
fmt.Printf(" => %v\n", vm.params.GasLimit)
|
||||
dbg.Printf(" => %v\n", vm.params.GasLimit)
|
||||
|
||||
case POP: // 0x50
|
||||
stack.Pop()
|
||||
fmt.Printf(" => %v\n", vm.params.GasLimit)
|
||||
dbg.Printf(" => %v\n", vm.params.GasLimit)
|
||||
|
||||
case MLOAD: // 0x51
|
||||
offset := stack.Pop64()
|
||||
@ -430,17 +472,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
if !ok {
|
||||
return nil, firstErr(err, ErrMemoryOutOfBounds)
|
||||
}
|
||||
stack.Push(RightPadWord(data))
|
||||
fmt.Printf(" => 0x%X\n", data)
|
||||
stack.Push(RightPadWord256(data))
|
||||
dbg.Printf(" => 0x%X\n", data)
|
||||
|
||||
case MSTORE: // 0x52
|
||||
offset, data := stack.Pop64(), stack.Pop()
|
||||
dest, ok := subslice(memory, offset, 32, true)
|
||||
dest, ok := subslice(memory, offset, 32, false)
|
||||
if !ok {
|
||||
return nil, firstErr(err, ErrMemoryOutOfBounds)
|
||||
}
|
||||
copy(dest, data[:])
|
||||
fmt.Printf(" => 0x%X\n", data)
|
||||
copy(dest, flip(data[:]))
|
||||
dbg.Printf(" => 0x%X\n", data)
|
||||
|
||||
case MSTORE8: // 0x53
|
||||
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)
|
||||
}
|
||||
memory[offset] = val
|
||||
fmt.Printf(" => [%v] 0x%X\n", offset, val)
|
||||
dbg.Printf(" => [%v] 0x%X\n", offset, val)
|
||||
|
||||
case SLOAD: // 0x54
|
||||
loc := stack.Pop()
|
||||
data, _ := vm.appState.GetStorage(callee.Address, loc)
|
||||
data := vm.appState.GetStorage(callee.Address, loc)
|
||||
stack.Push(data)
|
||||
fmt.Printf(" {0x%X : 0x%X}\n", loc, data)
|
||||
dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
|
||||
|
||||
case SSTORE: // 0x55
|
||||
loc, data := stack.Pop(), stack.Pop()
|
||||
updated, err_ := vm.appState.SetStorage(callee.Address, loc, data)
|
||||
if err = firstErr(err, err_); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if updated {
|
||||
useGas(gas, GasStorageUpdate)
|
||||
} else {
|
||||
useGas(gas, GasStorageCreate)
|
||||
}
|
||||
fmt.Printf(" {0x%X : 0x%X}\n", loc, data)
|
||||
vm.appState.SetStorage(callee.Address, loc, data)
|
||||
useGas(gas, GasStorageUpdate)
|
||||
dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
|
||||
|
||||
case JUMP: // 0x56
|
||||
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)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" ~> false\n")
|
||||
dbg.Printf(" ~> false\n")
|
||||
|
||||
case PC: // 0x58
|
||||
stack.Push64(pc)
|
||||
@ -489,10 +524,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
|
||||
case GAS: // 0x5A
|
||||
stack.Push64(*gas)
|
||||
fmt.Printf(" => %X\n", *gas)
|
||||
dbg.Printf(" => %X\n", *gas)
|
||||
|
||||
case JUMPDEST: // 0x5B
|
||||
fmt.Printf("\n")
|
||||
dbg.Printf("\n")
|
||||
// Do nothing
|
||||
|
||||
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
|
||||
@ -501,24 +536,24 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
if !ok {
|
||||
return nil, firstErr(err, ErrCodeOutOfBounds)
|
||||
}
|
||||
res := RightPadWord(codeSegment)
|
||||
res := RightPadWord256(codeSegment)
|
||||
stack.Push(res)
|
||||
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:
|
||||
n := int(op - DUP1 + 1)
|
||||
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:
|
||||
n := int(op - SWAP1 + 2)
|
||||
stack.Swap(n)
|
||||
fmt.Printf(" => [%d]\n", n)
|
||||
dbg.Printf(" => [%d]\n", n)
|
||||
|
||||
case LOG0, LOG1, LOG2, LOG3, LOG4:
|
||||
n := int(op - LOG0)
|
||||
topics := make([]Word, n)
|
||||
topics := make([]Word256, n)
|
||||
offset, size := stack.Pop64(), stack.Pop64()
|
||||
for i := 0; i < n; i++ {
|
||||
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.appState.AddLog(log)
|
||||
fmt.Printf(" => %v\n", log)
|
||||
dbg.Printf(" => %v\n", log)
|
||||
|
||||
case CREATE: // 0xF0
|
||||
contractValue := stack.Pop64()
|
||||
@ -551,19 +586,14 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
|
||||
// TODO charge for gas to create account _ the code length * GasCreateByte
|
||||
|
||||
newAccount, err := vm.appState.CreateAccount(callee)
|
||||
if err != nil {
|
||||
stack.Push(Zero)
|
||||
fmt.Printf(" (*) 0x0 %v\n", err)
|
||||
newAccount := vm.appState.CreateAccount(callee)
|
||||
// Run the input to get the contract code.
|
||||
ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas)
|
||||
if err_ != nil {
|
||||
stack.Push(Zero256)
|
||||
} else {
|
||||
// Run the input to get the contract code.
|
||||
ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas)
|
||||
if err_ != nil {
|
||||
stack.Push(Zero)
|
||||
} else {
|
||||
newAccount.Code = ret // Set the code
|
||||
stack.Push(newAccount.Address)
|
||||
}
|
||||
newAccount.Code = ret // Set the code
|
||||
stack.Push(newAccount.Address)
|
||||
}
|
||||
|
||||
case CALL, CALLCODE: // 0xF1, 0xF2
|
||||
@ -571,7 +601,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||
addr, value := stack.Pop(), stack.Pop64()
|
||||
inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs
|
||||
retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs
|
||||
fmt.Printf(" => %X\n", addr)
|
||||
dbg.Printf(" => %X\n", addr)
|
||||
|
||||
// Get the arguments from the memory
|
||||
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 {
|
||||
return nil, firstErr(err, ErrInsufficientGas)
|
||||
}
|
||||
account, err_ := vm.appState.GetAccount(addr)
|
||||
if err = firstErr(err, err_); err != nil {
|
||||
return nil, err
|
||||
acc := vm.appState.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, firstErr(err, ErrUnknownAddress)
|
||||
}
|
||||
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 {
|
||||
ret, err = vm.Call(callee, account, account.Code, args, value, gas)
|
||||
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
|
||||
}
|
||||
}
|
||||
|
||||
// Push result
|
||||
if err != nil {
|
||||
stack.Push(Zero)
|
||||
stack.Push(Zero256)
|
||||
} else {
|
||||
stack.Push(One)
|
||||
stack.Push(One256)
|
||||
dest, ok := subslice(memory, retOffset, retSize, false)
|
||||
if !ok {
|
||||
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.
|
||||
*gas += gasLimit
|
||||
|
||||
fmt.Printf("resume %X (%v)\n", callee.Address, gas)
|
||||
dbg.Printf("resume %X (%v)\n", callee.Address, gas)
|
||||
|
||||
case RETURN: // 0xF3
|
||||
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 {
|
||||
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
|
||||
|
||||
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 {
|
||||
return nil, firstErr(err, ErrInsufficientGas)
|
||||
}
|
||||
// TODO if the receiver is Zero, then make it the fee.
|
||||
receiver, err_ := vm.appState.GetAccount(addr)
|
||||
if err = firstErr(err, err_); err != nil {
|
||||
return nil, err
|
||||
// TODO if the receiver is , then make it the fee.
|
||||
receiver := vm.appState.GetAccount(addr)
|
||||
if receiver == nil {
|
||||
return nil, firstErr(err, ErrUnknownAddress)
|
||||
}
|
||||
balance := callee.Balance
|
||||
receiver.Balance += balance
|
||||
vm.appState.UpdateAccount(receiver)
|
||||
vm.appState.DeleteAccount(callee)
|
||||
fmt.Printf(" => (%X) %v\n", addr[:4], balance)
|
||||
vm.appState.RemoveAccount(callee)
|
||||
dbg.Printf(" => (%X) %v\n", addr[:4], balance)
|
||||
fallthrough
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@ -688,10 +718,10 @@ func codeGetOp(code []byte, n uint64) OpCode {
|
||||
func jump(code []byte, to uint64, pc *uint64) (err error) {
|
||||
dest := codeGetOp(code, to)
|
||||
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
|
||||
}
|
||||
fmt.Printf(" ~> %v\n", to)
|
||||
dbg.Printf(" ~> %v\n", to)
|
||||
*pc = to
|
||||
return nil
|
||||
}
|
||||
@ -724,10 +754,25 @@ func transfer(from, to *Account, amount uint64) error {
|
||||
}
|
||||
|
||||
func flip(in []byte) []byte {
|
||||
l2 := len(in) / 2
|
||||
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[len(in)-1-i] = in[i]
|
||||
}
|
||||
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user