Browse Source

copy entry on get, use strings for name/data, uint64 for expires

pull/83/head
Ethan Buchman 10 years ago
parent
commit
cff6bcfb31
11 changed files with 74 additions and 43 deletions
  1. +1
    -1
      rpc/core/names.go
  2. +3
    -3
      rpc/core_client/client_methods.go
  3. +2
    -2
      rpc/test/helpers.go
  4. +3
    -3
      rpc/test/tests.go
  5. +8
    -8
      state/block_cache.go
  6. +15
    -10
      state/execution.go
  7. +3
    -4
      state/state.go
  8. +13
    -4
      state/state_test.go
  9. +9
    -4
      types/names.go
  10. +15
    -2
      types/tx.go
  11. +2
    -2
      types/tx_utils.go

+ 1
- 1
rpc/core/names.go View File

@ -7,7 +7,7 @@ import (
) )
// XXX: need we be careful about rendering bytes as string or is that their problem ? // XXX: need we be careful about rendering bytes as string or is that their problem ?
func NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) {
func NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) {
st := consensusState.GetState() // performs a copy st := consensusState.GetState() // performs a copy
entry := st.GetNameRegEntry(name) entry := st.GetNameRegEntry(name)
if entry == nil { if entry == nil {


+ 3
- 3
rpc/core_client/client_methods.go View File

@ -28,7 +28,7 @@ type Client interface {
ListAccounts() (*ctypes.ResponseListAccounts, error) ListAccounts() (*ctypes.ResponseListAccounts, error)
ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error)
ListValidators() (*ctypes.ResponseListValidators, error) ListValidators() (*ctypes.ResponseListValidators, error)
NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error)
NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error)
NetInfo() (*ctypes.ResponseNetInfo, error) NetInfo() (*ctypes.ResponseNetInfo, error)
SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error)
Status() (*ctypes.ResponseStatus, error) Status() (*ctypes.ResponseStatus, error)
@ -454,7 +454,7 @@ func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) {
return response.Result, nil return response.Result, nil
} }
func (c *ClientHTTP) NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) {
func (c *ClientHTTP) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) {
values, err := argsToURLValues([]string{"name"}, name) values, err := argsToURLValues([]string{"name"}, name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -952,7 +952,7 @@ func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) {
return response.Result, nil return response.Result, nil
} }
func (c *ClientJSON) NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) {
func (c *ClientJSON) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) {
request := rpctypes.RPCRequest{ request := rpctypes.RPCRequest{
JSONRPC: "2.0", JSONRPC: "2.0",
Method: reverseFuncMap["NameRegEntry"], Method: reverseFuncMap["NameRegEntry"],


+ 2
- 2
rpc/test/helpers.go View File

@ -120,7 +120,7 @@ func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim,
return tx return tx
} }
func makeDefaultNameTx(t *testing.T, typ string, name, value []byte, amt, fee uint64) *types.NameTx {
func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee uint64) *types.NameTx {
nonce := getNonce(t, typ, user[0].Address) nonce := getNonce(t, typ, user[0].Address)
tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce) tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce)
tx.Sign(user[0]) tx.Sign(user[0])
@ -218,7 +218,7 @@ func callContract(t *testing.T, client cclient.Client, address, data, expected [
} }
// get the namereg entry // get the namereg entry
func getNameRegEntry(t *testing.T, typ string, name []byte) *types.NameRegEntry {
func getNameRegEntry(t *testing.T, typ string, name string) *types.NameRegEntry {
client := clients[typ] client := clients[typ]
r, err := client.NameRegEntry(name) r, err := client.NameRegEntry(name)
if err != nil { if err != nil {


+ 3
- 3
rpc/test/tests.go View File

@ -202,8 +202,8 @@ func testNameReg(t *testing.T, typ string) {
amt, fee := uint64(6969), uint64(1000) amt, fee := uint64(6969), uint64(1000)
// since entries ought to be unique and these run against different clients, we append the typ // since entries ought to be unique and these run against different clients, we append the typ
name := []byte("ye-old-domain-name-" + typ)
data := []byte("these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need?")
name := "ye-old-domain-name-" + typ
data := "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need?"
tx := makeDefaultNameTx(t, typ, name, data, amt, fee) tx := makeDefaultNameTx(t, typ, name, data, amt, fee)
broadcastTx(t, typ, tx) broadcastTx(t, typ, tx)
@ -215,7 +215,7 @@ func testNameReg(t *testing.T, typ string) {
entry := getNameRegEntry(t, typ, name) entry := getNameRegEntry(t, typ, name)
if bytes.Compare(entry.Data, data) != 0 {
if entry.Data != data {
t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data))
} }


+ 8
- 8
state/block_cache.go View File

@ -128,15 +128,15 @@ func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) {
//------------------------------------- //-------------------------------------
// BlockCache.names // BlockCache.names
func (cache *BlockCache) GetNameRegEntry(name []byte) *types.NameRegEntry {
entry, removed, _ := cache.names[string(name)].unpack()
func (cache *BlockCache) GetNameRegEntry(name string) *types.NameRegEntry {
entry, removed, _ := cache.names[name].unpack()
if removed { if removed {
return nil return nil
} else if entry != nil { } else if entry != nil {
return entry return entry
} else { } else {
entry = cache.backend.GetNameRegEntry(name) entry = cache.backend.GetNameRegEntry(name)
cache.names[string(name)] = nameInfo{entry, false, false}
cache.names[name] = nameInfo{entry, false, false}
return entry return entry
} }
} }
@ -144,22 +144,22 @@ func (cache *BlockCache) GetNameRegEntry(name []byte) *types.NameRegEntry {
func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) { func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) {
name := entry.Name name := entry.Name
// SANITY CHECK // SANITY CHECK
_, removed, _ := cache.names[string(name)].unpack()
_, removed, _ := cache.names[name].unpack()
if removed { if removed {
panic("UpdateNameRegEntry on a removed name") panic("UpdateNameRegEntry on a removed name")
} }
// SANITY CHECK END // SANITY CHECK END
cache.names[string(name)] = nameInfo{entry, false, true}
cache.names[name] = nameInfo{entry, false, true}
} }
func (cache *BlockCache) RemoveNameRegEntry(name []byte) {
func (cache *BlockCache) RemoveNameRegEntry(name string) {
// SANITY CHECK // SANITY CHECK
_, removed, _ := cache.names[string(name)].unpack()
_, removed, _ := cache.names[name].unpack()
if removed { if removed {
panic("RemoveNameRegEntry on a removed entry") panic("RemoveNameRegEntry on a removed entry")
} }
// SANITY CHECK END // SANITY CHECK END
cache.names[string(name)] = nameInfo{nil, true, false}
cache.names[name] = nameInfo{nil, true, false}
} }
// BlockCache.names // BlockCache.names


+ 15
- 10
state/execution.go View File

@ -504,13 +504,18 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
return types.ErrTxInsufficientFunds return types.ErrTxInsufficientFunds
} }
// validate the input strings
if !tx.Validate() {
log.Debug(Fmt("Invalid characters present in NameTx name (%s) or data (%s)", tx.Name, tx.Data))
return types.ErrTxInvalidString
}
value := tx.Input.Amount - tx.Fee value := tx.Input.Amount - tx.Fee
// let's say cost of a name for one block is len(data) + 32 // 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))
costPerBlock := uint64(len(tx.Data) + 32)
expiresIn := value / uint64(costPerBlock)
lastBlockHeight := uint64(_s.LastBlockHeight)
// check if the name exists // check if the name exists
entry := blockCache.GetNameRegEntry(tx.Name) entry := blockCache.GetNameRegEntry(tx.Name)
@ -518,7 +523,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
if entry != nil { if entry != nil {
var expired bool var expired bool
// if the entry already exists, and hasn't expired, we must be owner // if the entry already exists, and hasn't expired, we must be owner
if entry.Expires > _s.LastBlockHeight {
if entry.Expires > lastBlockHeight {
// ensure we are owner // ensure we are owner
if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { 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)) log.Debug(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name))
@ -537,14 +542,14 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
// update the entry by bumping the expiry // update the entry by bumping the expiry
// and changing the data // and changing the data
if expired { if expired {
entry.Expires = _s.LastBlockHeight + expiresIn
entry.Expires = lastBlockHeight + expiresIn
} else { } else {
// since the size of the data may have changed // since the size of the data may have changed
// we use the total amount of "credit" // we use the total amount of "credit"
credit := uint64((entry.Expires - _s.LastBlockHeight) * uint(len(entry.Data)))
credit := (entry.Expires - lastBlockHeight) * uint64(len(entry.Data))
credit += value credit += value
expiresIn = uint(credit) / costPerBlock
entry.Expires = _s.LastBlockHeight + expiresIn
expiresIn = credit / costPerBlock
entry.Expires = lastBlockHeight + expiresIn
} }
entry.Data = tx.Data entry.Data = tx.Data
blockCache.UpdateNameRegEntry(entry) blockCache.UpdateNameRegEntry(entry)
@ -558,7 +563,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
Name: tx.Name, Name: tx.Name,
Owner: tx.Input.Address, Owner: tx.Input.Address,
Data: tx.Data, Data: tx.Data,
Expires: _s.LastBlockHeight + expiresIn,
Expires: lastBlockHeight + expiresIn,
} }
blockCache.UpdateNameRegEntry(entry) blockCache.UpdateNameRegEntry(entry)
} }


+ 3
- 4
state/state.go View File

@ -283,21 +283,20 @@ func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) {
//------------------------------------- //-------------------------------------
// State.nameReg // State.nameReg
func (s *State) GetNameRegEntry(name []byte) *types.NameRegEntry {
func (s *State) GetNameRegEntry(name string) *types.NameRegEntry {
_, value := s.nameReg.Get(name) _, value := s.nameReg.Get(name)
if value == nil { if value == nil {
return nil return nil
} }
entry := value.(*types.NameRegEntry) entry := value.(*types.NameRegEntry)
// XXX: do we need to copy?
return entry
return entry.Copy()
} }
func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool { func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool {
return s.nameReg.Set(entry.Name, entry) return s.nameReg.Set(entry.Name, entry)
} }
func (s *State) RemoveNameRegEntry(name []byte) bool {
func (s *State) RemoveNameRegEntry(name string) bool {
_, removed := s.nameReg.Remove(name) _, removed := s.nameReg.Remove(name)
return removed return removed
} }


+ 13
- 4
state/state_test.go View File

@ -304,8 +304,8 @@ func TestTxs(t *testing.T) {
// NameTx. // NameTx.
{ {
entryName := []byte("satoshi")
entryData := []byte(`
entryName := "satoshi"
entryData := `
A purely peer-to-peer version of electronic cash would allow online 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 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 financial institution. Digital signatures provide part of the solution, but the main
@ -319,7 +319,7 @@ long as a majority of CPU power is controlled by nodes that are not cooperating
attack the network, they'll generate the longest chain and outpace attackers. The 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 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 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 `)
proof-of-work chain as proof of what happened while they were gone `
entryAmount := uint64(10000) entryAmount := uint64(10000)
state := state.Copy() state := state.Copy()
@ -348,9 +348,18 @@ proof-of-work chain as proof of what happened while they were gone `)
if entry == nil { if entry == nil {
t.Errorf("Expected an entry but got nil") t.Errorf("Expected an entry but got nil")
} }
if bytes.Compare(entry.Data, entryData) != 0 {
if entry.Data != entryData {
t.Errorf("Wrong data stored") t.Errorf("Wrong data stored")
} }
// test a bad string
tx.Data = string([]byte{0, 1, 2, 3, 127, 128, 129, 200, 251})
tx.Input.Sequence += 1
tx.Input.Signature = privAccounts[0].Sign(tx)
err = execTxWithState(state, tx, true)
if err != types.ErrTxInvalidString {
t.Errorf("Expected invalid string error. Got: %s", err.Error())
}
} }
// BondTx. // BondTx.


+ 9
- 4
types/names.go View File

@ -1,8 +1,13 @@
package types package types
type NameRegEntry struct { 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
Name string `json:"name"` // registered name for the entry
Owner []byte `json:"owner"` // address that created the entry
Data string `json:"data"` // binary encoded byte array
Expires uint64 `json:"expires"` // block at which this entry expires
}
func (entry *NameRegEntry) Copy() *NameRegEntry {
entryCopy := *entry
return &entryCopy
} }

+ 15
- 2
types/tx.go View File

@ -3,6 +3,7 @@ package types
import ( import (
"errors" "errors"
"io" "io"
"regexp"
"github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary" "github.com/tendermint/tendermint/binary"
@ -18,6 +19,7 @@ var (
ErrTxUnknownPubKey = errors.New("Error unknown pubkey") ErrTxUnknownPubKey = errors.New("Error unknown pubkey")
ErrTxInvalidPubKey = errors.New("Error invalid pubkey") ErrTxInvalidPubKey = errors.New("Error invalid pubkey")
ErrTxInvalidSignature = errors.New("Error invalid signature") ErrTxInvalidSignature = errors.New("Error invalid signature")
ErrTxInvalidString = errors.New("Error invalid string")
) )
type ErrTxInvalidSequence struct { type ErrTxInvalidSequence struct {
@ -183,8 +185,8 @@ func (tx *CallTx) String() string {
type NameTx struct { type NameTx struct {
Input *TxInput `json:"input"` Input *TxInput `json:"input"`
Name []byte `json:"name"`
Data []byte `json:"data"`
Name string `json:"name"`
Data string `json:"data"`
Fee uint64 `json:"fee"` Fee uint64 `json:"fee"`
} }
@ -197,6 +199,17 @@ func (tx *NameTx) WriteSignBytes(w io.Writer, n *int64, err *error) {
binary.WriteTo([]byte(`}]}`), w, n, err) binary.WriteTo([]byte(`}]}`), w, n, err)
} }
// alphanum, underscore, forward slash
var regexpAlphaNum, _ = regexp.Compile("^[a-zA-Z0-9_/]*$")
// anything you might find in a json
var regexpJSON, err = regexp.Compile(`^[a-zA-Z0-9_/ \-"':,\n\t.{}()\[\]]*$`)
func (tx *NameTx) Validate() bool {
// Name should be alphanum and Data should be like JSON
return regexpAlphaNum.Match([]byte(tx.Name)) && regexpJSON.Match([]byte(tx.Data))
}
func (tx *NameTx) String() string { func (tx *NameTx) String() string {
return Fmt("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) return Fmt("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data)
} }


+ 2
- 2
types/tx_utils.go View File

@ -106,7 +106,7 @@ func (tx *CallTx) Sign(chainID string, privAccount *account.PrivAccount) {
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// NameTx interface for creating tx // NameTx interface for creating tx
func NewNameTx(st AccountGetter, from account.PubKey, name, data []byte, amt, fee uint64) (*NameTx, error) {
func NewNameTx(st AccountGetter, from account.PubKey, name, data string, amt, fee uint64) (*NameTx, error) {
addr := from.Address() addr := from.Address()
acc := st.GetAccount(addr) acc := st.GetAccount(addr)
if acc == nil { if acc == nil {
@ -117,7 +117,7 @@ func NewNameTx(st AccountGetter, from account.PubKey, name, data []byte, amt, fe
return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil
} }
func NewNameTxWithNonce(from account.PubKey, name, data []byte, amt, fee, nonce uint64) *NameTx {
func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee, nonce uint64) *NameTx {
addr := from.Address() addr := from.Address()
input := &TxInput{ input := &TxInput{
Address: addr, Address: addr,


Loading…
Cancel
Save