mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-29 12:41:44 +00:00
name reg
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
types/names.go
Normal file
8
types/names.go
Normal 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
types/tx.go
25
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"`
|
||||
|
Reference in New Issue
Block a user