From fd0646fc4f6f33082b4523cbed140c080095882a Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 26 Mar 2015 10:58:20 -0700 Subject: [PATCH 01/14] Compare blockhashes in stageBlock() --- consensus/state.go | 3 ++- state/state.go | 1 + types/block.go | 23 ++++++++++++++++++----- vm/test/fake_app_state.go | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 683612496..84488731f 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 } diff --git a/state/state.go b/state/state.go index a3f3586eb..0fcd64f7b 100644 --- a/state/state.go +++ b/state/state.go @@ -633,6 +633,7 @@ func (s *State) AppendBlock(block *types.Block, blockPartsHeader types.PartSetHe return nil } +// Mutates the block in place and updates it with new state hash. func (s *State) SetBlockStateHash(block *types.Block) error { sCopy := s.Copy() err := sCopy.appendBlock(block, types.PartSetHeader{}) 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/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index f31ed80e9..be11467ea 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -1,4 +1,4 @@ -package main +package vm import ( "fmt" From 79304b0dd37a84627bb73a9884398b240615ae0d Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Mar 2015 23:44:07 -0700 Subject: [PATCH 02/14] Create BlockCache and TxCache for performance. --- account/account.go | 10 +- binary/reflect_test.go | 36 +- blockchain/reactor.go | 2 +- common/int.go | 11 + common/word.go | 78 +++++ consensus/state.go | 2 +- mempool/mempool.go | 7 +- state/block_cache.go | 196 +++++++++++ state/common.go | 18 + state/execution.go | 593 +++++++++++++++++++++++++++++++ state/state.go | 716 +++++--------------------------------- state/state_test.go | 23 +- state/tx_cache.go | 193 ++++++++++ state/vm_app_state.go | 265 -------------- vm/common.go | 35 -- vm/gas.go | 1 - vm/native.go | 13 +- vm/stack.go | 19 +- vm/test/fake_app_state.go | 56 ++- vm/types.go | 47 +-- vm/vm.go | 123 +++---- 21 files changed, 1341 insertions(+), 1103 deletions(-) create mode 100644 common/word.go create mode 100644 state/block_cache.go create mode 100644 state/common.go create mode 100644 state/execution.go create mode 100644 state/tx_cache.go delete mode 100644 state/vm_app_state.go delete mode 100644 vm/common.go 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/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 84488731f..8756df612 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1025,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/mempool/mempool.go b/mempool/mempool.go index e955dfacf..28e3ab708 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), } } @@ -36,7 +38,7 @@ func (mem *Mempool) GetState() *sm.State { 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 +64,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 +89,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/state/block_cache.go b/state/block_cache.go new file mode 100644 index 000000000..4da8e7ce7 --- /dev/null +++ b/state/block_cache.go @@ -0,0 +1,196 @@ +package state + +import ( + "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]Word256 +} + +func NewBlockCache(backend *State) *BlockCache { + return &BlockCache{ + db: backend.DB, + backend: backend, + accounts: make(map[string]accountInfo), + storages: make(map[Tuple256]Word256), + } +} + +func (cache *BlockCache) State() *State { + return cache.backend +} + +//------------------------------------- +// BlockCache.account + +func (cache *BlockCache) GetAccount(addr []byte) *ac.Account { + acc, storage, removed := unpack(cache.accounts[string(addr)]) + if removed { + return nil + } else if acc != nil { + return acc + } else { + acc = cache.backend.GetAccount(addr) + storage = makeStorage(cache.db, acc.StorageRoot) + cache.accounts[string(addr)] = accountInfo{acc, storage, false} + return acc + } +} + +func (cache *BlockCache) UpdateAccount(acc *ac.Account) { + addr := acc.Address + // SANITY CHECK + _, storage, removed := unpack(cache.accounts[string(addr)]) + if removed { + panic("UpdateAccount on a removed account") + } + // SANITY CHECK END + cache.accounts[string(addr)] = accountInfo{acc, storage, false} +} + +func (cache *BlockCache) RemoveAccount(addr []byte) { + // SANITY CHECK + _, _, removed := unpack(cache.accounts[string(addr)]) + if removed { + panic("RemoveAccount on a removed account") + } + // SANITY CHECK END + cache.accounts[string(addr)] = accountInfo{nil, nil, true} +} + +// BlockCache.account +//------------------------------------- +// BlockCache.storage + +func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) { + // Check cache + value, ok := cache.storages[Tuple256{addr, key}] + if ok { + return value + } + + // Get or load storage + _, storage, removed := unpack(cache.accounts[string(addr.Prefix(20))]) + if removed { + panic("GetStorage() on removed account") + } + + // Load and set cache + _, val_ := storage.Get(key.Bytes()) + value = Zero256 + if val_ != nil { + value = RightPadWord256(val_.([]byte)) + } + cache.storages[Tuple256{addr, key}] = value + return value +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { + _, _, removed := unpack(cache.accounts[string(addr.Prefix(20))]) + if removed { + panic("SetStorage() on a removed account") + } + cache.storages[Tuple256{addr, key}] = value +} + +// 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 := unpack(cache.accounts[string(addr.Prefix(20))]) + curAddr = addr + curAcc = acc + curAccRemoved = removed + curStorage = storage + } + if curAccRemoved { + continue + } + value := cache.storages[storageKey] + 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 := unpack(cache.accounts[addrStr]) + 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)) + } + acc.StorageRoot = storage.Save() + cache.backend.UpdateAccount(acc) + } + } + +} + +//----------------------------------------------------------------------------- + +type accountInfo struct { + account *ac.Account + storage merkle.Tree + removed bool +} + +func unpack(accInfo accountInfo) (*ac.Account, merkle.Tree, bool) { + return accInfo.account, accInfo.storage, accInfo.removed +} 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 0fcd64f7b..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,220 +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 -} - -// Mutates the block in place and updates it with new state hash. -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 -} - -// Appends the block, does not check block.StateHash -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling appendBlock! -func (s *State) appendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error { - // Basic block validation. - err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime) - if err != nil { - return err - } - - // Validate block Validation. - if block.Height == 1 { - if len(block.Validation.Commits) != 0 { - return errors.New("Block at height 1 (first block) should have no Validation commits") - } - } else { - if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() { - return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", - s.LastBondedValidators.Size(), len(block.Validation.Commits))) - } - var sumVotingPower uint64 - s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool { - commit := block.Validation.Commits[index] - if commit.IsZero() { - return false - } else { - vote := &types.Vote{ - Height: block.Height - 1, - Round: commit.Round, - Type: types.VoteTypeCommit, - BlockHash: block.LastBlockHash, - BlockParts: block.LastBlockParts, - } - if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) { - sumVotingPower += val.VotingPower - return false - } else { - log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote)) - err = errors.New("Invalid validation signature") - return true - } - } - }) - if err != nil { - return err - } - if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 { - return errors.New("Insufficient validation voting power") - } - } - - // Update Validator.LastCommitHeight as necessary. - for i, commit := range block.Validation.Commits { - if commit.IsZero() { - continue - } - _, val := s.LastBondedValidators.GetByIndex(uint(i)) - if val == nil { - panic(Fmt("Failed to fetch validator at index %v", i)) - } - if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.BondedValidators.Update(val_) - if !updated { - panic("Failed to update bonded validator LastCommitHeight") - } - } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.UnbondingValidators.Update(val_) - if !updated { - panic("Failed to update unbonding validator LastCommitHeight") - } - } else { - panic("Could not find validator") - } - } - - // Remember LastBondedValidators - s.LastBondedValidators = s.BondedValidators.Copy() - - // Commit each tx - for _, tx := range block.Data.Txs { - err := s.ExecTx(tx, true) - if err != nil { - return InvalidTxError{tx, err} - } - } - - // If any unbonding periods are over, - // reward account with bonded coins. - toRelease := []*Validator{} - s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool { - if val.UnbondHeight+unbondingPeriodBlocks < block.Height { - toRelease = append(toRelease, val) - } - return false - }) - for _, val := range toRelease { - s.releaseValidator(val) - } - - // If any validators haven't signed in a while, - // unbond them, they have timed out. - toTimeout := []*Validator{} - s.BondedValidators.Iterate(func(index uint, val *Validator) bool { - lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight) - if lastActivityHeight+validatorTimeoutBlocks < block.Height { - log.Info("Validator timeout", "validator", val, "height", block.Height) - toTimeout = append(toTimeout, val) - } - return false - }) - for _, val := range toTimeout { - s.unbondValidator(val) - } - - // Increment validator AccumPowers - s.BondedValidators.IncrementAccum(1) - - s.LastBlockHeight = block.Height - s.LastBlockHash = block.Hash() - s.LastBlockParts = blockPartsHeader - s.LastBlockTime = block.Time - return nil -} - -// The returned Account is a copy, so mutating it -// has no side effects. -func (s *State) GetAccount(address []byte) *account.Account { - _, acc := s.accounts.Get(address) - if acc == nil { - return nil - } - return acc.(*account.Account).Copy() -} - -// The returned Account is a copy, so mutating it -// has no side effects. -func (s *State) GetAccounts() merkle.Tree { - return s.accounts.Copy() -} +// State.validators +//------------------------------------- +// State.storage -// 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()) +func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { + storage = merkle.NewIAVLTree(binary.BasicCodec, binary.BasicCodec, 1024, s.DB) + storage.Load(hash) + return storage } -// 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()) - } -} +// State.storage +//------------------------------------- -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..1e7fd7139 --- /dev/null +++ b/state/tx_cache.go @@ -0,0 +1,193 @@ +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 and set cache + value = cache.backend.GetStorage(addr, key) + cache.storages[Tuple256{addr, key}] = value + return value +} + +// 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/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 be11467ea..876a77cba 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -10,41 +10,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,36 +51,34 @@ 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) { @@ -92,23 +88,23 @@ func (fas *FakeAppState) AddLog(log *Log) { func main() { appState := &FakeAppState{ accounts: make(map[string]*Account), - storage: make(map[string]Word), + storage: make(map[string]Word256), logs: nil, } params := Params{ BlockHeight: 0, - BlockHash: Zero, + BlockHash: Zero256, BlockTime: 0, GasLimit: 0, } - ourVm := NewVM(appState, params, Zero) + ourVm := NewVM(appState, params, Zero256) // Create accounts account1 := &Account{ - Address: Uint64ToWord(100), + Address: Uint64ToWord256(100), } account2 := &Account{ - Address: Uint64ToWord(101), + Address: Uint64ToWord256(101), } var gas uint64 = 1000 @@ -117,11 +113,11 @@ func main() { } // 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/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..8d5c15096 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -10,6 +10,7 @@ import ( ) var ( + ErrUnknownAddress = errors.New("Unknown address") ErrInsufficientBalance = errors.New("Insufficient balance") ErrInvalidJumpDest = errors.New("Invalid jump dest") ErrInsufficientGas = errors.New("Insuffient gas") @@ -32,12 +33,12 @@ const ( 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, @@ -114,7 +115,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case DIV: // 0x04 x, y := stack.Pop64(), stack.Pop64() if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(x / y) @@ -124,7 +125,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case SDIV: // 0x05 x, y := int64(stack.Pop64()), int64(stack.Pop64()) if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(uint64(x / y)) @@ -134,7 +135,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case MOD: // 0x06 x, y := stack.Pop64(), stack.Pop64() if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(x % y) @@ -144,7 +145,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case SMOD: // 0x07 x, y := int64(stack.Pop64()), int64(stack.Pop64()) if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(uint64(x % y)) @@ -154,7 +155,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case ADDMOD: // 0x08 x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64() if z == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) } else { stack.Push64(x % y) @@ -164,7 +165,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case MULMOD: // 0x09 x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64() if z == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) } else { stack.Push64(x % y) @@ -187,7 +188,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x < y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v < %v = %v\n", x, y, x < y) @@ -196,7 +197,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x > y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v > %v = %v\n", x, y, x > y) @@ -205,7 +206,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x < y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v < %v = %v\n", x, y, x < y) @@ -214,7 +215,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x > y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v > %v = %v\n", x, y, x > y) @@ -223,7 +224,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x > y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v == %v = %v\n", x, y, x == y) @@ -232,7 +233,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x == 0 { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v == 0 = %v\n", x, x == 0) @@ -287,11 +288,11 @@ 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) // 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) @@ -313,7 +314,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if !ok { return nil, firstErr(err, ErrInputOutOfBounds) } - stack.Push(RightPadWord(data)) + stack.Push(RightPadWord256(data)) fmt.Printf(" => 0x%X\n", data) case CALLDATASIZE: // 0x36 @@ -357,7 +358,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE_DEPRECATED: // 0x3A - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n") case EXTCODESIZE: // 0x3B @@ -365,11 +366,11 @@ 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_ != 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) @@ -379,11 +380,11 @@ 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_ != 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() @@ -399,11 +400,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case COINBASE: // 0x41 - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case TIMESTAMP: // 0x42 @@ -430,7 +431,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if !ok { return nil, firstErr(err, ErrMemoryOutOfBounds) } - stack.Push(RightPadWord(data)) + stack.Push(RightPadWord256(data)) fmt.Printf(" => 0x%X\n", data) case MSTORE: // 0x52 @@ -452,21 +453,14 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga 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) 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) - } + vm.appState.SetStorage(callee.Address, loc, data) + useGas(gas, GasStorageUpdate) fmt.Printf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 @@ -501,7 +495,7 @@ 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) @@ -518,7 +512,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga 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() @@ -551,19 +545,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 @@ -598,22 +587,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) @@ -640,15 +629,15 @@ 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 Zero256, 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) + vm.appState.RemoveAccount(callee) fmt.Printf(" => (%X) %v\n", addr[:4], balance) fallthrough From 4be97fa9d40c031628af4ce285f91dd950f5dc41 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Mar 2015 00:07:10 -0700 Subject: [PATCH 03/14] Transcribe vm changes from vm_fixes by Ethan --- vm/test/fake_app_state.go | 29 ----- vm/test/vm_test.go | 99 +++++++++++++++ vm/vm.go | 250 +++++++++++++++++++++++--------------- 3 files changed, 253 insertions(+), 125 deletions(-) create mode 100644 vm/test/vm_test.go diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index 876a77cba..3d6f5ca22 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -1,8 +1,6 @@ package vm import ( - "fmt" - . "github.com/tendermint/tendermint/common" . "github.com/tendermint/tendermint/vm" "github.com/tendermint/tendermint/vm/sha3" @@ -85,33 +83,6 @@ 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]Word256), - logs: nil, - } - params := Params{ - BlockHeight: 0, - BlockHash: Zero256, - BlockTime: 0, - GasLimit: 0, - } - ourVm := NewVM(appState, params, Zero256) - - // Create accounts - account1 := &Account{ - Address: Uint64ToWord256(100), - } - account2 := &Account{ - Address: Uint64ToWord256(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) Word256 { nonce := creator.Nonce 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/vm.go b/vm/vm.go index 8d5c15096..52f3d0234 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -3,7 +3,7 @@ package vm import ( "errors" "fmt" - "math" + "math/big" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/vm/sha3" @@ -24,12 +24,21 @@ 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 @@ -74,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 @@ -90,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 { @@ -98,90 +107,123 @@ 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 + //x, y := stack.Pop64(), stack.Pop64() + //stack.Push64(x / y) + x, y := stack.Pop(), stack.Pop() + if y.IsZero() { // TODO stack.Push(Zero256) - fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) + 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(Zero256) - fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) + 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 + //x, y := stack.Pop64(), stack.Pop64() + x, y := stack.Pop(), stack.Pop() + if y.IsZero() { // TODO stack.Push(Zero256) - fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) + 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(Zero256) - fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) + 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(Zero256) - fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) + dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) } else { - 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(Zero256) - fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) + dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) } else { - 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() @@ -190,7 +232,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } else { 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() @@ -199,7 +241,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } else { 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()) @@ -208,7 +250,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } else { 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()) @@ -217,16 +259,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } else { 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(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() @@ -235,27 +277,27 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } else { 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() @@ -264,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 { @@ -277,11 +319,11 @@ 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() @@ -294,19 +336,19 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } 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() @@ -315,11 +357,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrInputOutOfBounds) } stack.Push(RightPadWord256(data)) - fmt.Printf(" => 0x%X\n", 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() @@ -334,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) @@ -355,11 +396,11 @@ 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(Zero256) - fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n") + dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n") case EXTCODESIZE: // 0x3B addr := stack.Pop() @@ -373,7 +414,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga 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() @@ -397,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(Zero256) - fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case COINBASE: // 0x41 stack.Push(Zero256) - fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case TIMESTAMP: // 0x42 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() @@ -432,16 +473,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrMemoryOutOfBounds) } stack.Push(RightPadWord256(data)) - fmt.Printf(" => 0x%X\n", 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) @@ -449,19 +490,21 @@ 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) - stack.Push(data) - fmt.Printf(" {0x%X : 0x%X}\n", loc, data) + stack.Push(flipWord(data)) + dbg.Printf(" {0x%X : 0x%X}\n", loc, data) case SSTORE: // 0x55 loc, data := stack.Pop(), stack.Pop() + loc = flipWord(loc) + data = flipWord(data) vm.appState.SetStorage(callee.Address, loc, data) useGas(gas, GasStorageUpdate) - fmt.Printf(" {0x%X : 0x%X}\n", loc, data) + dbg.Printf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 err = jump(code, stack.Pop64(), &pc) @@ -473,7 +516,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) @@ -483,10 +526,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: @@ -498,17 +541,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga 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) @@ -528,7 +571,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() @@ -560,7 +603,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) @@ -613,7 +656,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() @@ -621,7 +664,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 @@ -629,7 +672,7 @@ 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 Zero256, then make it the fee. + // TODO if the receiver is , then make it the fee. receiver := vm.appState.GetAccount(addr) if receiver == nil { return nil, firstErr(err, ErrUnknownAddress) @@ -638,11 +681,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga receiver.Balance += balance vm.appState.UpdateAccount(receiver) vm.appState.RemoveAccount(callee) - fmt.Printf(" => (%X) %v\n", addr[:4], balance) + 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)) } @@ -677,10 +720,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 } @@ -713,10 +756,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 +} From 42e2e11364b2ec7aa3c5921593f067bf26db3f79 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Mar 2015 17:47:12 -0700 Subject: [PATCH 04/14] =?UTF-8?q?add=20dirty=20bit=20to=20BlockCache=20acc?= =?UTF-8?q?ounts=20and=20storages.=20construct=20account=E2=80=99s=20stora?= =?UTF-8?q?ge=20lazily.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- state/block_cache.go | 69 ++++++++++++++++++++++++++++++-------------- state/tx_cache.go | 6 ++-- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/state/block_cache.go b/state/block_cache.go index 4da8e7ce7..9af3b4edc 100644 --- a/state/block_cache.go +++ b/state/block_cache.go @@ -1,6 +1,7 @@ package state import ( + "bytes" "sort" ac "github.com/tendermint/tendermint/account" @@ -25,7 +26,7 @@ type BlockCache struct { db dbm.DB backend *State accounts map[string]accountInfo - storages map[Tuple256]Word256 + storages map[Tuple256]storageInfo } func NewBlockCache(backend *State) *BlockCache { @@ -33,7 +34,7 @@ func NewBlockCache(backend *State) *BlockCache { db: backend.DB, backend: backend, accounts: make(map[string]accountInfo), - storages: make(map[Tuple256]Word256), + storages: make(map[Tuple256]storageInfo), } } @@ -45,15 +46,14 @@ func (cache *BlockCache) State() *State { // BlockCache.account func (cache *BlockCache) GetAccount(addr []byte) *ac.Account { - acc, storage, removed := unpack(cache.accounts[string(addr)]) + acc, _, removed, _ := cache.accounts[string(addr)].unpack() if removed { return nil } else if acc != nil { return acc } else { acc = cache.backend.GetAccount(addr) - storage = makeStorage(cache.db, acc.StorageRoot) - cache.accounts[string(addr)] = accountInfo{acc, storage, false} + cache.accounts[string(addr)] = accountInfo{acc, nil, false, false} return acc } } @@ -61,22 +61,22 @@ func (cache *BlockCache) GetAccount(addr []byte) *ac.Account { func (cache *BlockCache) UpdateAccount(acc *ac.Account) { addr := acc.Address // SANITY CHECK - _, storage, removed := unpack(cache.accounts[string(addr)]) + _, 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} + cache.accounts[string(addr)] = accountInfo{acc, storage, false, true} } func (cache *BlockCache) RemoveAccount(addr []byte) { // SANITY CHECK - _, _, removed := unpack(cache.accounts[string(addr)]) + _, _, 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} + cache.accounts[string(addr)] = accountInfo{nil, nil, true, false} } // BlockCache.account @@ -85,16 +85,20 @@ func (cache *BlockCache) RemoveAccount(addr []byte) { func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) { // Check cache - value, ok := cache.storages[Tuple256{addr, key}] + info, ok := cache.storages[Tuple256{addr, key}] if ok { - return value + return info.value } // Get or load storage - _, storage, removed := unpack(cache.accounts[string(addr.Prefix(20))]) + 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()) @@ -102,17 +106,17 @@ func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) { if val_ != nil { value = RightPadWord256(val_.([]byte)) } - cache.storages[Tuple256{addr, key}] = value + 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 := unpack(cache.accounts[string(addr.Prefix(20))]) + _, _, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack() if removed { panic("SetStorage() on a removed account") } - cache.storages[Tuple256{addr, key}] = value + cache.storages[Tuple256{addr, key}] = storageInfo{value, true} } // BlockCache.storage @@ -140,7 +144,7 @@ func (cache *BlockCache) Sync() { for _, storageKey := range storageKeys { addr, key := Tuple256Split(storageKey) if addr != curAddr || curAcc == nil { - acc, storage, removed := unpack(cache.accounts[string(addr.Prefix(20))]) + acc, storage, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack() curAddr = addr curAcc = acc curAccRemoved = removed @@ -149,7 +153,10 @@ func (cache *BlockCache) Sync() { if curAccRemoved { continue } - value := cache.storages[storageKey] + value, dirty := cache.storages[storageKey].unpack() + if !dirty { + continue + } if value.IsZero() { curStorage.Remove(key.Bytes()) } else { @@ -166,7 +173,7 @@ func (cache *BlockCache) Sync() { // Update or delete accounts. for _, addrStr := range addrStrs { - acc, storage, removed := unpack(cache.accounts[addrStr]) + acc, storage, removed, dirty := cache.accounts[addrStr].unpack() if removed { removed := cache.backend.RemoveAccount(acc.Address) if !removed { @@ -176,8 +183,16 @@ func (cache *BlockCache) Sync() { if acc == nil { panic(Fmt("Account should not be nil for addr: %X", acc.Address)) } - acc.StorageRoot = storage.Save() - cache.backend.UpdateAccount(acc) + if storage != nil { + newStorageRoot := storage.Save() + if !bytes.Equal(newStorageRoot, acc.StorageRoot) { + acc.StorageRoot = newStorageRoot + dirty = true + } + } + if dirty { + cache.backend.UpdateAccount(acc) + } } } @@ -189,8 +204,18 @@ 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 unpack(accInfo accountInfo) (*ac.Account, merkle.Tree, bool) { - return accInfo.account, accInfo.storage, accInfo.removed +func (stjInfo storageInfo) unpack() (Word256, bool) { + return stjInfo.value, stjInfo.dirty } diff --git a/state/tx_cache.go b/state/tx_cache.go index 1e7fd7139..d4188db3c 100644 --- a/state/tx_cache.go +++ b/state/tx_cache.go @@ -94,10 +94,8 @@ func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 { return value } - // Load and set cache - value = cache.backend.GetStorage(addr, key) - cache.storages[Tuple256{addr, key}] = value - return value + // Load from backend + return cache.backend.GetStorage(addr, key) } // NOTE: Set value to zero to removed from the trie. From d2724c973179baed5f485edfb0d9300521e5ac9c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 25 Mar 2015 18:06:57 -0700 Subject: [PATCH 05/14] rpc: give each call a dedicated Response struct, add basic test --- daemon/daemon.go | 4 +++ p2p/switch.go | 4 +++ rpc/accounts.go | 34 ++++++++++++++------- rpc/blocks.go | 26 +++++++++++----- rpc/mempool.go | 17 +++++++---- rpc/net.go | 54 ++++++++++++++++++++++++---------- rpc/test/rpc_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ rpc/txs.go | 11 ++++++- rpc/validators.go | 16 ++++++---- 9 files changed, 191 insertions(+), 45 deletions(-) create mode 100644 rpc/test/rpc_test.go diff --git a/daemon/daemon.go b/daemon/daemon.go index ccb2932de..b50506188 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -144,6 +144,10 @@ func (n *Node) StartRpc() { rpc.StartHTTPServer() } +func (n *Node) Switch() *p2p.Switch { + return n.sw +} + func (n *Node) ConsensusState() *consensus.ConsensusState { return n.consensusState } diff --git a/p2p/switch.go b/p2p/switch.go index 635e0ecaa..42d2db9a7 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -223,6 +223,10 @@ func (sw *Switch) StopPeerGracefully(peer *Peer) { sw.doRemovePeer(peer, nil) } +func (sw *Switch) GetChainId() string { + return sw.chainId +} + func (sw *Switch) SetChainId(hash []byte, network string) { sw.chainId = hex.EncodeToString(hash) + "-" + network } diff --git a/rpc/accounts.go b/rpc/accounts.go index aba499081..8a44d03b0 100644 --- a/rpc/accounts.go +++ b/rpc/accounts.go @@ -8,16 +8,28 @@ import ( . "github.com/tendermint/tendermint/common" ) +//----------------------------------------------------------------------------- + +// Request: {} + +type ResponseGenPrivAccount struct { + PrivAccount *account.PrivAccount +} + func GenPrivAccountHandler(w http.ResponseWriter, r *http.Request) { privAccount := account.GenPrivAccount() - WriteAPIResponse(w, API_OK, struct { - PrivAccount *account.PrivAccount - }{privAccount}) + WriteAPIResponse(w, API_OK, ResponseGenPrivAccount{privAccount}) } //----------------------------------------------------------------------------- +// Request: {"address": string} + +type ResponseGetAccount struct { + Account *account.Account +} + func GetAccountHandler(w http.ResponseWriter, r *http.Request) { addressStr := GetParam(r, "address") @@ -37,13 +49,18 @@ func GetAccountHandler(w http.ResponseWriter, r *http.Request) { return } - WriteAPIResponse(w, API_OK, struct { - Account *account.Account - }{account_}) + WriteAPIResponse(w, API_OK, ResponseGetAccount{account_}) } //----------------------------------------------------------------------------- +// Request: {} + +type ResponseListAccounts struct { + BlockHeight uint + Accounts []*account.Account +} + func ListAccountsHandler(w http.ResponseWriter, r *http.Request) { var blockHeight uint var accounts []*account.Account @@ -54,8 +71,5 @@ func ListAccountsHandler(w http.ResponseWriter, r *http.Request) { return false }) - WriteAPIResponse(w, API_OK, struct { - BlockHeight uint - Accounts []*account.Account - }{blockHeight, accounts}) + WriteAPIResponse(w, API_OK, ResponseListAccounts{blockHeight, accounts}) } diff --git a/rpc/blocks.go b/rpc/blocks.go index 1415f1ff0..5a9e37e54 100644 --- a/rpc/blocks.go +++ b/rpc/blocks.go @@ -7,6 +7,15 @@ import ( "github.com/tendermint/tendermint/types" ) +//----------------------------------------------------------------------------- + +// Request: {} + +type ResponseBlockchainInfo struct { + LastHeight uint + BlockMetas []*types.BlockMeta +} + func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) { minHeight, _ := GetParamUint(r, "min_height") maxHeight, _ := GetParamUint(r, "max_height") @@ -26,14 +35,18 @@ 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}) + WriteAPIResponse(w, API_OK, ResponseBlockchainInfo{blockStore.Height(), blockMetas}) } //----------------------------------------------------------------------------- +// Request: {"height": uint} + +type ResponseGetBlock struct { + BlockMeta *types.BlockMeta + Block *types.Block +} + func GetBlockHandler(w http.ResponseWriter, r *http.Request) { height, _ := GetParamUint(r, "height") if height == 0 { @@ -48,8 +61,5 @@ func GetBlockHandler(w http.ResponseWriter, r *http.Request) { blockMeta := blockStore.LoadBlockMeta(height) block := blockStore.LoadBlock(height) - WriteAPIResponse(w, API_OK, struct { - BlockMeta *types.BlockMeta - Block *types.Block - }{blockMeta, block}) + WriteAPIResponse(w, API_OK, ResponseGetBlock{blockMeta, block}) } diff --git a/rpc/mempool.go b/rpc/mempool.go index 81402ebb3..5be8997b9 100644 --- a/rpc/mempool.go +++ b/rpc/mempool.go @@ -10,6 +10,17 @@ import ( "github.com/tendermint/tendermint/types" ) +//----------------------------------------------------------------------------- + +// Request: {"tx": string} +// Note: "tx" should be json encoded signed transaction + +type ResponseBroadcastTx struct { + TxHash []byte + CreatesContract bool + ContractAddr []byte +} + func BroadcastTxHandler(w http.ResponseWriter, r *http.Request) { txJSON := GetParam(r, "tx") var err error @@ -37,11 +48,7 @@ func BroadcastTxHandler(w http.ResponseWriter, r *http.Request) { } } - WriteAPIResponse(w, API_OK, struct { - TxHash []byte - CreatesContract bool - ContractAddr []byte - }{txHash, createsContract, contractAddr}) + WriteAPIResponse(w, API_OK, ResponseBroadcastTx{txHash, createsContract, contractAddr}) return } diff --git a/rpc/net.go b/rpc/net.go index ae187e1ab..6caeaec44 100644 --- a/rpc/net.go +++ b/rpc/net.go @@ -2,22 +2,47 @@ package rpc import ( "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/types" "net/http" ) +//----------------------------------------------------------------------------- + +// Request: {} + +type ResponseStatus struct { + ChainId string + LatestBlockHash []byte + LatestBlockHeight uint + LatestBlockTime int64 // nano + Network string +} + func StatusHandler(w http.ResponseWriter, r *http.Request) { - genesisHash := blockStore.LoadBlockMeta(0).Hash + genesisHash := p2pSwitch.GetChainId() 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")}) + var ( + latestBlockMeta *types.BlockMeta + latestBlockHash []byte + latestBlockTime int64 + ) + if latestHeight != 0 { + latestBlockMeta = blockStore.LoadBlockMeta(latestHeight) + latestBlockHash = latestBlockMeta.Hash + latestBlockTime = latestBlockMeta.Header.Time.UnixNano() + } + + WriteAPIResponse(w, API_OK, ResponseStatus{genesisHash, latestBlockHash, latestHeight, latestBlockTime, config.App().GetString("Network")}) +} + +//----------------------------------------------------------------------------- + +// Request: {} + +type ResponseNetInfo struct { + NumPeers int + Listening bool + Network string } func NetInfoHandler(w http.ResponseWriter, r *http.Request) { @@ -25,9 +50,6 @@ func NetInfoHandler(w http.ResponseWriter, r *http.Request) { 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}) + WriteAPIResponse(w, API_OK, + ResponseNetInfo{numPeers, listening, network}) } diff --git a/rpc/test/rpc_test.go b/rpc/test/rpc_test.go new file mode 100644 index 000000000..b480e310a --- /dev/null +++ b/rpc/test/rpc_test.go @@ -0,0 +1,70 @@ +package rpc + +import ( + "encoding/json" + "fmt" + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/daemon" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/rpc" + "io/ioutil" + "net/http" + "testing" + "time" +) + +var ( + rpcAddr = "127.0.0.1:8089" + requestAddr = "http://" + rpcAddr + "/" + chainId string + node *daemon.Node +) + +func newNode() { + // 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() + + // Sleep forever + ch := make(chan struct{}) + <-ch +} + +func init() { + app := config.App() + app.Set("SeedNode", "") + app.Set("DB.Backend", "memdb") + app.Set("RPC.HTTP.ListenAddr", rpcAddr) + config.SetApp(app) + // start a node + go newNode() + time.Sleep(2 * time.Second) +} + +func TestSayHello(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 rpc.ResponseStatus + } + err = json.Unmarshal(body, &status) + if err != nil { + t.Fatal(err) + } + if status.Data.ChainId != node.Switch().GetChainId() { + t.Fatal(fmt.Errorf("ChainId mismatch: got %s expected %s", status.Data.ChainId, node.Switch().GetChainId())) + } +} diff --git a/rpc/txs.go b/rpc/txs.go index 5c7588f77..6ef8c3140 100644 --- a/rpc/txs.go +++ b/rpc/txs.go @@ -9,6 +9,15 @@ import ( "github.com/tendermint/tendermint/types" ) +//----------------------------------------------------------------------------- + +// Request: {"tx": string} +// Note: "tx" should be json encoded unsigned transaction + +type ResponseSignTx struct { + types.Tx +} + func SignTxHandler(w http.ResponseWriter, r *http.Request) { txStr := GetParam(r, "tx") privAccountsStr := GetParam(r, "privAccounts") @@ -53,5 +62,5 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) { rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519) } - WriteAPIResponse(w, API_OK, struct{ types.Tx }{tx}) + WriteAPIResponse(w, API_OK, ResponseSignTx{tx}) } diff --git a/rpc/validators.go b/rpc/validators.go index 9e2775158..dfd4104a6 100644 --- a/rpc/validators.go +++ b/rpc/validators.go @@ -6,6 +6,16 @@ import ( sm "github.com/tendermint/tendermint/state" ) +//----------------------------------------------------------------------------- + +// Request: {} + +type ResponseListValidators struct { + BlockHeight uint + BondedValidators []*sm.Validator + UnbondingValidators []*sm.Validator +} + func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) { var blockHeight uint var bondedValidators []*sm.Validator @@ -22,9 +32,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}) + WriteAPIResponse(w, API_OK, ResponseListValidators{blockHeight, bondedValidators, unbondingValidators}) } From 1fb1163721b483b4b03640ac630e342c3ac50343 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 26 Mar 2015 21:30:42 -0700 Subject: [PATCH 06/14] rpc: separate out golang API into rpc/core --- rpc/core/accounts.go | 32 ++++++++++++++++++++++++++++++ rpc/core/blocks.go | 44 +++++++++++++++++++++++++++++++++++++++++ rpc/core/log.go | 7 +++++++ rpc/core/mempool.go | 42 +++++++++++++++++++++++++++++++++++++++ rpc/core/net.go | 39 ++++++++++++++++++++++++++++++++++++ rpc/core/pipe.go | 29 +++++++++++++++++++++++++++ rpc/core/txs.go | 45 ++++++++++++++++++++++++++++++++++++++++++ rpc/core/validators.go | 26 ++++++++++++++++++++++++ 8 files changed, 264 insertions(+) create mode 100644 rpc/core/accounts.go create mode 100644 rpc/core/blocks.go create mode 100644 rpc/core/log.go create mode 100644 rpc/core/mempool.go create mode 100644 rpc/core/net.go create mode 100644 rpc/core/pipe.go create mode 100644 rpc/core/txs.go create mode 100644 rpc/core/validators.go diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go new file mode 100644 index 000000000..b70fa1874 --- /dev/null +++ b/rpc/core/accounts.go @@ -0,0 +1,32 @@ +package core + +import ( + "github.com/tendermint/tendermint/account" +) + +//----------------------------------------------------------------------------- + +func GenPrivAccount() *account.PrivAccount { + return account.GenPrivAccount() +} + +//----------------------------------------------------------------------------- + +func GetAccount(address []byte) *account.Account { + state := consensusState.GetState() + return state.GetAccount(address) +} + +//----------------------------------------------------------------------------- + +func ListAccounts() (uint, []*account.Account) { + 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 blockHeight, accounts +} diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go new file mode 100644 index 000000000..0a92d2044 --- /dev/null +++ b/rpc/core/blocks.go @@ -0,0 +1,44 @@ +package core + +import ( + "fmt" + . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/types" +) + +//----------------------------------------------------------------------------- + +func BlockchainInfoHandler(minHeight, maxHeight uint) (uint, []*types.BlockMeta) { + if maxHeight == 0 { + maxHeight = blockStore.Height() + } else { + maxHeight = MinUint(blockStore.Height(), maxHeight) + } + if minHeight == 0 { + minHeight = uint(MaxInt(1, int(maxHeight)-20)) + } + log.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight) + + blockMetas := []*types.BlockMeta{} + for height := maxHeight; height >= minHeight; height-- { + blockMeta := blockStore.LoadBlockMeta(height) + blockMetas = append(blockMetas, blockMeta) + } + + return blockStore.Height(), blockMetas +} + +//----------------------------------------------------------------------------- + +func GetBlockHandler(height uint) (*types.BlockMeta, *types.Block, error) { + if height == 0 { + return nil, nil, fmt.Errorf("height must be greater than 1") + } + if height > blockStore.Height() { + return nil, nil, fmt.Errorf("height must be less than the current blockchain height") + } + + blockMeta := blockStore.LoadBlockMeta(height) + block := blockStore.LoadBlock(height) + return 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..e20dc2b54 --- /dev/null +++ b/rpc/core/mempool.go @@ -0,0 +1,42 @@ +package core + +import ( + "fmt" + . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/merkle" + "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" +) + +//----------------------------------------------------------------------------- + +type Receipt struct { + TxHash []byte + CreatesContract bool + ContractAddr []byte +} + +// pass pointer? +// Note: tx must be signed +func BroadcastTx(tx types.Tx) (*Receipt, error) { + err := mempoolReactor.BroadcastTx(tx) + if err != nil { + return nil, fmt.Errorf("Error broadcasting transaction: %v", err) + } + + txHash := merkle.HashFromBinary(tx) + var createsContract bool + var contractAddr []byte + // check if creates new contract + if callTx, ok := tx.(*types.CallTx); ok { + if callTx.Address == nil { + createsContract = true + contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence)) + } + } + return &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..b143d7271 --- /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() ([]byte, string, []byte, uint, int64) { + 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 genesisHash, config.App().GetString("Network"), latestBlockHash, latestHeight, latestBlockTime +} + +//----------------------------------------------------------------------------- + +func NetInfo() (int, bool, string) { + o, i, _ := p2pSwitch.NumPeers() + numPeers := o + i + listening := p2pSwitch.IsListening() + network := config.App().GetString("Network") + return numPeers, listening, network +} diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go new file mode 100644 index 000000000..b939c6dd6 --- /dev/null +++ b/rpc/core/pipe.go @@ -0,0 +1,29 @@ +package core + +import ( + "github.com/tendermint/tendermint/consensus" + mempl "github.com/tendermint/tendermint/mempool" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/types" +) + +var blockStore *types.BlockStore +var consensusState *consensus.ConsensusState +var mempoolReactor *mempl.MempoolReactor +var p2pSwitch *p2p.Switch + +func SetPipeBlockStore(bs *types.BlockStore) { + blockStore = bs +} + +func SetPipeConsensusState(cs *consensus.ConsensusState) { + consensusState = cs +} + +func SetPipeMempoolReactor(mr *mempl.MempoolReactor) { + mempoolReactor = mr +} + +func SetPipeSwitch(sw *p2p.Switch) { + p2pSwitch = sw +} diff --git a/rpc/core/txs.go b/rpc/core/txs.go new file mode 100644 index 000000000..805414898 --- /dev/null +++ b/rpc/core/txs.go @@ -0,0 +1,45 @@ +package core + +import ( + "fmt" + "github.com/tendermint/tendermint/account" + "github.com/tendermint/tendermint/types" +) + +//----------------------------------------------------------------------------- + +func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) { + // more checks? + + for i, privAccount := range privAccounts { + if privAccount == nil || privAccount.PrivKey == nil { + return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) + } + } + + switch tx.(type) { + case *types.SendTx: + sendTx := tx.(*types.SendTx) + for i, input := range sendTx.Inputs { + 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 { + input.PubKey = privAccounts[i].PubKey + input.Signature = privAccounts[i].Sign(bondTx) + } + case *types.UnbondTx: + unbondTx := tx.(*types.UnbondTx) + unbondTx.Signature = privAccounts[0].Sign(unbondTx).(account.SignatureEd25519) + case *types.RebondTx: + rebondTx := tx.(*types.RebondTx) + rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519) + } + return tx, nil +} diff --git a/rpc/core/validators.go b/rpc/core/validators.go new file mode 100644 index 000000000..b1892f69b --- /dev/null +++ b/rpc/core/validators.go @@ -0,0 +1,26 @@ +package core + +import ( + sm "github.com/tendermint/tendermint/state" +) + +//----------------------------------------------------------------------------- + +func ListValidators() (uint, []*sm.Validator, []*sm.Validator) { + var blockHeight uint + var bondedValidators []*sm.Validator + var unbondingValidators []*sm.Validator + + state := consensusState.GetState() + blockHeight = state.LastBlockHeight + state.BondedValidators.Iterate(func(index uint, val *sm.Validator) bool { + bondedValidators = append(bondedValidators, val) + return false + }) + state.UnbondingValidators.Iterate(func(index uint, val *sm.Validator) bool { + unbondingValidators = append(unbondingValidators, val) + return false + }) + + return blockHeight, bondedValidators, unbondingValidators +} From 9aeafffd9bc220cb0b8edee0a9f518b9eef6621c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 27 Mar 2015 02:25:41 -0700 Subject: [PATCH 07/14] rpc: generalized rpc using reflection on funcs and params --- daemon/daemon.go | 9 +- rpc/accounts.go | 75 ------- rpc/blocks.go | 65 ------ rpc/core/blocks.go | 4 +- rpc/http_handlers.go | 244 ++++++++++++++++++++++- rpc/mempool.go | 57 ------ rpc/net.go | 55 ----- rpc/responses.go | 61 ++++++ rpc/rpc.go | 29 --- rpc/test/.tendermint/genesis.json | 24 +++ rpc/test/.tendermint/priv_validator.json | 1 + rpc/test/rpc_test.go | 115 ++++++++++- rpc/txs.go | 66 ------ rpc/validators.go | 36 ---- 14 files changed, 436 insertions(+), 405 deletions(-) delete mode 100644 rpc/accounts.go delete mode 100644 rpc/blocks.go delete mode 100644 rpc/mempool.go delete mode 100644 rpc/net.go create mode 100644 rpc/responses.go delete mode 100644 rpc/rpc.go create mode 100644 rpc/test/.tendermint/genesis.json create mode 100755 rpc/test/.tendermint/priv_validator.json delete mode 100644 rpc/txs.go delete mode 100644 rpc/validators.go diff --git a/daemon/daemon.go b/daemon/daemon.go index b50506188..2cba7dc20 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -11,6 +11,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" "github.com/tendermint/tendermint/types" ) @@ -137,10 +138,10 @@ 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.SetPipeBlockStore(n.blockStore) + core.SetPipeConsensusState(n.consensusState) + core.SetPipeMempoolReactor(n.mempoolReactor) + core.SetPipeSwitch(n.sw) rpc.StartHTTPServer() } diff --git a/rpc/accounts.go b/rpc/accounts.go deleted file mode 100644 index 8a44d03b0..000000000 --- a/rpc/accounts.go +++ /dev/null @@ -1,75 +0,0 @@ -package rpc - -import ( - "net/http" - - "github.com/tendermint/tendermint/account" - "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" -) - -//----------------------------------------------------------------------------- - -// Request: {} - -type ResponseGenPrivAccount struct { - PrivAccount *account.PrivAccount -} - -func GenPrivAccountHandler(w http.ResponseWriter, r *http.Request) { - privAccount := account.GenPrivAccount() - - WriteAPIResponse(w, API_OK, ResponseGenPrivAccount{privAccount}) -} - -//----------------------------------------------------------------------------- - -// Request: {"address": string} - -type ResponseGetAccount struct { - Account *account.Account -} - -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, ResponseGetAccount{account_}) -} - -//----------------------------------------------------------------------------- - -// Request: {} - -type ResponseListAccounts struct { - BlockHeight uint - Accounts []*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, ResponseListAccounts{blockHeight, accounts}) -} diff --git a/rpc/blocks.go b/rpc/blocks.go deleted file mode 100644 index 5a9e37e54..000000000 --- a/rpc/blocks.go +++ /dev/null @@ -1,65 +0,0 @@ -package rpc - -import ( - "net/http" - - . "github.com/tendermint/tendermint/common" - "github.com/tendermint/tendermint/types" -) - -//----------------------------------------------------------------------------- - -// Request: {} - -type ResponseBlockchainInfo struct { - LastHeight uint - BlockMetas []*types.BlockMeta -} - -func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) { - minHeight, _ := GetParamUint(r, "min_height") - maxHeight, _ := GetParamUint(r, "max_height") - if maxHeight == 0 { - maxHeight = blockStore.Height() - } else { - maxHeight = MinUint(blockStore.Height(), maxHeight) - } - if minHeight == 0 { - minHeight = uint(MaxInt(1, int(maxHeight)-20)) - } - log.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight) - - blockMetas := []*types.BlockMeta{} - for height := maxHeight; height >= minHeight; height-- { - blockMeta := blockStore.LoadBlockMeta(height) - blockMetas = append(blockMetas, blockMeta) - } - - WriteAPIResponse(w, API_OK, ResponseBlockchainInfo{blockStore.Height(), blockMetas}) -} - -//----------------------------------------------------------------------------- - -// Request: {"height": uint} - -type ResponseGetBlock struct { - BlockMeta *types.BlockMeta - Block *types.Block -} - -func GetBlockHandler(w http.ResponseWriter, r *http.Request) { - height, _ := GetParamUint(r, "height") - if height == 0 { - WriteAPIResponse(w, API_INVALID_PARAM, "height must be greater than 1") - return - } - if height > blockStore.Height() { - WriteAPIResponse(w, API_INVALID_PARAM, "height must be less than the current blockchain height") - return - } - - blockMeta := blockStore.LoadBlockMeta(height) - block := blockStore.LoadBlock(height) - - WriteAPIResponse(w, API_OK, ResponseGetBlock{blockMeta, block}) -} diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 0a92d2044..84e7b53b0 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -8,7 +8,7 @@ import ( //----------------------------------------------------------------------------- -func BlockchainInfoHandler(minHeight, maxHeight uint) (uint, []*types.BlockMeta) { +func BlockchainInfo(minHeight, maxHeight uint) (uint, []*types.BlockMeta) { if maxHeight == 0 { maxHeight = blockStore.Height() } else { @@ -30,7 +30,7 @@ func BlockchainInfoHandler(minHeight, maxHeight uint) (uint, []*types.BlockMeta) //----------------------------------------------------------------------------- -func GetBlockHandler(height uint) (*types.BlockMeta, *types.Block, error) { +func GetBlock(height uint) (*types.BlockMeta, *types.Block, error) { if height == 0 { return nil, nil, fmt.Errorf("height must be greater than 1") } diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index 14a5a6813..f1a278c0d 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -1,21 +1,245 @@ package rpc import ( + "encoding/json" + "fmt" + "github.com/tendermint/tendermint/binary" + "github.com/tendermint/tendermint/rpc/core" + "io/ioutil" "net/http" + "reflect" + "strconv" ) +// map each function to the argument names +var argsMap = map[string][]string{ + "status": []string{}, + "net_info": []string{}, + "blockchain": []string{"min_height", "max_height"}, + "get_block": []string{"height"}, + "get_account": []string{"address"}, + "list_validators": []string{}, + "broadcast_tx": []string{"tx"}, + "list_accounts": []string{}, + "gen_priv_account": []string{}, + "sign_tx": []string{"tx", "privAccounts"}, +} + +// cache all type information about each function up front +var funcMap = map[string]*FuncWrapper{ + "status": funcWrap("status", core.Status), + "net_info": funcWrap("net_info", core.NetInfo), + "blockchain": funcWrap("blockchain", core.BlockchainInfo), + "get_block": funcWrap("get_block", core.GetBlock), + "get_account": funcWrap("get_account", core.GetAccount), + "list_validators": funcWrap("list_validators", core.ListValidators), + "broadcast_tx": funcWrap("broadcast_tx", core.BroadcastTx), + "list_accounts": funcWrap("list_accounts", core.ListAccounts), + "gen_priv_account": funcWrap("gen_priv_account", core.GenPrivAccount), + "sign_tx": funcWrap("sign_tx", core.SignTx), +} + +var responseMap = map[string]reflect.Value{ + "status": reflect.ValueOf(&ResponseStatus{}), + "net_info": reflect.ValueOf(&ResponseNetInfo{}), + "blockchain": reflect.ValueOf(&ResponseBlockchainInfo{}), + "get_block": reflect.ValueOf(&ResponseGetBlock{}), + "get_account": reflect.ValueOf(&ResponseGetAccount{}), + "list_validators": reflect.ValueOf(&ResponseListValidators{}), + "broadcast_tx": reflect.ValueOf(&ResponseBroadcastTx{}), + "list_accounts": reflect.ValueOf(&ResponseListAccounts{}), + "gen_priv_account": reflect.ValueOf(&ResponseGenPrivAccount{}), + "sign_tx": reflect.ValueOf(&ResponseSignTx{}), +} + +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 + response reflect.Value // response struct (to be filled with "returns") +} + +func funcWrap(name string, f interface{}) *FuncWrapper { + return &FuncWrapper{ + f: reflect.ValueOf(f), + args: funcArgTypes(f), + returns: funcReturnTypes(f), + argNames: argsMap[name], + response: responseMap[name], + } +} + +// 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, err.Error()) + return + } + returns := funcInfo.f.Call(values) + response, err := returnsToResponse(funcInfo, returns) + if err != nil { + WriteAPIResponse(w, API_ERROR, err.Error()) + } + WriteAPIResponse(w, API_OK, response) + } +} + +// covert an http query to a list of properly typed values +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)) + fmt.Println("names:", argNames) + for i, name := range argNames { + ty := argTypes[i] + v := reflect.New(ty).Elem() + kind := v.Kind() + arg := GetParam(r, name) + switch kind { + case reflect.Struct: + binary.ReadJSON(v.Interface(), []byte(arg), &err) + if err != nil { + return nil, err + } + case reflect.Slice: + v = reflect.ValueOf([]byte(arg)) + case reflect.Int64: + u, err := strconv.ParseInt(arg, 10, 64) + if err != nil { + return nil, err + } + v = reflect.ValueOf(u) + case reflect.Int32: + u, err := strconv.ParseInt(arg, 10, 32) + if err != nil { + return nil, err + } + v = reflect.ValueOf(u) + case reflect.Uint64: + u, err := strconv.ParseUint(arg, 10, 64) + if err != nil { + return nil, err + } + v = reflect.ValueOf(u) + case reflect.Uint: + u, err := strconv.ParseUint(arg, 10, 32) + if err != nil { + return nil, err + } + v = reflect.ValueOf(u) + default: + v = reflect.ValueOf(arg) + } + values[i] = v + } + return values, nil +} + +// covert a list of interfaces to properly typed values +func paramsToValues(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) { + values := make([]reflect.Value, len(params)) + for i, p := range params { + values[i] = reflect.ValueOf(p) + } + return values, 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] + return nil, errV.Interface().(error) + } + + v := funcInfo.response.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, err.Error()) + return + } + returns := funcInfo.f.Call(values) + response, err := returnsToResponse(funcInfo, returns) + if err != nil { + WriteAPIResponse(w, API_ERROR, err.Error()) + } + 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("gen_priv_account")) + http.HandleFunc("/unsafe/sign_tx", toHandler("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 []interface{} `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/mempool.go b/rpc/mempool.go deleted file mode 100644 index 5be8997b9..000000000 --- a/rpc/mempool.go +++ /dev/null @@ -1,57 +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" -) - -//----------------------------------------------------------------------------- - -// Request: {"tx": string} -// Note: "tx" should be json encoded signed transaction - -type ResponseBroadcastTx struct { - TxHash []byte - CreatesContract bool - ContractAddr []byte -} - -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, ResponseBroadcastTx{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 6caeaec44..000000000 --- a/rpc/net.go +++ /dev/null @@ -1,55 +0,0 @@ -package rpc - -import ( - "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/types" - "net/http" -) - -//----------------------------------------------------------------------------- - -// Request: {} - -type ResponseStatus struct { - ChainId string - LatestBlockHash []byte - LatestBlockHeight uint - LatestBlockTime int64 // nano - Network string -} - -func StatusHandler(w http.ResponseWriter, r *http.Request) { - genesisHash := p2pSwitch.GetChainId() - 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() - } - - WriteAPIResponse(w, API_OK, ResponseStatus{genesisHash, latestBlockHash, latestHeight, latestBlockTime, config.App().GetString("Network")}) -} - -//----------------------------------------------------------------------------- - -// Request: {} - -type ResponseNetInfo struct { - NumPeers int - Listening bool - Network string -} - -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, - ResponseNetInfo{numPeers, listening, network}) -} diff --git a/rpc/responses.go b/rpc/responses.go new file mode 100644 index 000000000..cc77ff794 --- /dev/null +++ b/rpc/responses.go @@ -0,0 +1,61 @@ +package rpc + +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 { + TxHash []byte + CreatesContract bool + ContractAddr []byte +} + +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/rpc.go b/rpc/rpc.go deleted file mode 100644 index 94631249c..000000000 --- a/rpc/rpc.go +++ /dev/null @@ -1,29 +0,0 @@ -package rpc - -import ( - "github.com/tendermint/tendermint/consensus" - mempl "github.com/tendermint/tendermint/mempool" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/types" -) - -var blockStore *types.BlockStore -var consensusState *consensus.ConsensusState -var mempoolReactor *mempl.MempoolReactor -var p2pSwitch *p2p.Switch - -func SetRPCBlockStore(bs *types.BlockStore) { - blockStore = bs -} - -func SetRPCConsensusState(cs *consensus.ConsensusState) { - consensusState = cs -} - -func SetRPCMempoolReactor(mr *mempl.MempoolReactor) { - mempoolReactor = mr -} - -func SetRPCSwitch(sw *p2p.Switch) { - p2pSwitch = sw -} 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/rpc_test.go b/rpc/test/rpc_test.go index b480e310a..f63e457fc 100644 --- a/rpc/test/rpc_test.go +++ b/rpc/test/rpc_test.go @@ -1,16 +1,19 @@ package rpc import ( + "bytes" + "encoding/hex" "encoding/json" "fmt" + "github.com/tendermint/tendermint/binary" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/daemon" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc" "io/ioutil" "net/http" + "net/url" "testing" - "time" ) var ( @@ -18,9 +21,10 @@ var ( requestAddr = "http://" + rpcAddr + "/" chainId string node *daemon.Node + userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB" ) -func newNode() { +func newNode(ready chan struct{}) { // Create & start node node = daemon.NewNode() l := p2p.NewDefaultListener("tcp", config.App().GetString("ListenAddr"), false) @@ -29,6 +33,7 @@ func newNode() { // Run the RPC server. node.StartRpc() + ready <- struct{}{} // Sleep forever ch := make(chan struct{}) @@ -36,14 +41,19 @@ func newNode() { } 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") config.SetApp(app) // start a node - go newNode() - time.Sleep(2 * time.Second) + ready := make(chan struct{}) + go newNode(ready) + <-ready } func TestSayHello(t *testing.T) { @@ -64,7 +74,100 @@ func TestSayHello(t *testing.T) { if err != nil { t.Fatal(err) } - if status.Data.ChainId != node.Switch().GetChainId() { - t.Fatal(fmt.Errorf("ChainId mismatch: got %s expected %s", status.Data.ChainId, node.Switch().GetChainId())) + 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 TestGenPriv(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 rpc.ResponseGenPrivAccount + } + 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 TestGetAccount(t *testing.T) { + byteAddr, _ := hex.DecodeString(userAddr) + resp, err := http.PostForm(requestAddr+"get_account", + url.Values{"address": {string(byteAddr)}}) + 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 rpc.ResponseGetAccount + } + fmt.Println(string(body)) + binary.ReadJSON(&status, body, &err) + if err != nil { + t.Fatal(err) + } + if bytes.Compare(status.Data.Account.Address, byteAddr) != 0 { + t.Fatalf("Failed to get correct account. Got %x, expected %x", status.Data.Account.Address, byteAddr) + } + +} + +func TestSignedTx(t *testing.T) { + +} + +/* + acc := mint.MempoolReactor.Mempool.GetState().GetAccount(mint.priv.Address) + nonce := 0 + if acc != nil { + nonce = int(acc.Sequence) + 1 + } + + amtInt, err := strconv.Atoi(amt) + if err != nil { + return "", err + } + amtUint64 := uint64(amtInt) + + tx := &blk.SendTx{ + Inputs: []*blk.TxInput{ + &blk.TxInput{ + Address: mint.priv.Address, + Amount: amtUint64, + Sequence: uint(nonce), + Signature: account.SignatureEd25519{}, + PubKey: mint.priv.PubKey, + }, + }, + Outputs: []*blk.TxOutput{ + &blk.TxOutput{ + Address: addrB, + Amount: amtUint64, + }, + }, + } + 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/txs.go b/rpc/txs.go deleted file mode 100644 index 6ef8c3140..000000000 --- a/rpc/txs.go +++ /dev/null @@ -1,66 +0,0 @@ -package rpc - -import ( - "net/http" - - "github.com/tendermint/tendermint/account" - "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" - "github.com/tendermint/tendermint/types" -) - -//----------------------------------------------------------------------------- - -// Request: {"tx": string} -// Note: "tx" should be json encoded unsigned transaction - -type ResponseSignTx struct { - types.Tx -} - -func SignTxHandler(w http.ResponseWriter, r *http.Request) { - txStr := GetParam(r, "tx") - privAccountsStr := GetParam(r, "privAccounts") - - 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 - } - } - - switch tx.(type) { - case *types.SendTx: - sendTx := tx.(*types.SendTx) - for i, input := range sendTx.Inputs { - input.PubKey = privAccounts[i].PubKey - input.Signature = privAccounts[i].Sign(sendTx) - } - case *types.BondTx: - bondTx := tx.(*types.BondTx) - for i, input := range bondTx.Inputs { - input.PubKey = privAccounts[i].PubKey - input.Signature = privAccounts[i].Sign(bondTx) - } - case *types.UnbondTx: - unbondTx := tx.(*types.UnbondTx) - unbondTx.Signature = privAccounts[0].Sign(unbondTx).(account.SignatureEd25519) - case *types.RebondTx: - rebondTx := tx.(*types.RebondTx) - rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519) - } - - WriteAPIResponse(w, API_OK, ResponseSignTx{tx}) -} diff --git a/rpc/validators.go b/rpc/validators.go deleted file mode 100644 index dfd4104a6..000000000 --- a/rpc/validators.go +++ /dev/null @@ -1,36 +0,0 @@ -package rpc - -import ( - "net/http" - - sm "github.com/tendermint/tendermint/state" -) - -//----------------------------------------------------------------------------- - -// Request: {} - -type ResponseListValidators struct { - BlockHeight uint - BondedValidators []*sm.Validator - UnbondingValidators []*sm.Validator -} - -func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) { - var blockHeight uint - var bondedValidators []*sm.Validator - var unbondingValidators []*sm.Validator - - state := consensusState.GetState() - blockHeight = state.LastBlockHeight - state.BondedValidators.Iterate(func(index uint, val *sm.Validator) bool { - bondedValidators = append(bondedValidators, val) - return false - }) - state.UnbondingValidators.Iterate(func(index uint, val *sm.Validator) bool { - unbondingValidators = append(unbondingValidators, val) - return false - }) - - WriteAPIResponse(w, API_OK, ResponseListValidators{blockHeight, bondedValidators, unbondingValidators}) -} From 6e81e8a848dffc99d323d7c2a5f54808447b0ff9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 27 Mar 2015 13:15:59 -0700 Subject: [PATCH 08/14] rpc: fixes for better type handlings, explicit error field in response, more tests --- account/priv_account.go | 12 +++ rpc/core/txs.go | 1 - rpc/http_handlers.go | 42 +++++++--- rpc/http_params.go | 2 +- rpc/http_server.go | 14 +++- rpc/test/rpc_test.go | 170 +++++++++++++++++++++++++++++++++------- 6 files changed, 197 insertions(+), 44 deletions(-) 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/rpc/core/txs.go b/rpc/core/txs.go index 805414898..670984394 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -16,7 +16,6 @@ func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) } } - switch tx.(type) { case *types.SendTx: sendTx := tx.(*types.SendTx) diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index f1a278c0d..7d9576f70 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -39,6 +39,7 @@ var funcMap = map[string]*FuncWrapper{ "sign_tx": funcWrap("sign_tx", core.SignTx), } +// map each function to an empty struct which can hold its return values var responseMap = map[string]reflect.Value{ "status": reflect.ValueOf(&ResponseStatus{}), "net_info": reflect.ValueOf(&ResponseNetInfo{}), @@ -52,6 +53,7 @@ var responseMap = map[string]reflect.Value{ "sign_tx": reflect.ValueOf(&ResponseSignTx{}), } +// 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 @@ -76,39 +78,57 @@ func toHandler(funcName string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { values, err := queryToValues(funcInfo, r) if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, err.Error()) + 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, err.Error()) + WriteAPIResponse(w, API_ERROR, nil, err.Error()) + return } - WriteAPIResponse(w, API_OK, response) + WriteAPIResponse(w, API_OK, response, "") } } // covert an http query to a list of properly typed values +// to be properly decoded the arg must be a concrete type from tendermint 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)) - fmt.Println("names:", argNames) for i, name := range argNames { ty := argTypes[i] v := reflect.New(ty).Elem() kind := v.Kind() arg := GetParam(r, name) switch kind { + case reflect.Interface: + v = reflect.New(ty) + binary.ReadJSON(v.Interface(), []byte(arg), &err) + if err != nil { + return nil, err + } + v = v.Elem() case reflect.Struct: binary.ReadJSON(v.Interface(), []byte(arg), &err) if err != nil { return nil, err } case reflect.Slice: - v = reflect.ValueOf([]byte(arg)) + rt := ty.Elem() + if rt.Kind() == reflect.Uint8 { + v = reflect.ValueOf([]byte(arg)) + } else { + v = reflect.New(ty) + binary.ReadJSON(v.Interface(), []byte(arg), &err) + if err != nil { + return nil, err + } + v = v.Elem() + } case reflect.Int64: u, err := strconv.ParseInt(arg, 10, 64) if err != nil { @@ -142,6 +162,7 @@ func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, err } // covert a list of interfaces to properly typed values +// TODO! func paramsToValues(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) { values := make([]reflect.Value, len(params)) for i, p := range params { @@ -156,7 +177,9 @@ func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interfac finalType := returnTypes[len(returnTypes)-1] if finalType.Implements(reflect.TypeOf((*error)(nil)).Elem()) { errV := returns[len(returnTypes)-1] - return nil, errV.Interface().(error) + if errV.Interface() != nil { + return nil, fmt.Errorf("%v", errV.Interface()) + } } v := funcInfo.response.Elem() @@ -181,15 +204,16 @@ func JsonRpcHandler(w http.ResponseWriter, r *http.Request) { funcInfo := funcMap[jrpc.Method] values, err := paramsToValues(funcInfo, jrpc.Params) if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, err.Error()) + 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, err.Error()) + WriteAPIResponse(w, API_ERROR, nil, err.Error()) + return } - WriteAPIResponse(w, API_OK, response) + WriteAPIResponse(w, API_OK, response, "") } func initHandlers() { 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/test/rpc_test.go b/rpc/test/rpc_test.go index f63e457fc..aa8090ece 100644 --- a/rpc/test/rpc_test.go +++ b/rpc/test/rpc_test.go @@ -5,11 +5,14 @@ import ( "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/types" "io/ioutil" "net/http" "net/url" @@ -22,6 +25,9 @@ var ( chainId string node *daemon.Node userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB" + userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" + + userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" ) func newNode(ready chan struct{}) { @@ -49,7 +55,9 @@ func init() { 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) @@ -105,10 +113,9 @@ func TestGenPriv(t *testing.T) { } } -func TestGetAccount(t *testing.T) { - byteAddr, _ := hex.DecodeString(userAddr) +func getAccount(t *testing.T, addr []byte) *account.Account { resp, err := http.PostForm(requestAddr+"get_account", - url.Values{"address": {string(byteAddr)}}) + url.Values{"address": {string(addr)}}) if err != nil { t.Fatal(err) } @@ -120,54 +127,159 @@ func TestGetAccount(t *testing.T) { var status struct { Status string Data rpc.ResponseGetAccount + Error string } fmt.Println(string(body)) binary.ReadJSON(&status, body, &err) if err != nil { t.Fatal(err) } - if bytes.Compare(status.Data.Account.Address, byteAddr) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", status.Data.Account.Address, byteAddr) - } - + return status.Data.Account } -func TestSignedTx(t *testing.T) { +func TestGetAccount(t *testing.T) { + byteAddr, _ := hex.DecodeString(userAddr) + acc := getAccount(t, byteAddr) + if bytes.Compare(acc.Address, byteAddr) != 0 { + t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr) + } } -/* - acc := mint.MempoolReactor.Mempool.GetState().GetAccount(mint.priv.Address) +func makeTx(t *testing.T, from, to []byte, amt uint64) *types.SendTx { + acc := getAccount(t, from) nonce := 0 if acc != nil { nonce = int(acc.Sequence) + 1 } - - amtInt, err := strconv.Atoi(amt) + bytePub, err := hex.DecodeString(userPub) if err != nil { - return "", err + t.Fatal(err) } - amtUint64 := uint64(amtInt) - - tx := &blk.SendTx{ - Inputs: []*blk.TxInput{ - &blk.TxInput{ - Address: mint.priv.Address, - Amount: amtUint64, + tx := &types.SendTx{ + Inputs: []*types.TxInput{ + &types.TxInput{ + Address: from, + Amount: amt, Sequence: uint(nonce), Signature: account.SignatureEd25519{}, - PubKey: mint.priv.PubKey, + PubKey: account.PubKeyEd25519(bytePub), }, }, - Outputs: []*blk.TxOutput{ - &blk.TxOutput{ - Address: addrB, - Amount: amtUint64, + Outputs: []*types.TxOutput{ + &types.TxOutput{ + Address: to, + Amount: amt, }, }, } - tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx)) - err = mint.MempoolReactor.BroadcastTx(tx) - return hex.EncodeToString(merkle.HashFromBinary(tx)), err + 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 TestSignedTx(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 := makeTx(t, byteAddr, toAddr, amt) + + /*b, err := json.Marshal(rpc.InterfaceType{types.TxTypeSend, tx}) + if err != nil { + t.Fatal(err) + }*/ + + n := new(int64) + var err error + w := new(bytes.Buffer) + binary.WriteJSON(tx, w, n, &err) + if err != nil { + t.Fatal(err) + } + b := w.Bytes() + + privAcc := account.GenPrivAccountFromKey(byteKey) + if bytes.Compare(privAcc.PubKey.Address(), byteAddr) != 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 rpc.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 = status.Data.(rpc.ResponseSignTx).Tx.(*types.SendTx) + tx = response.Tx.(*types.SendTx) + if bytes.Compare(tx.Inputs[0].Address, byteAddr) != 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(privAcc.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) + } +} + +func TestBroadcastTx(t *testing.T) { + /* + byteAddr, _ := hex.DecodeString(userAddr) + + 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 := makeTx(t, byteAddr, toAddr, amt) + + b, err := json.Marshal([]interface{}{types.TxTypeSend, tx}) + if err != nil { + t.Fatal(err) + } + + var status struct { + Status string + Data rpc.ResponseSignTx + } + // TODO ... + */ +} -*/ +/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx)) +err = mint.MempoolReactor.BroadcastTx(tx) +return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/ From fb90d5bc92fbfb2d89a7078dc7ef8ccd56706172 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 28 Mar 2015 21:22:39 -0700 Subject: [PATCH 09/14] rpc: cleanup, more tests, working http and jsonrpc --- rpc/core/mempool.go | 12 +- rpc/http_handlers.go | 209 +++++++++++++++--------------- rpc/responses.go | 5 +- rpc/test/http_rpc_test.go | 149 +++++++++++++++++++++ rpc/test/json_rpc_test.go | 170 ++++++++++++++++++++++++ rpc/test/{rpc_test.go => test.go} | 143 +++++--------------- 6 files changed, 465 insertions(+), 223 deletions(-) create mode 100644 rpc/test/http_rpc_test.go create mode 100644 rpc/test/json_rpc_test.go rename rpc/test/{rpc_test.go => test.go} (57%) diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index e20dc2b54..148cb84c3 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -12,29 +12,29 @@ import ( type Receipt struct { TxHash []byte - CreatesContract bool + CreatesContract uint8 ContractAddr []byte } // pass pointer? // Note: tx must be signed -func BroadcastTx(tx types.Tx) (*Receipt, error) { +func BroadcastTx(tx types.Tx) (Receipt, error) { err := mempoolReactor.BroadcastTx(tx) if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) + return Receipt{}, fmt.Errorf("Error broadcasting transaction: %v", err) } txHash := merkle.HashFromBinary(tx) - var createsContract bool + var createsContract uint8 var contractAddr []byte // check if creates new contract if callTx, ok := tx.(*types.CallTx); ok { if callTx.Address == nil { - createsContract = true + createsContract = 1 contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence)) } } - return &Receipt{txHash, createsContract, contractAddr}, nil + return Receipt{txHash, createsContract, contractAddr}, nil } /* diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index 7d9576f70..e72e04305 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -1,6 +1,7 @@ package rpc import ( + "encoding/hex" "encoding/json" "fmt" "github.com/tendermint/tendermint/binary" @@ -11,46 +12,20 @@ import ( "strconv" ) -// map each function to the argument names -var argsMap = map[string][]string{ - "status": []string{}, - "net_info": []string{}, - "blockchain": []string{"min_height", "max_height"}, - "get_block": []string{"height"}, - "get_account": []string{"address"}, - "list_validators": []string{}, - "broadcast_tx": []string{"tx"}, - "list_accounts": []string{}, - "gen_priv_account": []string{}, - "sign_tx": []string{"tx", "privAccounts"}, -} - // 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("status", core.Status), - "net_info": funcWrap("net_info", core.NetInfo), - "blockchain": funcWrap("blockchain", core.BlockchainInfo), - "get_block": funcWrap("get_block", core.GetBlock), - "get_account": funcWrap("get_account", core.GetAccount), - "list_validators": funcWrap("list_validators", core.ListValidators), - "broadcast_tx": funcWrap("broadcast_tx", core.BroadcastTx), - "list_accounts": funcWrap("list_accounts", core.ListAccounts), - "gen_priv_account": funcWrap("gen_priv_account", core.GenPrivAccount), - "sign_tx": funcWrap("sign_tx", core.SignTx), -} - -// map each function to an empty struct which can hold its return values -var responseMap = map[string]reflect.Value{ - "status": reflect.ValueOf(&ResponseStatus{}), - "net_info": reflect.ValueOf(&ResponseNetInfo{}), - "blockchain": reflect.ValueOf(&ResponseBlockchainInfo{}), - "get_block": reflect.ValueOf(&ResponseGetBlock{}), - "get_account": reflect.ValueOf(&ResponseGetAccount{}), - "list_validators": reflect.ValueOf(&ResponseListValidators{}), - "broadcast_tx": reflect.ValueOf(&ResponseBroadcastTx{}), - "list_accounts": reflect.ValueOf(&ResponseListAccounts{}), - "gen_priv_account": reflect.ValueOf(&ResponseGenPrivAccount{}), - "sign_tx": reflect.ValueOf(&ResponseSignTx{}), + "status": funcWrap(core.Status, &ResponseStatus{}, []string{}), + "net_info": funcWrap(core.NetInfo, &ResponseNetInfo{}, []string{}), + "blockchain": funcWrap(core.BlockchainInfo, &ResponseBlockchainInfo{}, []string{"min_height", "max_height"}), + "get_block": funcWrap(core.GetBlock, &ResponseGetBlock{}, []string{"height"}), + "get_account": funcWrap(core.GetAccount, &ResponseGetAccount{}, []string{"address"}), + "list_validators": funcWrap(core.ListValidators, &ResponseListValidators{}, []string{}), + "broadcast_tx": funcWrap(core.BroadcastTx, &ResponseBroadcastTx{}, []string{"tx"}), + "list_accounts": funcWrap(core.ListAccounts, &ResponseListAccounts{}, []string{}), + "unsafe/gen_priv_account": funcWrap(core.GenPrivAccount, &ResponseGenPrivAccount{}, []string{}), + "unsafe/sign_tx": funcWrap(core.SignTx, &ResponseSignTx{}, []string{"tx", "privAccounts"}), } // holds all type information for each function @@ -62,13 +37,13 @@ type FuncWrapper struct { response reflect.Value // response struct (to be filled with "returns") } -func funcWrap(name string, f interface{}) *FuncWrapper { +func funcWrap(f interface{}, response interface{}, args []string) *FuncWrapper { return &FuncWrapper{ f: reflect.ValueOf(f), args: funcArgTypes(f), returns: funcReturnTypes(f), - argNames: argsMap[name], - response: responseMap[name], + argNames: args, + response: reflect.ValueOf(response), } } @@ -91,8 +66,78 @@ func toHandler(funcName string) func(http.ResponseWriter, *http.Request) { } } -// covert an http query to a list of properly typed values -// to be properly decoded the arg must be a concrete type from tendermint +// 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 @@ -101,72 +146,26 @@ func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, err values := make([]reflect.Value, len(argNames)) for i, name := range argNames { ty := argTypes[i] - v := reflect.New(ty).Elem() - kind := v.Kind() arg := GetParam(r, name) - switch kind { - case reflect.Interface: - v = reflect.New(ty) - binary.ReadJSON(v.Interface(), []byte(arg), &err) - if err != nil { - return nil, err - } - v = v.Elem() - case reflect.Struct: - binary.ReadJSON(v.Interface(), []byte(arg), &err) - if err != nil { - return nil, err - } - case reflect.Slice: - rt := ty.Elem() - if rt.Kind() == reflect.Uint8 { - v = reflect.ValueOf([]byte(arg)) - } else { - v = reflect.New(ty) - binary.ReadJSON(v.Interface(), []byte(arg), &err) - if err != nil { - return nil, err - } - v = v.Elem() - } - case reflect.Int64: - u, err := strconv.ParseInt(arg, 10, 64) - if err != nil { - return nil, err - } - v = reflect.ValueOf(u) - case reflect.Int32: - u, err := strconv.ParseInt(arg, 10, 32) - if err != nil { - return nil, err - } - v = reflect.ValueOf(u) - case reflect.Uint64: - u, err := strconv.ParseUint(arg, 10, 64) - if err != nil { - return nil, err - } - v = reflect.ValueOf(u) - case reflect.Uint: - u, err := strconv.ParseUint(arg, 10, 32) - if err != nil { - return nil, err - } - v = reflect.ValueOf(u) - default: - v = reflect.ValueOf(arg) + values[i], err = jsonToArg(ty, arg) + if err != nil { + return nil, err } - values[i] = v } return values, nil } // covert a list of interfaces to properly typed values // TODO! -func paramsToValues(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) { +func paramsToValues(funcInfo *FuncWrapper, params []string) ([]reflect.Value, error) { values := make([]reflect.Value, len(params)) for i, p := range params { - values[i] = reflect.ValueOf(p) + ty := funcInfo.args[i] + v, err := jsonToArg(ty, p) + if err != nil { + return nil, err + } + values[i] = v } return values, nil } @@ -228,8 +227,8 @@ func initHandlers() { 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("gen_priv_account")) - http.HandleFunc("/unsafe/sign_tx", toHandler("sign_tx")) + 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) @@ -241,10 +240,10 @@ func initHandlers() { } type JsonRpc struct { - JsonRpc string `json:"jsonrpc"` - Method string `json:"method"` - Params []interface{} `json:"params"` - Id int `json:"id"` + JsonRpc string `json:"jsonrpc"` + Method string `json:"method"` + Params []string `json:"params"` + Id int `json:"id"` } // this will panic if not passed a function diff --git a/rpc/responses.go b/rpc/responses.go index cc77ff794..11360c004 100644 --- a/rpc/responses.go +++ b/rpc/responses.go @@ -2,6 +2,7 @@ package rpc import ( "github.com/tendermint/tendermint/account" + "github.com/tendermint/tendermint/rpc/core" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -31,9 +32,7 @@ type ResponseGetBlock struct { // curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=... type ResponseBroadcastTx struct { - TxHash []byte - CreatesContract bool - ContractAddr []byte + Receipt core.Receipt } type ResponseStatus struct { diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go new file mode 100644 index 000000000..31e37b482 --- /dev/null +++ b/rpc/test/http_rpc_test.go @@ -0,0 +1,149 @@ +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" + "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 rpc.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 rpc.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 rpc.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) != 1 { + t.Fatal("The mem pool has %d txs. Expected 1", len(txs)) + } + tx2 := txs[0].(*types.SendTx) + 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..c8cafa302 --- /dev/null +++ b/rpc/test/json_rpc_test.go @@ -0,0 +1,170 @@ +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" + "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 rpc.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 rpc.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 rpc.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) != 1 { + t.Fatal("The mem pool has %d txs. Expected 1", len(txs)) + } + tx2 := txs[0].(*types.SendTx) + 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/rpc_test.go b/rpc/test/test.go similarity index 57% rename from rpc/test/rpc_test.go rename to rpc/test/test.go index aa8090ece..50a3ba0d3 100644 --- a/rpc/test/rpc_test.go +++ b/rpc/test/test.go @@ -64,58 +64,28 @@ func init() { <-ready } -func TestSayHello(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 rpc.ResponseStatus - } - 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 TestGenPriv(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 rpc.ResponseGenPrivAccount - } - 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 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)}}) } -} - -func getAccount(t *testing.T, addr []byte) *account.Account { - resp, err := http.PostForm(requestAddr+"get_account", - url.Values{"address": {string(addr)}}) + fmt.Println("RESPONSE:", resp) if err != nil { t.Fatal(err) } @@ -137,17 +107,8 @@ func getAccount(t *testing.T, addr []byte) *account.Account { return status.Data.Account } -func TestGetAccount(t *testing.T) { - byteAddr, _ := hex.DecodeString(userAddr) - acc := getAccount(t, byteAddr) - if bytes.Compare(acc.Address, byteAddr) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr) - } - -} - -func makeTx(t *testing.T, from, to []byte, amt uint64) *types.SendTx { - acc := getAccount(t, from) +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 @@ -193,32 +154,19 @@ func requestResponse(t *testing.T, method string, values url.Values, status inte } } -func TestSignedTx(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 := makeTx(t, byteAddr, toAddr, amt) - - /*b, err := json.Marshal(rpc.InterfaceType{types.TxTypeSend, tx}) - 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 := new(int64) + n, w := new(int64), new(bytes.Buffer) var err error - w := new(bytes.Buffer) binary.WriteJSON(tx, w, n, &err) if err != nil { t.Fatal(err) } b := w.Bytes() - privAcc := account.GenPrivAccountFromKey(byteKey) - if bytes.Compare(privAcc.PubKey.Address(), byteAddr) != 0 { + privAcc := account.GenPrivAccountFromKey(key) + if bytes.Compare(privAcc.PubKey.Address(), fromAddr) != 0 { t.Fatal("Faield to generate correct priv acc") } w = new(bytes.Buffer) @@ -233,14 +181,16 @@ func TestSignedTx(t *testing.T) { 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 = status.Data.(rpc.ResponseSignTx).Tx.(*types.SendTx) tx = response.Tx.(*types.SendTx) - if bytes.Compare(tx.Inputs[0].Address, byteAddr) != 0 { + 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!") } @@ -250,7 +200,7 @@ func TestSignedTx(t *testing.T) { if err := in.ValidateBasic(); err != nil { t.Fatal(err) } - fmt.Println(privAcc.PubKey, in.PubKey) + fmt.Println(priv.PubKey, in.PubKey) // Check signatures // acc := getAccount(t, byteAddr) // NOTE: using the acc here instead of the in fails; its PubKeyNil ... ? @@ -258,28 +208,3 @@ func TestSignedTx(t *testing.T) { t.Fatal(types.ErrTxInvalidSignature) } } - -func TestBroadcastTx(t *testing.T) { - /* - byteAddr, _ := hex.DecodeString(userAddr) - - 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 := makeTx(t, byteAddr, toAddr, amt) - - b, err := json.Marshal([]interface{}{types.TxTypeSend, tx}) - if err != nil { - t.Fatal(err) - } - - var status struct { - Status string - Data rpc.ResponseSignTx - } - // TODO ... - */ -} - -/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx)) -err = mint.MempoolReactor.BroadcastTx(tx) -return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/ From d30fc2fa43a80932b1022b3a5bb01e2087804393 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 28 Mar 2015 21:38:29 -0700 Subject: [PATCH 10/14] rpc: fix tests to count mempool; copy responses to avoid data races --- rpc/http_handlers.go | 3 ++- rpc/test/http_rpc_test.go | 7 ++++--- rpc/test/json_rpc_test.go | 7 ++++--- rpc/test/test.go | 13 +++++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index e72e04305..6cb499b1d 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -181,7 +181,8 @@ func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interfac } } - v := funcInfo.response.Elem() + // 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}) diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go index 31e37b482..a6a5fb2f2 100644 --- a/rpc/test/http_rpc_test.go +++ b/rpc/test/http_rpc_test.go @@ -134,10 +134,11 @@ func TestHTTPBroadcastTx(t *testing.T) { } pool := node.MempoolReactor().Mempool txs := pool.GetProposalTxs() - if len(txs) != 1 { - t.Fatal("The mem pool has %d txs. Expected 1", len(txs)) + if len(txs) != mempoolCount+1 { + t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount+1) } - tx2 := txs[0].(*types.SendTx) + 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") } diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go index c8cafa302..d40f579f8 100644 --- a/rpc/test/json_rpc_test.go +++ b/rpc/test/json_rpc_test.go @@ -155,10 +155,11 @@ func TestJSONBroadcastTx(t *testing.T) { } pool := node.MempoolReactor().Mempool txs := pool.GetProposalTxs() - if len(txs) != 1 { - t.Fatal("The mem pool has %d txs. Expected 1", len(txs)) + if len(txs) != mempoolCount+1 { + t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount+1) } - tx2 := txs[0].(*types.SendTx) + 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") } diff --git a/rpc/test/test.go b/rpc/test/test.go index 50a3ba0d3..3057a8bf6 100644 --- a/rpc/test/test.go +++ b/rpc/test/test.go @@ -22,10 +22,15 @@ import ( var ( rpcAddr = "127.0.0.1:8089" requestAddr = "http://" + rpcAddr + "/" - chainId string - node *daemon.Node - userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB" - userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" + + chainId string + + node *daemon.Node + + mempoolCount = 0 + + userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB" + userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" ) From 731de7a6aaec12eb4f1ab75e0aed01fff103ce6e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 28 Mar 2015 23:10:05 -0700 Subject: [PATCH 11/14] rpc: return (*Response, error) for all functions --- rpc/core/accounts.go | 12 ++++++------ rpc/core/blocks.go | 12 ++++++------ rpc/core/mempool.go | 6 +++--- rpc/core/net.go | 8 ++++---- rpc/{ => core}/responses.go | 5 ++--- rpc/core/txs.go | 4 ++-- rpc/core/validators.go | 4 ++-- rpc/http_handlers.go | 36 ++++++++++++++++++++++-------------- rpc/test/http_rpc_test.go | 8 ++++---- rpc/test/json_rpc_test.go | 7 ++++--- rpc/test/test.go | 5 +++-- 11 files changed, 58 insertions(+), 49 deletions(-) rename rpc/{ => core}/responses.go (93%) diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index b70fa1874..84ffc2ee4 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -6,20 +6,20 @@ import ( //----------------------------------------------------------------------------- -func GenPrivAccount() *account.PrivAccount { - return account.GenPrivAccount() +func GenPrivAccount() (*ResponseGenPrivAccount, error) { + return &ResponseGenPrivAccount{account.GenPrivAccount()}, nil } //----------------------------------------------------------------------------- -func GetAccount(address []byte) *account.Account { +func GetAccount(address []byte) (*ResponseGetAccount, error) { state := consensusState.GetState() - return state.GetAccount(address) + return &ResponseGetAccount{state.GetAccount(address)}, nil } //----------------------------------------------------------------------------- -func ListAccounts() (uint, []*account.Account) { +func ListAccounts() (*ResponseListAccounts, error) { var blockHeight uint var accounts []*account.Account state := consensusState.GetState() @@ -28,5 +28,5 @@ func ListAccounts() (uint, []*account.Account) { accounts = append(accounts, value.(*account.Account)) return false }) - return blockHeight, accounts + return &ResponseListAccounts{blockHeight, accounts}, nil } diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 84e7b53b0..4bbe716c0 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -8,7 +8,7 @@ import ( //----------------------------------------------------------------------------- -func BlockchainInfo(minHeight, maxHeight uint) (uint, []*types.BlockMeta) { +func BlockchainInfo(minHeight, maxHeight uint) (*ResponseBlockchainInfo, error) { if maxHeight == 0 { maxHeight = blockStore.Height() } else { @@ -25,20 +25,20 @@ func BlockchainInfo(minHeight, maxHeight uint) (uint, []*types.BlockMeta) { blockMetas = append(blockMetas, blockMeta) } - return blockStore.Height(), blockMetas + return &ResponseBlockchainInfo{blockStore.Height(), blockMetas}, nil } //----------------------------------------------------------------------------- -func GetBlock(height uint) (*types.BlockMeta, *types.Block, error) { +func GetBlock(height uint) (*ResponseGetBlock, error) { if height == 0 { - return nil, nil, fmt.Errorf("height must be greater than 1") + return nil, fmt.Errorf("height must be greater than 1") } if height > blockStore.Height() { - return nil, nil, fmt.Errorf("height must be less than the current blockchain height") + return nil, fmt.Errorf("height must be less than the current blockchain height") } blockMeta := blockStore.LoadBlockMeta(height) block := blockStore.LoadBlock(height) - return blockMeta, block, nil + return &ResponseGetBlock{blockMeta, block}, nil } diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 148cb84c3..3ff1a85ea 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -18,10 +18,10 @@ type Receipt struct { // pass pointer? // Note: tx must be signed -func BroadcastTx(tx types.Tx) (Receipt, error) { +func BroadcastTx(tx types.Tx) (*ResponseBroadcastTx, error) { err := mempoolReactor.BroadcastTx(tx) if err != nil { - return Receipt{}, fmt.Errorf("Error broadcasting transaction: %v", err) + return nil, fmt.Errorf("Error broadcasting transaction: %v", err) } txHash := merkle.HashFromBinary(tx) @@ -34,7 +34,7 @@ func BroadcastTx(tx types.Tx) (Receipt, error) { contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence)) } } - return Receipt{txHash, createsContract, contractAddr}, nil + return &ResponseBroadcastTx{Receipt{txHash, createsContract, contractAddr}}, nil } /* diff --git a/rpc/core/net.go b/rpc/core/net.go index b143d7271..64db01cfa 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -9,7 +9,7 @@ import ( //----------------------------------------------------------------------------- -func Status() ([]byte, string, []byte, uint, int64) { +func Status() (*ResponseStatus, error) { db := dbm.NewMemDB() genesisState := sm.MakeGenesisStateFromFile(db, config.App().GetString("GenesisFile")) genesisHash := genesisState.Hash() @@ -25,15 +25,15 @@ func Status() ([]byte, string, []byte, uint, int64) { latestBlockTime = latestBlockMeta.Header.Time.UnixNano() } - return genesisHash, config.App().GetString("Network"), latestBlockHash, latestHeight, latestBlockTime + return &ResponseStatus{genesisHash, config.App().GetString("Network"), latestBlockHash, latestHeight, latestBlockTime}, nil } //----------------------------------------------------------------------------- -func NetInfo() (int, bool, string) { +func NetInfo() (*ResponseNetInfo, error) { o, i, _ := p2pSwitch.NumPeers() numPeers := o + i listening := p2pSwitch.IsListening() network := config.App().GetString("Network") - return numPeers, listening, network + return &ResponseNetInfo{numPeers, listening, network}, nil } diff --git a/rpc/responses.go b/rpc/core/responses.go similarity index 93% rename from rpc/responses.go rename to rpc/core/responses.go index 11360c004..3e6e32e6a 100644 --- a/rpc/responses.go +++ b/rpc/core/responses.go @@ -1,8 +1,7 @@ -package rpc +package core import ( "github.com/tendermint/tendermint/account" - "github.com/tendermint/tendermint/rpc/core" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -32,7 +31,7 @@ type ResponseGetBlock struct { // curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=... type ResponseBroadcastTx struct { - Receipt core.Receipt + Receipt Receipt } type ResponseStatus struct { diff --git a/rpc/core/txs.go b/rpc/core/txs.go index 670984394..95b9fb552 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -8,7 +8,7 @@ import ( //----------------------------------------------------------------------------- -func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) { +func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ResponseSignTx, error) { // more checks? for i, privAccount := range privAccounts { @@ -40,5 +40,5 @@ func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) rebondTx := tx.(*types.RebondTx) rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519) } - return tx, nil + return &ResponseSignTx{tx}, nil } diff --git a/rpc/core/validators.go b/rpc/core/validators.go index b1892f69b..5d156156d 100644 --- a/rpc/core/validators.go +++ b/rpc/core/validators.go @@ -6,7 +6,7 @@ import ( //----------------------------------------------------------------------------- -func ListValidators() (uint, []*sm.Validator, []*sm.Validator) { +func ListValidators() (*ResponseListValidators, error) { var blockHeight uint var bondedValidators []*sm.Validator var unbondingValidators []*sm.Validator @@ -22,5 +22,5 @@ func ListValidators() (uint, []*sm.Validator, []*sm.Validator) { return false }) - return blockHeight, bondedValidators, unbondingValidators + return &ResponseListValidators{blockHeight, bondedValidators, unbondingValidators}, nil } diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index 6cb499b1d..225561360 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -16,16 +16,16 @@ import ( // (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, &ResponseStatus{}, []string{}), - "net_info": funcWrap(core.NetInfo, &ResponseNetInfo{}, []string{}), - "blockchain": funcWrap(core.BlockchainInfo, &ResponseBlockchainInfo{}, []string{"min_height", "max_height"}), - "get_block": funcWrap(core.GetBlock, &ResponseGetBlock{}, []string{"height"}), - "get_account": funcWrap(core.GetAccount, &ResponseGetAccount{}, []string{"address"}), - "list_validators": funcWrap(core.ListValidators, &ResponseListValidators{}, []string{}), - "broadcast_tx": funcWrap(core.BroadcastTx, &ResponseBroadcastTx{}, []string{"tx"}), - "list_accounts": funcWrap(core.ListAccounts, &ResponseListAccounts{}, []string{}), - "unsafe/gen_priv_account": funcWrap(core.GenPrivAccount, &ResponseGenPrivAccount{}, []string{}), - "unsafe/sign_tx": funcWrap(core.SignTx, &ResponseSignTx{}, []string{"tx", "privAccounts"}), + "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 @@ -34,16 +34,14 @@ type FuncWrapper struct { args []reflect.Type // type of each function arg returns []reflect.Type // type of each return arg argNames []string // name of each argument - response reflect.Value // response struct (to be filled with "returns") } -func funcWrap(f interface{}, response interface{}, args []string) *FuncWrapper { +func funcWrap(f interface{}, args []string) *FuncWrapper { return &FuncWrapper{ f: reflect.ValueOf(f), args: funcArgTypes(f), returns: funcReturnTypes(f), argNames: args, - response: reflect.ValueOf(response), } } @@ -170,6 +168,16 @@ func paramsToValues(funcInfo *FuncWrapper, params []string) ([]reflect.Value, er 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 @@ -190,7 +198,7 @@ func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interfac } 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) { diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go index a6a5fb2f2..3021584c3 100644 --- a/rpc/test/http_rpc_test.go +++ b/rpc/test/http_rpc_test.go @@ -8,7 +8,7 @@ import ( "github.com/tendermint/tendermint/binary" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/merkle" - "github.com/tendermint/tendermint/rpc" + "github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/types" "io/ioutil" "net/http" @@ -28,7 +28,7 @@ func TestHTTPStatus(t *testing.T) { } var status struct { Status string - Data rpc.ResponseStatus + Data core.ResponseStatus Error string } err = json.Unmarshal(body, &status) @@ -56,7 +56,7 @@ func TestHTTPGenPriv(t *testing.T) { } var status struct { Status string - Data rpc.ResponseGenPrivAccount + Data core.ResponseGenPrivAccount Error string } binary.ReadJSON(&status, body, &err) @@ -118,7 +118,7 @@ func TestHTTPBroadcastTx(t *testing.T) { var status struct { Status string - Data rpc.ResponseBroadcastTx + Data core.ResponseBroadcastTx Error string } requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status) diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go index d40f579f8..62f9c60bb 100644 --- a/rpc/test/json_rpc_test.go +++ b/rpc/test/json_rpc_test.go @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/merkle" "github.com/tendermint/tendermint/rpc" + "github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/types" "io/ioutil" "net/http" @@ -39,7 +40,7 @@ func TestJSONStatus(t *testing.T) { } status := new(struct { Status string - Data rpc.ResponseStatus + Data core.ResponseStatus Error string }) err = json.Unmarshal(body, status) @@ -77,7 +78,7 @@ func TestJSONGenPriv(t *testing.T) { } var status struct { Status string - Data rpc.ResponseGenPrivAccount + Data core.ResponseGenPrivAccount Error string } binary.ReadJSON(&status, body, &err) @@ -139,7 +140,7 @@ func TestJSONBroadcastTx(t *testing.T) { var status struct { Status string - Data rpc.ResponseBroadcastTx + Data core.ResponseBroadcastTx Error string } requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status) diff --git a/rpc/test/test.go b/rpc/test/test.go index 3057a8bf6..45a5f412a 100644 --- a/rpc/test/test.go +++ b/rpc/test/test.go @@ -12,6 +12,7 @@ import ( "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" @@ -101,7 +102,7 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account { } var status struct { Status string - Data rpc.ResponseGetAccount + Data core.ResponseGetAccount Error string } fmt.Println(string(body)) @@ -182,7 +183,7 @@ func signTx(t *testing.T, typ string, fromAddr, toAddr []byte, key [64]byte, amt var status struct { Status string - Data rpc.ResponseSignTx + Data core.ResponseSignTx Error string } requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &status) From 19a50c1229048d95a95c7dd0f390535ca156e037 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 29 Mar 2015 18:06:55 -0700 Subject: [PATCH 12/14] SetRPC/Pipe => core.Set... --- daemon/daemon.go | 8 ++++---- rpc/core/pipe.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 5b6c32221..03a4d26be 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -151,10 +151,10 @@ func (n *Node) DialSeed() { } func (n *Node) StartRpc() { - core.SetPipeBlockStore(n.blockStore) - core.SetPipeConsensusState(n.consensusState) - core.SetPipeMempoolReactor(n.mempoolReactor) - core.SetPipeSwitch(n.sw) + core.SetBlockStore(n.blockStore) + core.SetConsensusState(n.consensusState) + core.SetMempoolReactor(n.mempoolReactor) + core.SetSwitch(n.sw) rpc.StartHTTPServer() } diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index c82783c82..ca2d9db20 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -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 SetPipeConsensusState(cs *consensus.ConsensusState) { +func SetConsensusState(cs *consensus.ConsensusState) { consensusState = cs } -func SetPipeMempoolReactor(mr *mempl.MempoolReactor) { +func SetMempoolReactor(mr *mempl.MempoolReactor) { mempoolReactor = mr } -func SetPipeSwitch(sw *p2p.Switch) { +func SetSwitch(sw *p2p.Switch) { p2pSwitch = sw } From 5cb57d3eaa132fa44f00542f6440fe1cc8661d43 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Mar 2015 18:43:27 -0700 Subject: [PATCH 13/14] Use BlockCache for RPC/mempool and added TxId() TxId() uses signbytes --- .gitignore | 1 + mempool/mempool.go | 4 ++++ rpc/core/accounts.go | 4 ++-- rpc/core/mempool.go | 3 +-- rpc/test/json_rpc_test.go | 6 +++--- types/tx.go | 7 +++++++ 6 files changed, 18 insertions(+), 7 deletions(-) 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/mempool/mempool.go b/mempool/mempool.go index 28e3ab708..819198778 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -34,6 +34,10 @@ 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() diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index 84ffc2ee4..6949ab861 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -13,8 +13,8 @@ func GenPrivAccount() (*ResponseGenPrivAccount, error) { //----------------------------------------------------------------------------- func GetAccount(address []byte) (*ResponseGetAccount, error) { - state := consensusState.GetState() - return &ResponseGetAccount{state.GetAccount(address)}, nil + cache := mempoolReactor.Mempool.GetCache() + return &ResponseGetAccount{cache.GetAccount(address)}, nil } //----------------------------------------------------------------------------- diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 3ff1a85ea..070d6e40d 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -3,7 +3,6 @@ package core import ( "fmt" . "github.com/tendermint/tendermint/common" - "github.com/tendermint/tendermint/merkle" "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -24,7 +23,7 @@ func BroadcastTx(tx types.Tx) (*ResponseBroadcastTx, error) { return nil, fmt.Errorf("Error broadcasting transaction: %v", err) } - txHash := merkle.HashFromBinary(tx) + txHash := types.TxId(tx) var createsContract uint8 var contractAddr []byte // check if creates new contract diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go index 62f9c60bb..3b857f24e 100644 --- a/rpc/test/json_rpc_test.go +++ b/rpc/test/json_rpc_test.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/merkle" "github.com/tendermint/tendermint/rpc" "github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/types" @@ -161,8 +161,8 @@ func TestJSONBroadcastTx(t *testing.T) { } 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") + 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)) } } 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) +} From f234d0aafb2dc763ec8d1a337bdfa940bd79b6de Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 29 Mar 2015 19:05:29 -0700 Subject: [PATCH 14/14] Remove spurious flippage --- vm/vm.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vm/vm.go b/vm/vm.go index 52f3d0234..bc93ed3ea 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -495,13 +495,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case SLOAD: // 0x54 loc := stack.Pop() data := vm.appState.GetStorage(callee.Address, loc) - stack.Push(flipWord(data)) + stack.Push(data) dbg.Printf(" {0x%X : 0x%X}\n", loc, data) case SSTORE: // 0x55 loc, data := stack.Pop(), stack.Pop() - loc = flipWord(loc) - data = flipWord(data) vm.appState.SetStorage(callee.Address, loc, data) useGas(gas, GasStorageUpdate) dbg.Printf(" {0x%X : 0x%X}\n", loc, data)