diff --git a/state/block_cache.go b/state/block_cache.go index 2eda83fa9..102d26253 100644 --- a/state/block_cache.go +++ b/state/block_cache.go @@ -9,6 +9,7 @@ import ( . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/merkle" + "github.com/tendermint/tendermint/types" ) func makeStorage(db dbm.DB, root []byte) merkle.Tree { @@ -27,6 +28,7 @@ type BlockCache struct { backend *State accounts map[string]accountInfo storages map[Tuple256]storageInfo + names map[string]nameInfo } func NewBlockCache(backend *State) *BlockCache { @@ -35,6 +37,7 @@ func NewBlockCache(backend *State) *BlockCache { backend: backend, accounts: make(map[string]accountInfo), storages: make(map[Tuple256]storageInfo), + names: make(map[string]nameInfo), } } @@ -123,6 +126,44 @@ func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { // BlockCache.storage //------------------------------------- +// BlockCache.names + +func (cache *BlockCache) GetNameRegEntry(name []byte) *types.NameRegEntry { + entry, removed, _ := cache.names[string(name)].unpack() + if removed { + return nil + } else if entry != nil { + return entry + } else { + entry = cache.backend.GetNameRegEntry(name) + cache.names[string(name)] = nameInfo{entry, false, false} + return entry + } +} + +func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) { + name := entry.Name + // SANITY CHECK + _, removed, _ := cache.names[string(name)].unpack() + if removed { + panic("UpdateNameRegEntry on a removed name") + } + // SANITY CHECK END + cache.names[string(name)] = nameInfo{entry, false, true} +} + +func (cache *BlockCache) RemoveNameRegEntry(name []byte) { + // SANITY CHECK + _, removed, _ := cache.names[string(name)].unpack() + if removed { + panic("RemoveNameRegEntry on a removed entry") + } + // SANITY CHECK END + cache.names[string(name)] = nameInfo{nil, true, false} +} + +// BlockCache.names +//------------------------------------- // CONTRACT the updates are in deterministic order. func (cache *BlockCache) Sync() { @@ -202,6 +243,33 @@ func (cache *BlockCache) Sync() { } } + // Determine order for names + // note names may be of any length less than some limit + // and are arbitrary byte arrays... + nameStrs := []string{} + for nameStr := range cache.names { + nameStrs = append(nameStrs, nameStr) + } + sort.Strings(nameStrs) + + // Update or delete names. + for _, nameStr := range nameStrs { + entry, removed, dirty := cache.names[nameStr].unpack() + if removed { + removed := cache.backend.RemoveNameRegEntry(entry.Name) + if !removed { + panic(Fmt("Could not remove namereg entry to be removed: %X", entry.Name)) + } + } else { + if entry == nil { + continue + } + if dirty { + cache.backend.UpdateNameRegEntry(entry) + } + } + } + } //----------------------------------------------------------------------------- @@ -225,3 +293,13 @@ type storageInfo struct { func (stjInfo storageInfo) unpack() (Word256, bool) { return stjInfo.value, stjInfo.dirty } + +type nameInfo struct { + name *types.NameRegEntry + removed bool + dirty bool +} + +func (nInfo nameInfo) unpack() (*types.NameRegEntry, bool, bool) { + return nInfo.name, nInfo.removed, nInfo.dirty +} diff --git a/state/execution.go b/state/execution.go index e46a2150b..a9f3ca823 100644 --- a/state/execution.go +++ b/state/execution.go @@ -296,7 +296,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // TODO: do something with fees fees := uint64(0) - _s := blockCache.State() // hack to access validators and event switch. + _s := blockCache.State() // hack to access validators and block height // Exec tx switch tx := tx_.(type) { @@ -478,6 +478,102 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return nil + case *types.NameTx: + var inAcc *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 + } + // fee is in addition to the amount which is used to determine the TTL + 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 + } + + value := tx.Input.Amount - tx.Fee + + // let's say cost of a name for one block is len(data) + 32 + // TODO: the casting is dangerous and things can overflow (below)! + // we should make LastBlockHeight a uint64 + costPerBlock := uint(len(tx.Data) + 32) + expiresIn := uint(value / uint64(costPerBlock)) + + // check if the name exists + entry := blockCache.GetNameRegEntry(tx.Name) + + if entry != nil { + var expired bool + // if the entry already exists, and hasn't expired, we must be owner + if entry.Expires > _s.LastBlockHeight { + // ensure we are owner + if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { + log.Debug(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) + return types.ErrTxInvalidAddress // (?) + } + } else { + expired = true + } + + // no value and empty data means delete the entry + if value == 0 && len(tx.Data) == 0 { + // maybe we reward you for telling us we can delete this crap + // (owners if not expired, anyone if expired) + blockCache.RemoveNameRegEntry(entry.Name) + } else { + // update the entry by bumping the expiry + // and changing the data + if expired { + entry.Expires = _s.LastBlockHeight + expiresIn + } else { + // since the size of the data may have changed + // we use the total amount of "credit" + credit := uint64((entry.Expires - _s.LastBlockHeight) * uint(len(entry.Data))) + credit += value + expiresIn = uint(credit) / costPerBlock + entry.Expires = _s.LastBlockHeight + expiresIn + } + entry.Data = tx.Data + blockCache.UpdateNameRegEntry(entry) + } + } else { + if expiresIn < 5 { + return fmt.Errorf("Names must be registered for at least 5 blocks") + } + // entry does not exist, so create it + entry = &types.NameRegEntry{ + Name: tx.Name, + Owner: tx.Input.Address, + Data: tx.Data, + Expires: _s.LastBlockHeight + expiresIn, + } + blockCache.UpdateNameRegEntry(entry) + } + + // TODO: something with the value sent? + + // Good! + inAcc.Sequence += 1 + inAcc.Balance -= value + blockCache.UpdateAccount(inAcc) + + // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? + + return nil + case *types.BondTx: valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) if valInfo != nil { diff --git a/state/genesis.go b/state/genesis.go index 691597ce0..2024e2117 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -100,9 +100,14 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { } } + // Make namereg tree + nameReg := merkle.NewIAVLTree(binary.BasicCodec, NameRegCodec, 0, db) + // TODO: add names to genesis.json + // IAVLTrees must be persisted before copy operations. accounts.Save() validatorInfos.Save() + nameReg.Save() return &State{ DB: db, @@ -116,5 +121,6 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { UnbondingValidators: NewValidatorSet(nil), accounts: accounts, validatorInfos: validatorInfos, + nameReg: nameReg, } } diff --git a/state/state.go b/state/state.go index 13bd7d74d..e5bc863ea 100644 --- a/state/state.go +++ b/state/state.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "io" "time" "github.com/tendermint/tendermint/account" @@ -36,6 +37,7 @@ type State struct { UnbondingValidators *ValidatorSet accounts merkle.Tree // Shouldn't be accessed directly. validatorInfos merkle.Tree // Shouldn't be accessed directly. + nameReg merkle.Tree // Shouldn't be accessed directly. evc events.Fireable // typically an events.EventCache } @@ -61,6 +63,9 @@ func LoadState(db dbm.DB) *State { validatorInfosHash := binary.ReadByteSlice(r, n, err) s.validatorInfos = merkle.NewIAVLTree(binary.BasicCodec, ValidatorInfoCodec, 0, db) s.validatorInfos.Load(validatorInfosHash) + nameRegHash := binary.ReadByteSlice(r, n, err) + s.nameReg = merkle.NewIAVLTree(binary.BasicCodec, NameRegCodec, 0, db) + s.nameReg.Load(nameRegHash) if *err != nil { panic(*err) } @@ -72,6 +77,7 @@ func LoadState(db dbm.DB) *State { func (s *State) Save() { s.accounts.Save() s.validatorInfos.Save() + s.nameReg.Save() buf, n, err := new(bytes.Buffer), new(int64), new(error) binary.WriteString(s.ChainID, buf, n, err) binary.WriteUvarint(s.LastBlockHeight, buf, n, err) @@ -83,6 +89,7 @@ func (s *State) Save() { binary.WriteBinary(s.UnbondingValidators, buf, n, err) binary.WriteByteSlice(s.accounts.Hash(), buf, n, err) binary.WriteByteSlice(s.validatorInfos.Hash(), buf, n, err) + binary.WriteByteSlice(s.nameReg.Hash(), buf, n, err) if *err != nil { panic(*err) } @@ -105,6 +112,7 @@ func (s *State) Copy() *State { UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily. accounts: s.accounts.Copy(), validatorInfos: s.validatorInfos.Copy(), + nameReg: s.nameReg.Copy(), evc: nil, } } @@ -116,6 +124,7 @@ func (s *State) Hash() []byte { s.UnbondingValidators, s.accounts, s.validatorInfos, + s.nameReg, } return merkle.HashFromHashables(hashables) } @@ -272,6 +281,46 @@ func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { // State.storage //------------------------------------- +// State.nameReg + +func (s *State) GetNameRegEntry(name []byte) *types.NameRegEntry { + _, value := s.nameReg.Get(name) + if value == nil { + return nil + } + entry := value.(*types.NameRegEntry) + // XXX: do we need to copy? + return entry +} + +func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool { + return s.nameReg.Set(entry.Name, entry) +} + +func (s *State) RemoveNameRegEntry(name []byte) bool { + _, removed := s.nameReg.Remove(name) + return removed +} + +func (s *State) GetNames() merkle.Tree { + return s.nameReg.Copy() +} + +func NameRegEncoder(o interface{}, w io.Writer, n *int64, err *error) { + binary.WriteBinary(o.(*types.NameRegEntry), w, n, err) +} + +func NameRegDecoder(r io.Reader, n *int64, err *error) interface{} { + return binary.ReadBinary(&types.NameRegEntry{}, r, n, err) +} + +var NameRegCodec = binary.Codec{ + Encode: NameRegEncoder, + Decode: NameRegDecoder, +} + +// State.nameReg +//------------------------------------- // Implements events.Eventable. Typically uses events.EventCache func (s *State) SetFireable(evc events.Fireable) { diff --git a/state/state_test.go b/state/state_test.go index a2fb2b78a..6804ca0c4 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -268,6 +268,91 @@ func TestTxs(t *testing.T) { } } + // CallTx. + { + state := state.Copy() + newAcc1 := state.GetAccount(acc1.Address) + newAcc1.Code = []byte{0x60} + state.UpdateAccount(newAcc1) + tx := &types.CallTx{ + Input: &types.TxInput{ + Address: acc0.Address, + Amount: 1, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + Address: acc1.Address, + GasLimit: 10, + } + + tx.Input.Signature = privAccounts[0].Sign(tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-1 != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-1, newAcc0.Balance) + } + newAcc1 = state.GetAccount(acc1.Address) + if acc1.Balance+1 != newAcc1.Balance { + t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", + acc1.Balance+1, newAcc1.Balance) + } + } + + // NameTx. + { + entryName := []byte("satoshi") + entryData := []byte(` +A purely peer-to-peer version of electronic cash would allow online +payments to be sent directly from one party to another without going through a +financial institution. Digital signatures provide part of the solution, but the main +benefits are lost if a trusted third party is still required to prevent double-spending. +We propose a solution to the double-spending problem using a peer-to-peer network. +The network timestamps transactions by hashing them into an ongoing chain of +hash-based proof-of-work, forming a record that cannot be changed without redoing +the proof-of-work. The longest chain not only serves as proof of the sequence of +events witnessed, but proof that it came from the largest pool of CPU power. As +long as a majority of CPU power is controlled by nodes that are not cooperating to +attack the network, they'll generate the longest chain and outpace attackers. The +network itself requires minimal structure. Messages are broadcast on a best effort +basis, and nodes can leave and rejoin the network at will, accepting the longest +proof-of-work chain as proof of what happened while they were gone `) + entryAmount := uint64(10000) + + state := state.Copy() + tx := &types.NameTx{ + Input: &types.TxInput{ + Address: acc0.Address, + Amount: entryAmount, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + Name: entryName, + Data: entryData, + } + + tx.Input.Signature = privAccounts[0].Sign(tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-entryAmount != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-entryAmount, newAcc0.Balance) + } + entry := state.GetNameRegEntry(entryName) + if entry == nil { + t.Errorf("Expected an entry but got nil") + } + if bytes.Compare(entry.Data, entryData) != 0 { + t.Errorf("Wrong data stored") + } + } + // BondTx. { state := state.Copy() diff --git a/types/names.go b/types/names.go new file mode 100644 index 000000000..2fbb16da8 --- /dev/null +++ b/types/names.go @@ -0,0 +1,8 @@ +package types + +type NameRegEntry struct { + Name []byte // registered name for the entry + Owner []byte // address that created the entry + Data []byte // binary encoded byte array + Expires uint // block at which this entry expires +} diff --git a/types/tx.go b/types/tx.go index 2d365881f..06f90d796 100644 --- a/types/tx.go +++ b/types/tx.go @@ -35,6 +35,7 @@ Tx (Transaction) is an atomic operation on the ledger state. Account Txs: - SendTx Send coins to address - CallTx Send a msg to a contract that runs in the vm + - NameTx Store some value under a name in the global namereg Validation Txs: - BondTx New validator posts a bond @@ -50,6 +51,7 @@ const ( // Account transactions TxTypeSend = byte(0x01) TxTypeCall = byte(0x02) + TxTypeName = byte(0x03) // Validation transactions TxTypeBond = byte(0x11) @@ -63,6 +65,7 @@ var _ = binary.RegisterInterface( struct{ Tx }{}, binary.ConcreteType{&SendTx{}, TxTypeSend}, binary.ConcreteType{&CallTx{}, TxTypeCall}, + binary.ConcreteType{&NameTx{}, TxTypeName}, binary.ConcreteType{&BondTx{}, TxTypeBond}, binary.ConcreteType{&UnbondTx{}, TxTypeUnbond}, binary.ConcreteType{&RebondTx{}, TxTypeRebond}, @@ -178,6 +181,28 @@ func (tx *CallTx) String() string { //----------------------------------------------------------------------------- +type NameTx struct { + Input *TxInput `json:"input"` + Name []byte `json:"name"` + Data []byte `json:"data"` + Fee uint64 `json:"fee"` +} + +func (tx *NameTx) WriteSignBytes(w io.Writer, n *int64, err *error) { + // We hex encode the network name so we don't deal with escaping issues. + binary.WriteTo([]byte(Fmt(`{"network":"%X"`, config.GetString("network"))), w, n, err) + binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"name":"%s","data":"%s"`, TxTypeName, tx.Name, tx.Data)), w, n, err) + binary.WriteTo([]byte(Fmt(`,"fee":%v,"input":`, tx.Fee)), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + binary.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *NameTx) String() string { + return Fmt("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) +} + +//----------------------------------------------------------------------------- + type BondTx struct { PubKey account.PubKeyEd25519 `json:"pub_key"` Signature account.SignatureEd25519 `json:"signature"`