diff --git a/.gitignore b/.gitignore index c4760c4f6..eba2cac3f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .bak tendermint .DS_Store +rpc/test/.tendermint diff --git a/account/account.go b/account/account.go index 5c8bfda30..520afd70c 100644 --- a/account/account.go +++ b/account/account.go @@ -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) { diff --git a/account/priv_account.go b/account/priv_account.go index 48e1b75c7..23114893a 100644 --- a/account/priv_account.go +++ b/account/priv_account.go @@ -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)) } diff --git a/binary/reflect_test.go b/binary/reflect_test.go index d8055f53a..ac5423685 100644 --- a/binary/reflect_test.go +++ b/binary/reflect_test.go @@ -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}) } diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 6d65708f2..7e776ab07 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -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)) diff --git a/common/int.go b/common/int.go index 6ca602193..f1c376d75 100644 --- a/common/int.go +++ b/common/int.go @@ -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) +} diff --git a/common/word.go b/common/word.go new file mode 100644 index 000000000..d67730e13 --- /dev/null +++ b/common/word.go @@ -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) } diff --git a/consensus/state.go b/consensus/state.go index 683612496..8756df612 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -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 { diff --git a/daemon/daemon.go b/daemon/daemon.go index dc43e2fa1..03a4d26be 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -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 } diff --git a/mempool/mempool.go b/mempool/mempool.go index e955dfacf..819198778 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -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) diff --git a/rpc/accounts.go b/rpc/accounts.go deleted file mode 100644 index aba499081..000000000 --- a/rpc/accounts.go +++ /dev/null @@ -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}) -} diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go new file mode 100644 index 000000000..6949ab861 --- /dev/null +++ b/rpc/core/accounts.go @@ -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 +} diff --git a/rpc/blocks.go b/rpc/core/blocks.go similarity index 52% rename from rpc/blocks.go rename to rpc/core/blocks.go index 1415f1ff0..4bbe716c0 100644 --- a/rpc/blocks.go +++ b/rpc/core/blocks.go @@ -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 } diff --git a/rpc/core/log.go b/rpc/core/log.go new file mode 100644 index 000000000..d359bee26 --- /dev/null +++ b/rpc/core/log.go @@ -0,0 +1,7 @@ +package core + +import ( + "github.com/tendermint/log15" +) + +var log = log15.New("module", "rpc") diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go new file mode 100644 index 000000000..070d6e40d --- /dev/null +++ b/rpc/core/mempool.go @@ -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=... +*/ diff --git a/rpc/core/net.go b/rpc/core/net.go new file mode 100644 index 000000000..64db01cfa --- /dev/null +++ b/rpc/core/net.go @@ -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 +} diff --git a/rpc/rpc.go b/rpc/core/pipe.go similarity index 67% rename from rpc/rpc.go rename to rpc/core/pipe.go index 8cf905d3a..ca2d9db20 100644 --- a/rpc/rpc.go +++ b/rpc/core/pipe.go @@ -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 } diff --git a/rpc/core/responses.go b/rpc/core/responses.go new file mode 100644 index 000000000..3e6e32e6a --- /dev/null +++ b/rpc/core/responses.go @@ -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 +} diff --git a/rpc/txs.go b/rpc/core/txs.go similarity index 52% rename from rpc/txs.go rename to rpc/core/txs.go index 5c7588f77..95b9fb552 100644 --- a/rpc/txs.go +++ b/rpc/core/txs.go @@ -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 } diff --git a/rpc/validators.go b/rpc/core/validators.go similarity index 64% rename from rpc/validators.go rename to rpc/core/validators.go index 9e2775158..5d156156d 100644 --- a/rpc/validators.go +++ b/rpc/core/validators.go @@ -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 } diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index 14a5a6813..225561360 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -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 } diff --git a/rpc/http_params.go b/rpc/http_params.go index d1d3c0f60..1837fd762 100644 --- a/rpc/http_params.go +++ b/rpc/http_params.go @@ -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 { diff --git a/rpc/http_server.go b/rpc/http_server.go index 17d157fb9..5428d8bb0 100644 --- a/rpc/http_server.go +++ b/rpc/http_server.go @@ -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) diff --git a/rpc/mempool.go b/rpc/mempool.go deleted file mode 100644 index 81402ebb3..000000000 --- a/rpc/mempool.go +++ /dev/null @@ -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=... -*/ diff --git a/rpc/net.go b/rpc/net.go deleted file mode 100644 index ae187e1ab..000000000 --- a/rpc/net.go +++ /dev/null @@ -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}) -} diff --git a/rpc/test/.tendermint/genesis.json b/rpc/test/.tendermint/genesis.json new file mode 100644 index 000000000..c324aa760 --- /dev/null +++ b/rpc/test/.tendermint/genesis.json @@ -0,0 +1,24 @@ +{ + "Accounts": [ + { + "Address": "d7dff9806078899c8da3fe3633cc0bf3c6c2b1bb", + "Amount": 200000000 + }, + { + "Address": "AC89A6DDF4C309A89A2C4078CE409A5A7B282270", + "Amount": 200000000 + } + ], + "Validators": [ + { + "PubKey": [1, "2239c21c81ea7173a6c489145490c015e05d4b97448933b708a7ec5b7b4921e3"], + "Amount": 1000000, + "UnbondTo": [ + { + "Address": "d7dff9806078899c8da3fe3633cc0bf3c6c2b1bb", + "Amount": 100000 + } + ] + } + ] +} diff --git a/rpc/test/.tendermint/priv_validator.json b/rpc/test/.tendermint/priv_validator.json new file mode 100755 index 000000000..e3bcca94d --- /dev/null +++ b/rpc/test/.tendermint/priv_validator.json @@ -0,0 +1 @@ +{"Address":"D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB","PubKey":[1,"2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"],"PrivKey":[1,"FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"],"LastHeight":3,"LastRound":0,"LastStep":2} \ No newline at end of file diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go new file mode 100644 index 000000000..3021584c3 --- /dev/null +++ b/rpc/test/http_rpc_test.go @@ -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*/ diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go new file mode 100644 index 000000000..3b857f24e --- /dev/null +++ b/rpc/test/json_rpc_test.go @@ -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*/ diff --git a/rpc/test/test.go b/rpc/test/test.go new file mode 100644 index 000000000..45a5f412a --- /dev/null +++ b/rpc/test/test.go @@ -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) + } +} diff --git a/state/block_cache.go b/state/block_cache.go new file mode 100644 index 000000000..9af3b4edc --- /dev/null +++ b/state/block_cache.go @@ -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 +} diff --git a/state/common.go b/state/common.go new file mode 100644 index 000000000..342d35779 --- /dev/null +++ b/state/common.go @@ -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 +} diff --git a/state/execution.go b/state/execution.go new file mode 100644 index 000000000..d7b3b78f7 --- /dev/null +++ b/state/execution.go @@ -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") + } +} diff --git a/state/state.go b/state/state.go index a3f3586eb..700202b87 100644 --- a/state/state.go +++ b/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 - } - 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 +// 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 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{} +// Mutates the block in place and updates it with new state hash. +func (s *State) SetBlockStateHash(block *types.Block) error { + sCopy := s.Copy() + err := execBlock(sCopy, block, types.PartSetHeader{}) + if err != nil { + return err } + // Set block.StateHash + block.StateHash = sCopy.Hash() return nil } -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 -} +//------------------------------------- +// State.accounts -func (s *State) 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 +// 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 nil + return acc.(*account.Account).Copy() } -func (s *State) ValidateOutputs(outs []*types.TxOutput) (total uint64, err error) { - for _, out := range outs { - // Check TxOutput basic - if err := out.ValidateBasic(); err != nil { - return 0, err - } - // Good. Add amount to total - total += out.Amount - } - return total, nil +// 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) 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 - } +// Implements Statelike +func (s *State) RemoveAccount(address []byte) bool { + _, removed := s.accounts.Remove(address) + return removed } -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 - } +// The returned Account is a copy, so mutating it +// has no side effects. +func (s *State) GetAccounts() merkle.Tree { + return s.accounts.Copy() } -// If the tx is invalid, an error will be returned. -// Unlike AppendBlock(), state will not be altered. -func (s *State) ExecTx(tx_ types.Tx, runCall bool) error { - - // TODO: do something with fees - fees := uint64(0) - - // Exec tx - switch tx := tx_.(type) { - case *types.SendTx: - accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.Outputs) - if err != nil { - return err - } - signBytes := account.SignBytes(tx) - inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs) - if err != nil { - return err - } - outTotal, err := s.ValidateOutputs(tx.Outputs) - if err != nil { - return err - } - if outTotal > inTotal { - return types.ErrTxInsufficientFunds - } - fee := inTotal - outTotal - fees += fee - - // Good! Adjust accounts - s.AdjustByInputs(accounts, tx.Inputs) - s.AdjustByOutputs(accounts, tx.Outputs) - s.UpdateAccounts(accounts) - return nil - - case *types.CallTx: - var inAcc, outAcc *account.Account - - // Validate input - inAcc = s.GetAccount(tx.Input.Address) - if inAcc == nil { - log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) - return types.ErrTxInvalidAddress - } - // pubKey should be present in either "inAcc" or "tx.Input" - if err := checkInputPubKey(inAcc, tx.Input); err != nil { - log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) - return err - } - signBytes := account.SignBytes(tx) - err := s.ValidateInput(inAcc, signBytes, tx.Input) - if err != nil { - log.Debug(Fmt("ValidateInput failed on %X:", tx.Input.Address)) - return err - } - if tx.Input.Amount < tx.Fee { - log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) - return types.ErrTxInsufficientFunds - } - - createAccount := len(tx.Address) == 0 - if !createAccount { - // Validate output - if len(tx.Address) != 20 { - log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address)) - return types.ErrTxInvalidAddress - } - // this may be nil if we are still in mempool and contract was created in same block as this tx - // but that's fine, because the account will be created properly when the create tx runs in the block - // and then this won't return nil. otherwise, we take their fee - outAcc = s.GetAccount(tx.Address) - } - - log.Debug(Fmt("Out account: %v", outAcc)) - - // Good! - value := tx.Input.Amount - tx.Fee - inAcc.Sequence += 1 - - if runCall { - - var ( - gas uint64 = tx.GasLimit - err error = nil - caller *vm.Account = toVMAccount(inAcc) - callee *vm.Account = nil - code []byte = nil - appState = NewVMAppState(s) // TODO: confusing. - params = vm.Params{ - BlockHeight: uint64(s.LastBlockHeight), - BlockHash: vm.BytesToWord(s.LastBlockHash), - BlockTime: s.LastBlockTime.Unix(), - GasLimit: 10000000, - } - ) - - // Maybe create a new callee account if - // this transaction is creating a new contract. - if !createAccount { - if outAcc == nil { - // take fees (sorry pal) - inAcc.Balance -= tx.Fee - s.UpdateAccount(inAcc) - log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) - return types.ErrTxInvalidAddress - - } - callee = toVMAccount(outAcc) - code = callee.Code - log.Debug(Fmt("Calling contract %X with code %X", callee.Address.Address(), callee.Code)) - } else { - callee, err = appState.CreateAccount(caller) - if err != nil { - log.Debug(Fmt("Error creating account")) - return err - } - log.Debug(Fmt("Created new account %X", callee.Address.Address())) - code = tx.Data - } - log.Debug(Fmt("Code for this contract: %X", code)) - - appState.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe. - appState.UpdateAccount(callee) // because we adjusted by input above. - vmach := vm.NewVM(appState, params, caller.Address) - // NOTE: Call() transfers the value from caller to callee iff call succeeds. - ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) - if err != nil { - // Failure. Charge the gas fee. The 'value' was otherwise not transferred. - log.Debug(Fmt("Error on execution: %v", err)) - inAcc.Balance -= tx.Fee - s.UpdateAccount(inAcc) - // Throw away 'appState' which holds incomplete updates (don't sync it). - } else { - log.Debug("Successful execution") - // Success - if createAccount { - callee.Code = ret - } - - appState.Sync() - } - // Create a receipt from the ret and whether errored. - log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err) - } else { - // The mempool does not call txs until - // the proposer determines the order of txs. - // So mempool will skip the actual .Call(), - // and only deduct from the caller's balance. - inAcc.Balance -= value - if createAccount { - inAcc.Sequence += 1 - } - s.UpdateAccount(inAcc) - } - - return nil - - case *types.BondTx: - valInfo := s.GetValidatorInfo(tx.PubKey.Address()) - if valInfo != nil { - // TODO: In the future, check that the validator wasn't destroyed, - // add funds, merge UnbondTo outputs, and unbond validator. - return errors.New("Adding coins to existing validators not yet supported") - } - accounts, err := s.GetOrMakeAccounts(tx.Inputs, nil) - if err != nil { - return err - } - signBytes := account.SignBytes(tx) - inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs) - if err != nil { - return err - } - if err := tx.PubKey.ValidateBasic(); err != nil { - return err - } - outTotal, err := s.ValidateOutputs(tx.UnbondTo) - if err != nil { - return err - } - if outTotal > inTotal { - return types.ErrTxInsufficientFunds - } - fee := inTotal - outTotal - fees += fee - - // Good! Adjust accounts - s.AdjustByInputs(accounts, tx.Inputs) - s.UpdateAccounts(accounts) - // Add ValidatorInfo - s.SetValidatorInfo(&ValidatorInfo{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, - UnbondTo: tx.UnbondTo, - FirstBondHeight: s.LastBlockHeight + 1, - FirstBondAmount: outTotal, - }) - // Add Validator - added := s.BondedValidators.Add(&Validator{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, - BondHeight: s.LastBlockHeight + 1, - VotingPower: outTotal, - Accum: 0, - }) - if !added { - panic("Failed to add validator") - } - return nil - - case *types.UnbondTx: - // The validator must be active - _, val := s.BondedValidators.GetByAddress(tx.Address) - if val == nil { - return types.ErrTxInvalidAddress - } - - // Verify the signature - signBytes := account.SignBytes(tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { - return types.ErrTxInvalidSignature - } - - // tx.Height must be greater than val.LastCommitHeight - if tx.Height <= val.LastCommitHeight { - return errors.New("Invalid unbond height") - } - - // Good! - s.unbondValidator(val) - return nil - - case *types.RebondTx: - // The validator must be inactive - _, val := s.UnbondingValidators.GetByAddress(tx.Address) - if val == nil { - return types.ErrTxInvalidAddress - } - - // Verify the signature - signBytes := account.SignBytes(tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { - return types.ErrTxInvalidSignature - } - - // tx.Height must be equal to the next height - if tx.Height != s.LastBlockHeight+1 { - return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", s.LastBlockHeight+1, tx.Height)) - } - - // Good! - s.rebondValidator(val) - return nil - - case *types.DupeoutTx: - // Verify the signatures - _, accused := s.BondedValidators.GetByAddress(tx.Address) - if accused == nil { - _, accused = s.UnbondingValidators.GetByAddress(tx.Address) - if accused == nil { - return types.ErrTxInvalidAddress - } - } - voteASignBytes := account.SignBytes(&tx.VoteA) - voteBSignBytes := account.SignBytes(&tx.VoteB) - if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || - !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { - return types.ErrTxInvalidSignature - } - - // Verify equivocation - // TODO: in the future, just require one vote from a previous height that - // doesn't exist on this chain. - if tx.VoteA.Height != tx.VoteB.Height { - return errors.New("DupeoutTx heights don't match") - } - if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round { - // Check special case (not an error, validator must be slashed!) - // Validators should not sign another vote after committing. - } else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round { - // We need to check both orderings of the votes - } else { - if tx.VoteA.Round != tx.VoteB.Round { - return errors.New("DupeoutTx rounds don't match") - } - if tx.VoteA.Type != tx.VoteB.Type { - return errors.New("DupeoutTx types don't match") - } - if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { - return errors.New("DupeoutTx blockhashes shouldn't match") - } - } +// State.accounts +//------------------------------------- +// State.validators - // Good! (Bad validator!) - s.destroyValidator(accused) +// 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 - - 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) 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 +func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { + storage = merkle.NewIAVLTree(binary.BasicCodec, binary.BasicCodec, 1024, s.DB) + storage.Load(hash) + return storage } -// 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 -} +// State.storage +//------------------------------------- -// 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()) +type InvalidTxError struct { + Tx types.Tx + Reason error } -// 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) } diff --git a/state/state_test.go b/state/state_test.go index 0d2c963bb..5f2990652 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -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) } diff --git a/state/tx_cache.go b/state/tx_cache.go new file mode 100644 index 000000000..d4188db3c --- /dev/null +++ b/state/tx_cache.go @@ -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 +} diff --git a/state/vm_app_state.go b/state/vm_app_state.go deleted file mode 100644 index 1005fc3dd..000000000 --- a/state/vm_app_state.go +++ /dev/null @@ -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] -} diff --git a/types/block.go b/types/block.go index 176ce4385..c00e90ccb 100644 --- a/types/block.go +++ b/types/block.go @@ -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) diff --git a/types/tx.go b/types/tx.go index 1df6f6be1..31af05b7a 100644 --- a/types/tx.go +++ b/types/tx.go @@ -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) +} diff --git a/vm/common.go b/vm/common.go deleted file mode 100644 index cf72ed6f9..000000000 --- a/vm/common.go +++ /dev/null @@ -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) -} diff --git a/vm/gas.go b/vm/gas.go index 40a7b9a06..ebe5573a3 100644 --- a/vm/gas.go +++ b/vm/gas.go @@ -3,7 +3,6 @@ package vm const ( GasSha3 uint64 = 1 GasGetAccount uint64 = 1 - GasStorageCreate uint64 = 1 GasStorageUpdate uint64 = 1 GasStackOp uint64 = 1 diff --git a/vm/native.go b/vm/native.go index 467e0a022..ad9f3f3ae 100644 --- a/vm/native.go +++ b/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 } //----------------------------------------------------------------------------- diff --git a/vm/stack.go b/vm/stack.go index 4ac05ba9a..6b74643ac 100644 --- a/vm/stack.go +++ b/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] } diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index f31ed80e9..3d6f5ca22 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -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]) } diff --git a/vm/test/vm_test.go b/vm/test/vm_test.go new file mode 100644 index 000000000..ee3fe7e57 --- /dev/null +++ b/vm/test/vm_test.go @@ -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} +*/ diff --git a/vm/types.go b/vm/types.go index 968538ef1..443b72c63 100644 --- a/vm/types.go +++ b/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 } diff --git a/vm/vm.go b/vm/vm.go index aa4c3d62e..bc93ed3ea 100644 --- a/vm/vm.go +++ b/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 +}