Browse Source

name reg

pull/83/head
Ethan Buchman 9 years ago
parent
commit
8631d5085e
7 changed files with 348 additions and 1 deletions
  1. +78
    -0
      state/block_cache.go
  2. +97
    -1
      state/execution.go
  3. +6
    -0
      state/genesis.go
  4. +49
    -0
      state/state.go
  5. +85
    -0
      state/state_test.go
  6. +8
    -0
      types/names.go
  7. +25
    -0
      types/tx.go

+ 78
- 0
state/block_cache.go View File

@ -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
}

+ 97
- 1
state/execution.go View File

@ -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 {


+ 6
- 0
state/genesis.go View File

@ -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,
}
}

+ 49
- 0
state/state.go View File

@ -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) {


+ 85
- 0
state/state_test.go View File

@ -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()


+ 8
- 0
types/names.go View File

@ -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
}

+ 25
- 0
types/tx.go View File

@ -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"`


Loading…
Cancel
Save