mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
implementing ExecTx...
This commit is contained in:
parent
0c206aa748
commit
18e2d4bf48
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Signable interface {
|
type Signable interface {
|
||||||
GenDocument() []byte
|
Binary
|
||||||
GetSignature() Signature
|
GetSignature() Signature
|
||||||
SetSignature(Signature)
|
SetSignature(Signature)
|
||||||
}
|
}
|
||||||
|
85
blocks/tx.go
85
blocks/tx.go
@ -19,59 +19,56 @@ Validation Txs:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type Tx interface {
|
type Tx interface {
|
||||||
Type() byte
|
Signable
|
||||||
//IsValidation() bool
|
GetSequence() uint64
|
||||||
Binary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Account transactions
|
// Account transactions
|
||||||
txTypeSend = byte(0x01)
|
TxTypeSend = byte(0x01)
|
||||||
txTypeName = byte(0x02)
|
TxTypeName = byte(0x02)
|
||||||
|
|
||||||
// Validation transactions
|
// Validation transactions
|
||||||
txTypeBond = byte(0x11)
|
TxTypeBond = byte(0x11)
|
||||||
txTypeUnbond = byte(0x12)
|
TxTypeUnbond = byte(0x12)
|
||||||
txTypeTimeout = byte(0x13)
|
TxTypeTimeout = byte(0x13)
|
||||||
txTypeDupeout = byte(0x14)
|
TxTypeDupeout = byte(0x14)
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReadTx(r io.Reader, n *int64, err *error) Tx {
|
func ReadTx(r io.Reader, n *int64, err *error) Tx {
|
||||||
switch t := ReadByte(r, n, err); t {
|
switch t := ReadByte(r, n, err); t {
|
||||||
case txTypeSend:
|
case TxTypeSend:
|
||||||
return &SendTx{
|
return &SendTx{
|
||||||
BaseTx: ReadBaseTx(r, n, err),
|
BaseTx: ReadBaseTx(r, n, err),
|
||||||
Fee: ReadUInt64(r, n, err),
|
Fee: ReadUInt64(r, n, err),
|
||||||
To: ReadUInt64(r, n, err),
|
To: ReadUInt64(r, n, err),
|
||||||
Amount: ReadUInt64(r, n, err),
|
Amount: ReadUInt64(r, n, err),
|
||||||
}
|
}
|
||||||
case txTypeName:
|
case TxTypeName:
|
||||||
return &NameTx{
|
return &NameTx{
|
||||||
BaseTx: ReadBaseTx(r, n, err),
|
BaseTx: ReadBaseTx(r, n, err),
|
||||||
Fee: ReadUInt64(r, n, err),
|
Fee: ReadUInt64(r, n, err),
|
||||||
Name: ReadString(r, n, err),
|
Name: ReadString(r, n, err),
|
||||||
PubKey: ReadByteSlice(r, n, err),
|
PubKey: ReadByteSlice(r, n, err),
|
||||||
}
|
}
|
||||||
case txTypeBond:
|
case TxTypeBond:
|
||||||
return &BondTx{
|
return &BondTx{
|
||||||
BaseTx: ReadBaseTx(r, n, err),
|
BaseTx: ReadBaseTx(r, n, err),
|
||||||
Fee: ReadUInt64(r, n, err),
|
Fee: ReadUInt64(r, n, err),
|
||||||
UnbondTo: ReadUInt64(r, n, err),
|
UnbondTo: ReadUInt64(r, n, err),
|
||||||
Amount: ReadUInt64(r, n, err),
|
|
||||||
}
|
}
|
||||||
case txTypeUnbond:
|
case TxTypeUnbond:
|
||||||
return &UnbondTx{
|
return &UnbondTx{
|
||||||
BaseTx: ReadBaseTx(r, n, err),
|
BaseTx: ReadBaseTx(r, n, err),
|
||||||
Fee: ReadUInt64(r, n, err),
|
Fee: ReadUInt64(r, n, err),
|
||||||
Amount: ReadUInt64(r, n, err),
|
|
||||||
}
|
}
|
||||||
case txTypeTimeout:
|
case TxTypeTimeout:
|
||||||
return &TimeoutTx{
|
return &TimeoutTx{
|
||||||
BaseTx: ReadBaseTx(r, n, err),
|
BaseTx: ReadBaseTx(r, n, err),
|
||||||
AccountId: ReadUInt64(r, n, err),
|
AccountId: ReadUInt64(r, n, err),
|
||||||
Penalty: ReadUInt64(r, n, err),
|
Penalty: ReadUInt64(r, n, err),
|
||||||
}
|
}
|
||||||
case txTypeDupeout:
|
case TxTypeDupeout:
|
||||||
return &DupeoutTx{
|
return &DupeoutTx{
|
||||||
BaseTx: ReadBaseTx(r, n, err),
|
BaseTx: ReadBaseTx(r, n, err),
|
||||||
VoteA: *ReadVote(r, n, err),
|
VoteA: *ReadVote(r, n, err),
|
||||||
@ -103,6 +100,10 @@ func (tx BaseTx) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *BaseTx) GetSequence() uint64 {
|
||||||
|
return tx.Sequence
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *BaseTx) GetSignature() Signature {
|
func (tx *BaseTx) GetSignature() Signature {
|
||||||
return tx.Signature
|
return tx.Signature
|
||||||
}
|
}
|
||||||
@ -120,12 +121,8 @@ type SendTx struct {
|
|||||||
Amount uint64
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *SendTx) Type() byte {
|
|
||||||
return txTypeSend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) {
|
func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteByte(w, tx.Type(), &n, &err)
|
WriteByte(w, TxTypeSend, &n, &err)
|
||||||
WriteBinary(w, tx.BaseTx, &n, &err)
|
WriteBinary(w, tx.BaseTx, &n, &err)
|
||||||
WriteUInt64(w, tx.Fee, &n, &err)
|
WriteUInt64(w, tx.Fee, &n, &err)
|
||||||
WriteUInt64(w, tx.To, &n, &err)
|
WriteUInt64(w, tx.To, &n, &err)
|
||||||
@ -142,12 +139,8 @@ type NameTx struct {
|
|||||||
PubKey []byte
|
PubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *NameTx) Type() byte {
|
|
||||||
return txTypeName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) {
|
func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteByte(w, tx.Type(), &n, &err)
|
WriteByte(w, TxTypeName, &n, &err)
|
||||||
WriteBinary(w, tx.BaseTx, &n, &err)
|
WriteBinary(w, tx.BaseTx, &n, &err)
|
||||||
WriteUInt64(w, tx.Fee, &n, &err)
|
WriteUInt64(w, tx.Fee, &n, &err)
|
||||||
WriteString(w, tx.Name, &n, &err)
|
WriteString(w, tx.Name, &n, &err)
|
||||||
@ -161,19 +154,13 @@ type BondTx struct {
|
|||||||
BaseTx
|
BaseTx
|
||||||
Fee uint64
|
Fee uint64
|
||||||
UnbondTo uint64
|
UnbondTo uint64
|
||||||
Amount uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *BondTx) Type() byte {
|
|
||||||
return txTypeBond
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) {
|
func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteByte(w, tx.Type(), &n, &err)
|
WriteByte(w, TxTypeBond, &n, &err)
|
||||||
WriteBinary(w, tx.BaseTx, &n, &err)
|
WriteBinary(w, tx.BaseTx, &n, &err)
|
||||||
WriteUInt64(w, tx.Fee, &n, &err)
|
WriteUInt64(w, tx.Fee, &n, &err)
|
||||||
WriteUInt64(w, tx.UnbondTo, &n, &err)
|
WriteUInt64(w, tx.UnbondTo, &n, &err)
|
||||||
WriteUInt64(w, tx.Amount, &n, &err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,19 +168,13 @@ func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
|
|
||||||
type UnbondTx struct {
|
type UnbondTx struct {
|
||||||
BaseTx
|
BaseTx
|
||||||
Fee uint64
|
Fee uint64
|
||||||
Amount uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *UnbondTx) Type() byte {
|
|
||||||
return txTypeUnbond
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) {
|
func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteByte(w, tx.Type(), &n, &err)
|
WriteByte(w, TxTypeUnbond, &n, &err)
|
||||||
WriteBinary(w, tx.BaseTx, &n, &err)
|
WriteBinary(w, tx.BaseTx, &n, &err)
|
||||||
WriteUInt64(w, tx.Fee, &n, &err)
|
WriteUInt64(w, tx.Fee, &n, &err)
|
||||||
WriteUInt64(w, tx.Amount, &n, &err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,12 +186,8 @@ type TimeoutTx struct {
|
|||||||
Penalty uint64
|
Penalty uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *TimeoutTx) Type() byte {
|
|
||||||
return txTypeTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *TimeoutTx) WriteTo(w io.Writer) (n int64, err error) {
|
func (tx *TimeoutTx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteByte(w, tx.Type(), &n, &err)
|
WriteByte(w, TxTypeTimeout, &n, &err)
|
||||||
WriteBinary(w, tx.BaseTx, &n, &err)
|
WriteBinary(w, tx.BaseTx, &n, &err)
|
||||||
WriteUInt64(w, tx.AccountId, &n, &err)
|
WriteUInt64(w, tx.AccountId, &n, &err)
|
||||||
WriteUInt64(w, tx.Penalty, &n, &err)
|
WriteUInt64(w, tx.Penalty, &n, &err)
|
||||||
@ -225,22 +202,10 @@ type DupeoutTx struct {
|
|||||||
VoteB Vote
|
VoteB Vote
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *DupeoutTx) Type() byte {
|
|
||||||
return txTypeDupeout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *DupeoutTx) WriteTo(w io.Writer) (n int64, err error) {
|
func (tx *DupeoutTx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteByte(w, tx.Type(), &n, &err)
|
WriteByte(w, TxTypeDupeout, &n, &err)
|
||||||
WriteBinary(w, tx.BaseTx, &n, &err)
|
WriteBinary(w, tx.BaseTx, &n, &err)
|
||||||
WriteBinary(w, &tx.VoteA, &n, &err)
|
WriteBinary(w, &tx.VoteA, &n, &err)
|
||||||
WriteBinary(w, &tx.VoteB, &n, &err)
|
WriteBinary(w, &tx.VoteB, &n, &err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *DupeoutTx) GenDocument() []byte {
|
|
||||||
oldSig := tx.Signature
|
|
||||||
tx.Signature = Signature{}
|
|
||||||
doc := BinaryBytes(tx)
|
|
||||||
tx.Signature = oldSig
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
@ -49,14 +49,6 @@ func (v *Vote) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vote) GenDocument() []byte {
|
|
||||||
oldSig := v.Signature
|
|
||||||
v.Signature = Signature{}
|
|
||||||
doc := BinaryBytes(v)
|
|
||||||
v.Signature = oldSig
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Vote) GetSignature() Signature {
|
func (v *Vote) GetSignature() Signature {
|
||||||
return v.Signature
|
return v.Signature
|
||||||
}
|
}
|
||||||
|
@ -402,7 +402,7 @@ OUTER_LOOP:
|
|||||||
// Signs a vote document and broadcasts it.
|
// Signs a vote document and broadcasts it.
|
||||||
func (conR *ConsensusReactor) signAndBroadcastVote(rs *RoundState, vote *Vote) {
|
func (conR *ConsensusReactor) signAndBroadcastVote(rs *RoundState, vote *Vote) {
|
||||||
if rs.PrivValidator != nil {
|
if rs.PrivValidator != nil {
|
||||||
rs.PrivValidator.SignVote(vote)
|
rs.PrivValidator.Sign(vote)
|
||||||
conR.conS.AddVote(vote)
|
conR.conS.AddVote(vote)
|
||||||
msg := p2p.TypedMessage{msgTypeVote, vote}
|
msg := p2p.TypedMessage{msgTypeVote, vote}
|
||||||
conR.sw.Broadcast(VoteCh, msg)
|
conR.sw.Broadcast(VoteCh, msg)
|
||||||
|
@ -45,8 +45,8 @@ func (pol *POL) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
func (pol *POL) Verify(vset *ValidatorSet) error {
|
func (pol *POL) Verify(vset *ValidatorSet) error {
|
||||||
|
|
||||||
talliedVotingPower := uint64(0)
|
talliedVotingPower := uint64(0)
|
||||||
voteDoc := (&Vote{Height: pol.Height, Round: pol.Round,
|
voteDoc := BinaryBytes(&Vote{Height: pol.Height, Round: pol.Round,
|
||||||
Type: VoteTypeBare, BlockHash: pol.BlockHash}).GenDocument()
|
Type: VoteTypeBare, BlockHash: pol.BlockHash})
|
||||||
seenValidators := map[uint64]struct{}{}
|
seenValidators := map[uint64]struct{}{}
|
||||||
|
|
||||||
for _, sig := range pol.Votes {
|
for _, sig := range pol.Votes {
|
||||||
@ -59,7 +59,7 @@ func (pol *POL) Verify(vset *ValidatorSet) error {
|
|||||||
if validator == nil {
|
if validator == nil {
|
||||||
return Errorf("Invalid validator for vote %v for POL %v", sig, pol)
|
return Errorf("Invalid validator for vote %v for POL %v", sig, pol)
|
||||||
}
|
}
|
||||||
if !validator.Verify(voteDoc, sig) {
|
if !validator.VerifyBytes(voteDoc, sig) {
|
||||||
return Errorf("Invalid signature for vote %v for POL %v", sig, pol)
|
return Errorf("Invalid signature for vote %v for POL %v", sig, pol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,9 +80,9 @@ func (pol *POL) Verify(vset *ValidatorSet) error {
|
|||||||
return Errorf("Invalid validator for commit %v for POL %v", sig, pol)
|
return Errorf("Invalid validator for commit %v for POL %v", sig, pol)
|
||||||
}
|
}
|
||||||
|
|
||||||
commitDoc := (&Vote{Height: pol.Height, Round: round,
|
commitDoc := BinaryBytes(&Vote{Height: pol.Height, Round: round,
|
||||||
Type: VoteTypeCommit, BlockHash: pol.BlockHash}).GenDocument() // TODO cache
|
Type: VoteTypeCommit, BlockHash: pol.BlockHash}) // TODO cache
|
||||||
if !validator.Verify(commitDoc, sig) {
|
if !validator.VerifyBytes(commitDoc, sig) {
|
||||||
return Errorf("Invalid signature for commit %v for POL %v", sig, pol)
|
return Errorf("Invalid signature for commit %v for POL %v", sig, pol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,14 +13,14 @@ type PrivValidator struct {
|
|||||||
db *db_.LevelDB
|
db *db_.LevelDB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double signing results in an error.
|
// Double signing results in a panic.
|
||||||
func (pv *PrivValidator) SignProposal(proposal *Proposal) {
|
func (pv *PrivValidator) Sign(o Signable) {
|
||||||
//TODO: prevent double signing.
|
switch o.(type) {
|
||||||
pv.SignSignable(proposal)
|
case *Proposal:
|
||||||
}
|
//TODO: prevent double signing.
|
||||||
|
pv.PrivAccount.Sign(o.(*Proposal))
|
||||||
// Double signing results in an error.
|
case *Vote:
|
||||||
func (pv *PrivValidator) SignVote(vote *Vote) {
|
//TODO: prevent double signing.
|
||||||
//TODO: prevent double signing.
|
pv.PrivAccount.Sign(o.(*Vote))
|
||||||
pv.SignSignable(vote)
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,14 +58,6 @@ func (p *Proposal) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proposal) GenDocument() []byte {
|
|
||||||
oldSig := p.Signature
|
|
||||||
p.Signature = Signature{}
|
|
||||||
doc := BinaryBytes(p)
|
|
||||||
p.Signature = oldSig
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proposal) GetSignature() Signature {
|
func (p *Proposal) GetSignature() Signature {
|
||||||
return p.Signature
|
return p.Signature
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify signature
|
// Verify signature
|
||||||
if !cs.Proposer.Verify(proposal.GenDocument(), proposal.Signature) {
|
if !cs.Proposer.Verify(proposal) {
|
||||||
return ErrInvalidProposalSignature
|
return ErrInvalidProposalSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ func (cs *ConsensusState) MakeProposal() {
|
|||||||
// Make proposal
|
// Make proposal
|
||||||
proposal := NewProposal(cs.Height, cs.Round, blockPartSet.Total(), blockPartSet.RootHash(),
|
proposal := NewProposal(cs.Height, cs.Round, blockPartSet.Total(), blockPartSet.RootHash(),
|
||||||
polPartSet.Total(), polPartSet.RootHash())
|
polPartSet.Total(), polPartSet.RootHash())
|
||||||
cs.PrivValidator.SignProposal(proposal)
|
cs.PrivValidator.Sign(proposal)
|
||||||
|
|
||||||
// Set fields
|
// Set fields
|
||||||
cs.Proposal = proposal
|
cs.Proposal = proposal
|
||||||
|
@ -69,7 +69,7 @@ func (vs *VoteSet) AddVote(vote *Vote) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check signature.
|
// Check signature.
|
||||||
if !val.Verify(vote.GenDocument(), vote.Signature) {
|
if !val.Verify(vote) {
|
||||||
// Bad signature.
|
// Bad signature.
|
||||||
return false, ErrVoteInvalidSignature
|
return false, ErrVoteInvalidSignature
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package merkle
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"container/list"
|
"container/list"
|
||||||
. "github.com/tendermint/tendermint/binary"
|
|
||||||
. "github.com/tendermint/tendermint/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultCacheCapacity = 1000 // TODO make configurable.
|
const defaultCacheCapacity = 1000 // TODO make configurable.
|
||||||
@ -117,83 +115,6 @@ func (t *IAVLTree) Copy() Tree {
|
|||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
// TODO: make TypedTree work with the underlying tree to cache the decoded value.
|
|
||||||
type TypedTree struct {
|
|
||||||
Tree Tree
|
|
||||||
keyCodec Codec
|
|
||||||
valueCodec Codec
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTypedTree(tree Tree, keyCodec, valueCodec Codec) *TypedTree {
|
|
||||||
return &TypedTree{
|
|
||||||
Tree: tree,
|
|
||||||
keyCodec: keyCodec,
|
|
||||||
valueCodec: valueCodec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TypedTree) Has(key interface{}) bool {
|
|
||||||
bytes, err := t.keyCodec.Write(key)
|
|
||||||
if err != nil {
|
|
||||||
Panicf("Error from keyCodec: %v", err)
|
|
||||||
}
|
|
||||||
return t.Tree.Has(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TypedTree) Get(key interface{}) interface{} {
|
|
||||||
keyBytes, err := t.keyCodec.Write(key)
|
|
||||||
if err != nil {
|
|
||||||
Panicf("Error from keyCodec: %v", err)
|
|
||||||
}
|
|
||||||
valueBytes := t.Tree.Get(keyBytes)
|
|
||||||
if valueBytes == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
value, err := t.valueCodec.Read(valueBytes)
|
|
||||||
if err != nil {
|
|
||||||
Panicf("Error from valueCodec: %v", err)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TypedTree) Set(key interface{}, value interface{}) bool {
|
|
||||||
keyBytes, err := t.keyCodec.Write(key)
|
|
||||||
if err != nil {
|
|
||||||
Panicf("Error from keyCodec: %v", err)
|
|
||||||
}
|
|
||||||
valueBytes, err := t.valueCodec.Write(value)
|
|
||||||
if err != nil {
|
|
||||||
Panicf("Error from valueCodec: %v", err)
|
|
||||||
}
|
|
||||||
return t.Tree.Set(keyBytes, valueBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TypedTree) Remove(key interface{}) (interface{}, error) {
|
|
||||||
keyBytes, err := t.keyCodec.Write(key)
|
|
||||||
if err != nil {
|
|
||||||
Panicf("Error from keyCodec: %v", err)
|
|
||||||
}
|
|
||||||
valueBytes, err := t.Tree.Remove(keyBytes)
|
|
||||||
if valueBytes == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
value, err_ := t.valueCodec.Read(valueBytes)
|
|
||||||
if err_ != nil {
|
|
||||||
Panicf("Error from valueCodec: %v", err)
|
|
||||||
}
|
|
||||||
return value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TypedTree) Copy() *TypedTree {
|
|
||||||
return &TypedTree{
|
|
||||||
Tree: t.Tree.Copy(),
|
|
||||||
keyCodec: t.keyCodec,
|
|
||||||
valueCodec: t.valueCodec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type nodeElement struct {
|
type nodeElement struct {
|
||||||
node *IAVLNode
|
node *IAVLNode
|
||||||
elem *list.Element
|
elem *list.Element
|
||||||
|
81
merkle/typed_tree.go
Normal file
81
merkle/typed_tree.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package merkle
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/tendermint/tendermint/binary"
|
||||||
|
. "github.com/tendermint/tendermint/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: make TypedTree work with the underlying tree to cache the decoded value.
|
||||||
|
type TypedTree struct {
|
||||||
|
Tree Tree
|
||||||
|
keyCodec Codec
|
||||||
|
valueCodec Codec
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTypedTree(tree Tree, keyCodec, valueCodec Codec) *TypedTree {
|
||||||
|
return &TypedTree{
|
||||||
|
Tree: tree,
|
||||||
|
keyCodec: keyCodec,
|
||||||
|
valueCodec: valueCodec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedTree) Has(key interface{}) bool {
|
||||||
|
bytes, err := t.keyCodec.Write(key)
|
||||||
|
if err != nil {
|
||||||
|
Panicf("Error from keyCodec: %v", err)
|
||||||
|
}
|
||||||
|
return t.Tree.Has(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedTree) Get(key interface{}) interface{} {
|
||||||
|
keyBytes, err := t.keyCodec.Write(key)
|
||||||
|
if err != nil {
|
||||||
|
Panicf("Error from keyCodec: %v", err)
|
||||||
|
}
|
||||||
|
valueBytes := t.Tree.Get(keyBytes)
|
||||||
|
if valueBytes == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
value, err := t.valueCodec.Read(valueBytes)
|
||||||
|
if err != nil {
|
||||||
|
Panicf("Error from valueCodec: %v", err)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedTree) Set(key interface{}, value interface{}) bool {
|
||||||
|
keyBytes, err := t.keyCodec.Write(key)
|
||||||
|
if err != nil {
|
||||||
|
Panicf("Error from keyCodec: %v", err)
|
||||||
|
}
|
||||||
|
valueBytes, err := t.valueCodec.Write(value)
|
||||||
|
if err != nil {
|
||||||
|
Panicf("Error from valueCodec: %v", err)
|
||||||
|
}
|
||||||
|
return t.Tree.Set(keyBytes, valueBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedTree) Remove(key interface{}) (interface{}, error) {
|
||||||
|
keyBytes, err := t.keyCodec.Write(key)
|
||||||
|
if err != nil {
|
||||||
|
Panicf("Error from keyCodec: %v", err)
|
||||||
|
}
|
||||||
|
valueBytes, err := t.Tree.Remove(keyBytes)
|
||||||
|
if valueBytes == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value, err_ := t.valueCodec.Read(valueBytes)
|
||||||
|
if err_ != nil {
|
||||||
|
Panicf("Error from valueCodec: %v", err)
|
||||||
|
}
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedTree) Copy() *TypedTree {
|
||||||
|
return &TypedTree{
|
||||||
|
Tree: t.Tree.Copy(),
|
||||||
|
keyCodec: t.keyCodec,
|
||||||
|
valueCodec: t.valueCodec,
|
||||||
|
}
|
||||||
|
}
|
@ -9,8 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AccountBalanceStatusNominal = byte(0x00)
|
AccountDetailStatusNominal = byte(0x00)
|
||||||
AccountBalanceStatusBonded = byte(0x01)
|
AccountDetailStatusBonded = byte(0x01)
|
||||||
|
AccountDetailStatusUnbonding = byte(0x02)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
@ -31,7 +32,7 @@ func (account Account) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (account Account) Verify(msg []byte, sig Signature) bool {
|
func (account Account) VerifyBytes(msg []byte, sig Signature) bool {
|
||||||
if sig.SignerId != account.Id {
|
if sig.SignerId != account.Id {
|
||||||
panic("account.id doesn't match sig.signerid")
|
panic("account.id doesn't match sig.signerid")
|
||||||
}
|
}
|
||||||
@ -44,32 +45,37 @@ func (account Account) Verify(msg []byte, sig Signature) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (account Account) VerifySignable(o Signable) bool {
|
func (account Account) Verify(o Signable) bool {
|
||||||
msg := o.GenDocument()
|
|
||||||
sig := o.GetSignature()
|
sig := o.GetSignature()
|
||||||
return account.Verify(msg, sig)
|
o.SetSignature(Signature{}) // clear
|
||||||
|
msg := BinaryBytes(o)
|
||||||
|
o.SetSignature(sig) // restore
|
||||||
|
return account.VerifyBytes(msg, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
type AccountBalance struct {
|
type AccountDetail struct {
|
||||||
Account
|
Account
|
||||||
Balance uint64
|
Sequence uint64
|
||||||
Status byte
|
Balance uint64
|
||||||
|
Status byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadAccountBalance(r io.Reader, n *int64, err *error) *AccountBalance {
|
func ReadAccountDetail(r io.Reader, n *int64, err *error) *AccountDetail {
|
||||||
return &AccountBalance{
|
return &AccountDetail{
|
||||||
Account: ReadAccount(r, n, err),
|
Account: ReadAccount(r, n, err),
|
||||||
Balance: ReadUInt64(r, n, err),
|
Sequence: ReadUInt64(r, n, err),
|
||||||
Status: ReadByte(r, n, err),
|
Balance: ReadUInt64(r, n, err),
|
||||||
|
Status: ReadByte(r, n, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (accBal AccountBalance) WriteTo(w io.Writer) (n int64, err error) {
|
func (accDet AccountDetail) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
WriteBinary(w, accBal.Account, &n, &err)
|
WriteBinary(w, accDet.Account, &n, &err)
|
||||||
WriteUInt64(w, accBal.Balance, &n, &err)
|
WriteUInt64(w, accDet.Sequence, &n, &err)
|
||||||
WriteByte(w, accBal.Status, &n, &err)
|
WriteUInt64(w, accDet.Balance, &n, &err)
|
||||||
|
WriteByte(w, accDet.Status, &n, &err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +100,7 @@ func GenPrivAccount() *PrivAccount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *PrivAccount) Sign(msg []byte) Signature {
|
func (pa *PrivAccount) SignBytes(msg []byte) Signature {
|
||||||
signature := crypto.SignMessage(msg, pa.PrivKey, pa.PubKey)
|
signature := crypto.SignMessage(msg, pa.PrivKey, pa.PubKey)
|
||||||
sig := Signature{
|
sig := Signature{
|
||||||
SignerId: pa.Id,
|
SignerId: pa.Id,
|
||||||
@ -103,8 +109,9 @@ func (pa *PrivAccount) Sign(msg []byte) Signature {
|
|||||||
return sig
|
return sig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *PrivAccount) SignSignable(o Signable) {
|
func (pa *PrivAccount) Sign(o Signable) {
|
||||||
msg := o.GenDocument()
|
o.SetSignature(Signature{}) // clear
|
||||||
sig := pa.Sign(msg)
|
msg := BinaryBytes(o)
|
||||||
|
sig := pa.SignBytes(msg)
|
||||||
o.SetSignature(sig)
|
o.SetSignature(sig)
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ func TestSignAndValidate(t *testing.T) {
|
|||||||
account := &privAccount.Account
|
account := &privAccount.Account
|
||||||
|
|
||||||
msg := CRandBytes(128)
|
msg := CRandBytes(128)
|
||||||
sig := privAccount.Sign(msg)
|
sig := privAccount.SignBytes(msg)
|
||||||
t.Logf("msg: %X, sig: %X", msg, sig)
|
t.Logf("msg: %X, sig: %X", msg, sig)
|
||||||
|
|
||||||
// Test the signature
|
// Test the signature
|
||||||
|
156
state/state.go
156
state/state.go
@ -12,62 +12,67 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrStateInvalidAccountId = errors.New("Error State invalid account id")
|
||||||
|
ErrStateInvalidSignature = errors.New("Error State invalid signature")
|
||||||
ErrStateInvalidSequenceNumber = errors.New("Error State invalid sequence number")
|
ErrStateInvalidSequenceNumber = errors.New("Error State invalid sequence number")
|
||||||
|
ErrStateInvalidAccountState = errors.New("Error State invalid account state")
|
||||||
ErrStateInvalidValidationStateHash = errors.New("Error State invalid ValidationStateHash")
|
ErrStateInvalidValidationStateHash = errors.New("Error State invalid ValidationStateHash")
|
||||||
ErrStateInvalidAccountStateHash = errors.New("Error State invalid AccountStateHash")
|
ErrStateInvalidAccountStateHash = errors.New("Error State invalid AccountStateHash")
|
||||||
|
ErrStateInsufficientFunds = errors.New("Error State insufficient funds")
|
||||||
|
|
||||||
stateKey = []byte("stateKey")
|
stateKey = []byte("stateKey")
|
||||||
|
minBondAmount = uint64(1) // TODO adjust
|
||||||
)
|
)
|
||||||
|
|
||||||
type accountBalanceCodec struct{}
|
type accountDetailCodec struct{}
|
||||||
|
|
||||||
func (abc accountBalanceCodec) Write(accBal interface{}) (accBalBytes []byte, err error) {
|
func (abc accountDetailCodec) Write(accDet interface{}) (accDetBytes []byte, err error) {
|
||||||
w := new(bytes.Buffer)
|
w := new(bytes.Buffer)
|
||||||
_, err = accBal.(*AccountBalance).WriteTo(w)
|
_, err = accDet.(*AccountDetail).WriteTo(w)
|
||||||
return w.Bytes(), err
|
return w.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (abc accountBalanceCodec) Read(accBalBytes []byte) (interface{}, error) {
|
func (abc accountDetailCodec) Read(accDetBytes []byte) (interface{}, error) {
|
||||||
n, err, r := new(int64), new(error), bytes.NewBuffer(accBalBytes)
|
n, err, r := new(int64), new(error), bytes.NewBuffer(accDetBytes)
|
||||||
return ReadAccountBalance(r, n, err), *err
|
return ReadAccountDetail(r, n, err), *err
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
// NOTE: not goroutine-safe.
|
// NOTE: not goroutine-safe.
|
||||||
type State struct {
|
type State struct {
|
||||||
DB DB
|
DB DB
|
||||||
Height uint32 // Last known block height
|
Height uint32 // Last known block height
|
||||||
BlockHash []byte // Last known block hash
|
BlockHash []byte // Last known block hash
|
||||||
CommitTime time.Time
|
CommitTime time.Time
|
||||||
AccountBalances *merkle.TypedTree
|
AccountDetails *merkle.TypedTree
|
||||||
Validators *ValidatorSet
|
Validators *ValidatorSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenesisState(db DB, genesisTime time.Time, accBals []*AccountBalance) *State {
|
func GenesisState(db DB, genesisTime time.Time, accDets []*AccountDetail) *State {
|
||||||
|
|
||||||
// TODO: Use "uint64Codec" instead of BasicCodec
|
// TODO: Use "uint64Codec" instead of BasicCodec
|
||||||
accountBalances := merkle.NewTypedTree(merkle.NewIAVLTree(db), BasicCodec, accountBalanceCodec{})
|
accountDetails := merkle.NewTypedTree(merkle.NewIAVLTree(db), BasicCodec, accountDetailCodec{})
|
||||||
validators := map[uint64]*Validator{}
|
validators := map[uint64]*Validator{}
|
||||||
|
|
||||||
for _, accBal := range accBals {
|
for _, accDet := range accDets {
|
||||||
accountBalances.Set(accBal.Id, accBal)
|
accountDetails.Set(accDet.Id, accDet)
|
||||||
validators[accBal.Id] = &Validator{
|
validators[accDet.Id] = &Validator{
|
||||||
Account: accBal.Account,
|
Account: accDet.Account,
|
||||||
BondHeight: 0,
|
BondHeight: 0,
|
||||||
VotingPower: accBal.Balance,
|
VotingPower: accDet.Balance,
|
||||||
Accum: 0,
|
Accum: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
validatorSet := NewValidatorSet(validators)
|
validatorSet := NewValidatorSet(validators)
|
||||||
|
|
||||||
return &State{
|
return &State{
|
||||||
DB: db,
|
DB: db,
|
||||||
Height: 0,
|
Height: 0,
|
||||||
BlockHash: nil,
|
BlockHash: nil,
|
||||||
CommitTime: genesisTime,
|
CommitTime: genesisTime,
|
||||||
AccountBalances: accountBalances,
|
AccountDetails: accountDetails,
|
||||||
Validators: validatorSet,
|
Validators: validatorSet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,8 +88,8 @@ func LoadState(db DB) *State {
|
|||||||
s.Height = ReadUInt32(reader, &n, &err)
|
s.Height = ReadUInt32(reader, &n, &err)
|
||||||
s.CommitTime = ReadTime(reader, &n, &err)
|
s.CommitTime = ReadTime(reader, &n, &err)
|
||||||
s.BlockHash = ReadByteSlice(reader, &n, &err)
|
s.BlockHash = ReadByteSlice(reader, &n, &err)
|
||||||
accountBalancesHash := ReadByteSlice(reader, &n, &err)
|
accountDetailsHash := ReadByteSlice(reader, &n, &err)
|
||||||
s.AccountBalances = merkle.NewTypedTree(merkle.LoadIAVLTreeFromHash(db, accountBalancesHash), BasicCodec, accountBalanceCodec{})
|
s.AccountDetails = merkle.NewTypedTree(merkle.LoadIAVLTreeFromHash(db, accountDetailsHash), BasicCodec, accountDetailCodec{})
|
||||||
var validators = map[uint64]*Validator{}
|
var validators = map[uint64]*Validator{}
|
||||||
for reader.Len() > 0 {
|
for reader.Len() > 0 {
|
||||||
validator := ReadValidator(reader, &n, &err)
|
validator := ReadValidator(reader, &n, &err)
|
||||||
@ -103,14 +108,14 @@ func LoadState(db DB) *State {
|
|||||||
// is saved here.
|
// is saved here.
|
||||||
func (s *State) Save(commitTime time.Time) {
|
func (s *State) Save(commitTime time.Time) {
|
||||||
s.CommitTime = commitTime
|
s.CommitTime = commitTime
|
||||||
s.AccountBalances.Tree.Save()
|
s.AccountDetails.Tree.Save()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
var n int64
|
var n int64
|
||||||
var err error
|
var err error
|
||||||
WriteUInt32(&buf, s.Height, &n, &err)
|
WriteUInt32(&buf, s.Height, &n, &err)
|
||||||
WriteTime(&buf, commitTime, &n, &err)
|
WriteTime(&buf, commitTime, &n, &err)
|
||||||
WriteByteSlice(&buf, s.BlockHash, &n, &err)
|
WriteByteSlice(&buf, s.BlockHash, &n, &err)
|
||||||
WriteByteSlice(&buf, s.AccountBalances.Tree.Hash(), &n, &err)
|
WriteByteSlice(&buf, s.AccountDetails.Tree.Hash(), &n, &err)
|
||||||
for _, validator := range s.Validators.Map() {
|
for _, validator := range s.Validators.Map() {
|
||||||
WriteBinary(&buf, validator, &n, &err)
|
WriteBinary(&buf, validator, &n, &err)
|
||||||
}
|
}
|
||||||
@ -122,26 +127,76 @@ func (s *State) Save(commitTime time.Time) {
|
|||||||
|
|
||||||
func (s *State) Copy() *State {
|
func (s *State) Copy() *State {
|
||||||
return &State{
|
return &State{
|
||||||
DB: s.DB,
|
DB: s.DB,
|
||||||
Height: s.Height,
|
Height: s.Height,
|
||||||
CommitTime: s.CommitTime,
|
CommitTime: s.CommitTime,
|
||||||
BlockHash: s.BlockHash,
|
BlockHash: s.BlockHash,
|
||||||
AccountBalances: s.AccountBalances.Copy(),
|
AccountDetails: s.AccountDetails.Copy(),
|
||||||
Validators: s.Validators.Copy(),
|
Validators: s.Validators.Copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the tx is invalid, an error will be returned.
|
// If the tx is invalid, an error will be returned.
|
||||||
// Unlike AppendBlock(), state will not be altered.
|
// Unlike AppendBlock(), state will not be altered.
|
||||||
func (s *State) ExecTx(tx Tx) error {
|
func (s *State) ExecTx(tx Tx) error {
|
||||||
/*
|
accDet := s.GetAccountDetail(tx.GetSignature().SignerId)
|
||||||
// Get the signer's incr
|
if accDet == nil {
|
||||||
signerId := tx.Signature().SignerId
|
return ErrStateInvalidAccountId
|
||||||
if mem.state.AccountSequence(signerId) != tx.Sequence() {
|
}
|
||||||
return ErrStateInvalidSequenceNumber
|
// Check signature
|
||||||
|
if !accDet.Verify(tx) {
|
||||||
|
return ErrStateInvalidSignature
|
||||||
|
}
|
||||||
|
// Check sequence
|
||||||
|
if tx.GetSequence() <= accDet.Sequence {
|
||||||
|
return ErrStateInvalidSequenceNumber
|
||||||
|
}
|
||||||
|
// Exec tx
|
||||||
|
switch tx.(type) {
|
||||||
|
case *SendTx:
|
||||||
|
stx := tx.(*SendTx)
|
||||||
|
toAccDet := s.GetAccountDetail(stx.To)
|
||||||
|
// Accounts must be nominal
|
||||||
|
if accDet.Status != AccountDetailStatusNominal {
|
||||||
|
return ErrStateInvalidAccountState
|
||||||
}
|
}
|
||||||
*/
|
if toAccDet.Status != AccountDetailStatusNominal {
|
||||||
// XXX commit the tx
|
return ErrStateInvalidAccountState
|
||||||
|
}
|
||||||
|
// Check account balance
|
||||||
|
if accDet.Balance < stx.Fee+stx.Amount {
|
||||||
|
return ErrStateInsufficientFunds
|
||||||
|
}
|
||||||
|
// Check existence of destination account
|
||||||
|
if toAccDet == nil {
|
||||||
|
return ErrStateInvalidAccountId
|
||||||
|
}
|
||||||
|
// Good!
|
||||||
|
accDet.Balance -= (stx.Fee + stx.Amount)
|
||||||
|
toAccDet.Balance += (stx.Amount)
|
||||||
|
s.SetAccountDetail(accDet)
|
||||||
|
s.SetAccountDetail(toAccDet)
|
||||||
|
//case *NameTx
|
||||||
|
case *BondTx:
|
||||||
|
btx := tx.(*BondTx)
|
||||||
|
// Account must be nominal
|
||||||
|
if accDet.Status != AccountDetailStatusNominal {
|
||||||
|
return ErrStateInvalidAccountState
|
||||||
|
}
|
||||||
|
// Check account balance
|
||||||
|
if accDet.Balance < minBondAmount {
|
||||||
|
return ErrStateInsufficientFunds
|
||||||
|
}
|
||||||
|
// TODO: max number of validators?
|
||||||
|
// Good!
|
||||||
|
accDet.Balance -= btx.Fee // remaining balance are bonded coins.
|
||||||
|
accDet.Status = AccountDetailStatusBonded
|
||||||
|
s.SetAccountDetail(accDet)
|
||||||
|
// XXX add validator
|
||||||
|
case *UnbondTx:
|
||||||
|
case *TimeoutTx:
|
||||||
|
case *DupeoutTx:
|
||||||
|
}
|
||||||
panic("Implement ExecTx()")
|
panic("Implement ExecTx()")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -170,7 +225,7 @@ func (s *State) AppendBlock(b *Block) error {
|
|||||||
if !bytes.Equal(s.Validators.Hash(), b.ValidationStateHash) {
|
if !bytes.Equal(s.Validators.Hash(), b.ValidationStateHash) {
|
||||||
return ErrStateInvalidValidationStateHash
|
return ErrStateInvalidValidationStateHash
|
||||||
}
|
}
|
||||||
if !bytes.Equal(s.AccountBalances.Tree.Hash(), b.AccountStateHash) {
|
if !bytes.Equal(s.AccountDetails.Tree.Hash(), b.AccountStateHash) {
|
||||||
return ErrStateInvalidAccountStateHash
|
return ErrStateInvalidAccountStateHash
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,10 +234,15 @@ func (s *State) AppendBlock(b *Block) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) AccountBalance(accountId uint64) *AccountBalance {
|
func (s *State) GetAccountDetail(accountId uint64) *AccountDetail {
|
||||||
accBal := s.AccountBalances.Get(accountId)
|
accDet := s.AccountDetails.Get(accountId)
|
||||||
if accBal == nil {
|
if accDet == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return accBal.(*AccountBalance)
|
return accDet.(*AccountDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns false if new, true if updated.
|
||||||
|
func (s *State) SetAccountDetail(accDet *AccountDetail) (updated bool) {
|
||||||
|
return s.AccountDetails.Set(accDet.Id, accDet)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user