mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-13 21:31:23 +00:00
Bare consensus refactor
This commit is contained in:
22
Godeps/_workspace/src/github.com/tendermint/go-p2p/secret_connection.go
generated
vendored
22
Godeps/_workspace/src/github.com/tendermint/go-p2p/secret_connection.go
generated
vendored
@ -20,7 +20,7 @@ import (
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
@ -38,7 +38,7 @@ type SecretConnection struct {
|
||||
recvBuffer []byte
|
||||
recvNonce *[24]byte
|
||||
sendNonce *[24]byte
|
||||
remPubKey acm.PubKeyEd25519
|
||||
remPubKey crypto.PubKeyEd25519
|
||||
shrSecret *[32]byte // shared secret
|
||||
}
|
||||
|
||||
@ -46,9 +46,9 @@ type SecretConnection struct {
|
||||
// Returns nil if error in handshake.
|
||||
// Caller should call conn.Close()
|
||||
// See docs/sts-final.pdf for more information.
|
||||
func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey acm.PrivKeyEd25519) (*SecretConnection, error) {
|
||||
func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKeyEd25519) (*SecretConnection, error) {
|
||||
|
||||
locPubKey := locPrivKey.PubKey().(acm.PubKeyEd25519)
|
||||
locPubKey := locPrivKey.PubKey().(crypto.PubKeyEd25519)
|
||||
|
||||
// Generate ephemeral keys for perfect forward secrecy.
|
||||
locEphPub, locEphPriv := genEphKeys()
|
||||
@ -101,7 +101,7 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey acm.PrivKeyEd25519
|
||||
}
|
||||
|
||||
// Returns authenticated remote pubkey
|
||||
func (sc *SecretConnection) RemotePubKey() acm.PubKeyEd25519 {
|
||||
func (sc *SecretConnection) RemotePubKey() crypto.PubKeyEd25519 {
|
||||
return sc.remPubKey
|
||||
}
|
||||
|
||||
@ -254,17 +254,17 @@ func genChallenge(loPubKey, hiPubKey *[32]byte) (challenge *[32]byte) {
|
||||
return hash32(append(loPubKey[:], hiPubKey[:]...))
|
||||
}
|
||||
|
||||
func signChallenge(challenge *[32]byte, locPrivKey acm.PrivKeyEd25519) (signature acm.SignatureEd25519) {
|
||||
signature = locPrivKey.Sign(challenge[:]).(acm.SignatureEd25519)
|
||||
func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKeyEd25519) (signature crypto.SignatureEd25519) {
|
||||
signature = locPrivKey.Sign(challenge[:]).(crypto.SignatureEd25519)
|
||||
return
|
||||
}
|
||||
|
||||
type authSigMessage struct {
|
||||
Key acm.PubKeyEd25519
|
||||
Sig acm.SignatureEd25519
|
||||
Key crypto.PubKeyEd25519
|
||||
Sig crypto.SignatureEd25519
|
||||
}
|
||||
|
||||
func shareAuthSignature(sc *SecretConnection, pubKey acm.PubKeyEd25519, signature acm.SignatureEd25519) (*authSigMessage, error) {
|
||||
func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKeyEd25519, signature crypto.SignatureEd25519) (*authSigMessage, error) {
|
||||
var recvMsg authSigMessage
|
||||
var err1, err2 error
|
||||
|
||||
@ -293,7 +293,7 @@ func shareAuthSignature(sc *SecretConnection, pubKey acm.PubKeyEd25519, signatur
|
||||
return &recvMsg, nil
|
||||
}
|
||||
|
||||
func verifyChallengeSignature(challenge *[32]byte, remPubKey acm.PubKeyEd25519, remSignature acm.SignatureEd25519) bool {
|
||||
func verifyChallengeSignature(challenge *[32]byte, remPubKey crypto.PubKeyEd25519, remSignature crypto.SignatureEd25519) bool {
|
||||
return remPubKey.VerifyBytes(challenge[:], remSignature)
|
||||
}
|
||||
|
||||
|
12
Godeps/_workspace/src/github.com/tendermint/go-p2p/secret_connection_test.go
generated
vendored
12
Godeps/_workspace/src/github.com/tendermint/go-p2p/secret_connection_test.go
generated
vendored
@ -5,7 +5,7 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
@ -32,10 +32,10 @@ func makeDummyConnPair() (fooConn, barConn dummyConn) {
|
||||
|
||||
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
|
||||
fooConn, barConn := makeDummyConnPair()
|
||||
fooPrvKey := acm.GenPrivKeyEd25519()
|
||||
fooPubKey := fooPrvKey.PubKey().(acm.PubKeyEd25519)
|
||||
barPrvKey := acm.GenPrivKeyEd25519()
|
||||
barPubKey := barPrvKey.PubKey().(acm.PubKeyEd25519)
|
||||
fooPrvKey := crypto.GenPrivKeyEd25519()
|
||||
fooPubKey := fooPrvKey.PubKey().(crypto.PubKeyEd25519)
|
||||
barPrvKey := crypto.GenPrivKeyEd25519()
|
||||
barPubKey := barPrvKey.PubKey().(crypto.PubKeyEd25519)
|
||||
|
||||
Parallel(
|
||||
func() {
|
||||
@ -89,7 +89,7 @@ func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
genNodeRunner := func(nodeConn dummyConn, nodeWrites []string, nodeReads *[]string) func() {
|
||||
return func() {
|
||||
// Node handskae
|
||||
nodePrvKey := acm.GenPrivKeyEd25519()
|
||||
nodePrvKey := crypto.GenPrivKeyEd25519()
|
||||
nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to establish SecretConnection for node: %v", err)
|
||||
|
8
Godeps/_workspace/src/github.com/tendermint/go-p2p/switch.go
generated
vendored
8
Godeps/_workspace/src/github.com/tendermint/go-p2p/switch.go
generated
vendored
@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/log15"
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
@ -63,7 +63,7 @@ type Switch struct {
|
||||
peers *PeerSet
|
||||
dialing *CMap
|
||||
nodeInfo *types.NodeInfo // our node info
|
||||
nodePrivKey acm.PrivKeyEd25519 // our node privkey
|
||||
nodePrivKey crypto.PrivKeyEd25519 // our node privkey
|
||||
}
|
||||
|
||||
var (
|
||||
@ -145,10 +145,10 @@ func (sw *Switch) NodeInfo() *types.NodeInfo {
|
||||
|
||||
// Not goroutine safe.
|
||||
// NOTE: Overwrites sw.nodeInfo.PubKey
|
||||
func (sw *Switch) SetNodePrivKey(nodePrivKey acm.PrivKeyEd25519) {
|
||||
func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKeyEd25519) {
|
||||
sw.nodePrivKey = nodePrivKey
|
||||
if sw.nodeInfo != nil {
|
||||
sw.nodeInfo.PubKey = nodePrivKey.PubKey().(acm.PubKeyEd25519)
|
||||
sw.nodeInfo.PubKey = nodePrivKey.PubKey().(crypto.PubKeyEd25519)
|
||||
}
|
||||
}
|
||||
|
||||
|
10
Godeps/_workspace/src/github.com/tendermint/go-p2p/switch_test.go
generated
vendored
10
Godeps/_workspace/src/github.com/tendermint/go-p2p/switch_test.go
generated
vendored
@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
. "github.com/tendermint/go-common"
|
||||
_ "github.com/tendermint/go-config/tendermint_test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
@ -72,13 +72,13 @@ func (tr *TestReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
|
||||
// convenience method for creating two switches connected to each other.
|
||||
func makeSwitchPair(t testing.TB, initSwitch func(*Switch) *Switch) (*Switch, *Switch) {
|
||||
|
||||
s1PrivKey := acm.GenPrivKeyEd25519()
|
||||
s2PrivKey := acm.GenPrivKeyEd25519()
|
||||
s1PrivKey := crypto.GenPrivKeyEd25519()
|
||||
s2PrivKey := crypto.GenPrivKeyEd25519()
|
||||
|
||||
// Create two switches that will be interconnected.
|
||||
s1 := initSwitch(NewSwitch())
|
||||
s1.SetNodeInfo(&types.NodeInfo{
|
||||
PubKey: s1PrivKey.PubKey().(acm.PubKeyEd25519),
|
||||
PubKey: s1PrivKey.PubKey().(crypto.PubKeyEd25519),
|
||||
Moniker: "switch1",
|
||||
ChainID: "testing",
|
||||
Version: types.Versions{Tendermint: "123.123.123"},
|
||||
@ -86,7 +86,7 @@ func makeSwitchPair(t testing.TB, initSwitch func(*Switch) *Switch) (*Switch, *S
|
||||
s1.SetNodePrivKey(s1PrivKey)
|
||||
s2 := initSwitch(NewSwitch())
|
||||
s2.SetNodeInfo(&types.NodeInfo{
|
||||
PubKey: s2PrivKey.PubKey().(acm.PubKeyEd25519),
|
||||
PubKey: s2PrivKey.PubKey().(crypto.PubKeyEd25519),
|
||||
Moniker: "switch2",
|
||||
ChainID: "testing",
|
||||
Version: types.Versions{Tendermint: "123.123.123"},
|
||||
|
3
Makefile
3
Makefile
@ -10,7 +10,6 @@ install:
|
||||
go install github.com/tendermint/tendermint/cmd/debora
|
||||
go install github.com/tendermint/tendermint/cmd/stdinwriter
|
||||
go install github.com/tendermint/tendermint/cmd/logjack
|
||||
go install github.com/tendermint/tendermint/cmd/sim_txs
|
||||
|
||||
build:
|
||||
go build -o build/tendermint github.com/tendermint/tendermint/cmd/tendermint
|
||||
@ -18,7 +17,6 @@ build:
|
||||
go build -o build/debora github.com/tendermint/tendermint/cmd/debora
|
||||
go build -o build/stdinwriter github.com/tendermint/tendermint/cmd/stdinwriter
|
||||
go build -o build/logjack github.com/tendermint/tendermint/cmd/logjack
|
||||
go build -o build/sim_txs github.com/tendermint/tendermint/cmd/sim_txs
|
||||
|
||||
build_race:
|
||||
go build -race -o build/tendermint github.com/tendermint/tendermint/cmd/tendermint
|
||||
@ -26,7 +24,6 @@ build_race:
|
||||
go build -race -o build/debora github.com/tendermint/tendermint/cmd/debora
|
||||
go build -race -o build/stdinwriter github.com/tendermint/tendermint/cmd/stdinwriter
|
||||
go build -race -o build/logjack github.com/tendermint/tendermint/cmd/logjack
|
||||
go build -race -o build/sim_txs github.com/tendermint/tendermint/cmd/sim_txs
|
||||
|
||||
test: build
|
||||
-rm -rf ~/.tendermint_test_bak
|
||||
|
@ -1,74 +0,0 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-merkle"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
)
|
||||
|
||||
// Signable is an interface for all signable things.
|
||||
// It typically removes signatures before serializing.
|
||||
type Signable interface {
|
||||
WriteSignBytes(chainID string, w io.Writer, n *int64, err *error)
|
||||
}
|
||||
|
||||
// SignBytes is a convenience method for getting the bytes to sign of a Signable.
|
||||
func SignBytes(chainID string, o Signable) []byte {
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
o.WriteSignBytes(chainID, buf, n, err)
|
||||
if *err != nil {
|
||||
PanicCrisis(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// HashSignBytes is a convenience method for getting the hash of the bytes of a signable
|
||||
func HashSignBytes(chainID string, o Signable) []byte {
|
||||
return merkle.SimpleHashFromBinary(SignBytes(chainID, o))
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Account resides in the application state, and is mutated by transactions
|
||||
// on the blockchain.
|
||||
// Serialized by wire.[read|write]Reflect
|
||||
type Account struct {
|
||||
Address []byte `json:"address"`
|
||||
PubKey PubKey `json:"pub_key"`
|
||||
Sequence int `json:"sequence"`
|
||||
Balance int64 `json:"balance"`
|
||||
Code []byte `json:"code"` // VM code
|
||||
StorageRoot []byte `json:"storage_root"` // VM storage merkle root.
|
||||
|
||||
Permissions ptypes.AccountPermissions `json:"permissions"`
|
||||
}
|
||||
|
||||
func (acc *Account) Copy() *Account {
|
||||
accCopy := *acc
|
||||
return &accCopy
|
||||
}
|
||||
|
||||
func (acc *Account) String() string {
|
||||
if acc == nil {
|
||||
return "nil-Account"
|
||||
}
|
||||
return fmt.Sprintf("Account{%X:%v B:%v C:%v S:%X P:%s}", acc.Address, acc.PubKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions)
|
||||
}
|
||||
|
||||
func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteBinary(o.(*Account), w, n, err)
|
||||
}
|
||||
|
||||
func AccountDecoder(r io.Reader, n *int64, err *error) interface{} {
|
||||
return wire.ReadBinary(&Account{}, r, n, err)
|
||||
}
|
||||
|
||||
var AccountCodec = wire.Codec{
|
||||
Encode: AccountEncoder,
|
||||
Decode: AccountDecoder,
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/tendermint/ed25519"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
type PrivAccount struct {
|
||||
Address []byte `json:"address"`
|
||||
PubKey PubKey `json:"pub_key"`
|
||||
PrivKey PrivKey `json:"priv_key"`
|
||||
}
|
||||
|
||||
func (pA *PrivAccount) Generate(index int) *PrivAccount {
|
||||
newPrivKey := pA.PrivKey.(PrivKeyEd25519).Generate(index)
|
||||
newPubKey := newPrivKey.PubKey()
|
||||
newAddress := newPubKey.Address()
|
||||
return &PrivAccount{
|
||||
Address: newAddress,
|
||||
PubKey: newPubKey,
|
||||
PrivKey: newPrivKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (pA *PrivAccount) Sign(chainID string, o Signable) Signature {
|
||||
return pA.PrivKey.Sign(SignBytes(chainID, o))
|
||||
}
|
||||
|
||||
func (pA *PrivAccount) String() string {
|
||||
return Fmt("PrivAccount{%X}", pA.Address)
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
// Generates a new account with private key.
|
||||
func GenPrivAccount() *PrivAccount {
|
||||
privKeyBytes := new([64]byte)
|
||||
copy(privKeyBytes[:32], CRandBytes(32))
|
||||
pubKeyBytes := ed25519.MakePublicKey(privKeyBytes)
|
||||
pubKey := PubKeyEd25519(*pubKeyBytes)
|
||||
privKey := PrivKeyEd25519(*privKeyBytes)
|
||||
return &PrivAccount{
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
PrivKey: privKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Generates 32 priv key bytes from secret
|
||||
func GenPrivKeyBytesFromSecret(secret string) []byte {
|
||||
return wire.BinarySha256(secret) // Not Ripemd160 because we want 32 bytes.
|
||||
}
|
||||
|
||||
// Generates a new account with private key from SHA256 hash of a secret
|
||||
func GenPrivAccountFromSecret(secret string) *PrivAccount {
|
||||
privKey32 := GenPrivKeyBytesFromSecret(secret)
|
||||
privKeyBytes := new([64]byte)
|
||||
copy(privKeyBytes[:32], privKey32)
|
||||
pubKeyBytes := ed25519.MakePublicKey(privKeyBytes)
|
||||
pubKey := PubKeyEd25519(*pubKeyBytes)
|
||||
privKey := PrivKeyEd25519(*privKeyBytes)
|
||||
return &PrivAccount{
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
PrivKey: privKey,
|
||||
}
|
||||
}
|
||||
|
||||
func GenPrivAccountFromPrivKeyBytes(privKeyBytes []byte) *PrivAccount {
|
||||
if len(privKeyBytes) != 64 {
|
||||
PanicSanity(Fmt("Expected 64 bytes but got %v", len(privKeyBytes)))
|
||||
}
|
||||
var privKeyArray [64]byte
|
||||
copy(privKeyArray[:], privKeyBytes)
|
||||
pubKeyBytes := ed25519.MakePublicKey(&privKeyArray)
|
||||
pubKey := PubKeyEd25519(*pubKeyBytes)
|
||||
privKey := PrivKeyEd25519(privKeyArray)
|
||||
return &PrivAccount{
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
PrivKey: privKey,
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/tendermint/ed25519"
|
||||
"github.com/tendermint/ed25519/extra25519"
|
||||
"github.com/tendermint/go-wire"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
// PrivKey is part of PrivAccount and state.PrivValidator.
|
||||
type PrivKey interface {
|
||||
Sign(msg []byte) Signature
|
||||
PubKey() PubKey
|
||||
}
|
||||
|
||||
// Types of PrivKey implementations
|
||||
const (
|
||||
PrivKeyTypeEd25519 = byte(0x01)
|
||||
)
|
||||
|
||||
// for wire.readReflect
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ PrivKey }{},
|
||||
wire.ConcreteType{PrivKeyEd25519{}, PrivKeyTypeEd25519},
|
||||
)
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// Implements PrivKey
|
||||
type PrivKeyEd25519 [64]byte
|
||||
|
||||
func (key PrivKeyEd25519) Sign(msg []byte) Signature {
|
||||
privKeyBytes := [64]byte(key)
|
||||
signatureBytes := ed25519.Sign(&privKeyBytes, msg)
|
||||
return SignatureEd25519(*signatureBytes)
|
||||
}
|
||||
|
||||
func (privKey PrivKeyEd25519) PubKey() PubKey {
|
||||
privKeyBytes := [64]byte(privKey)
|
||||
return PubKeyEd25519(*ed25519.MakePublicKey(&privKeyBytes))
|
||||
}
|
||||
|
||||
func (privKey PrivKeyEd25519) ToCurve25519() *[32]byte {
|
||||
keyCurve25519 := new([32]byte)
|
||||
privKeyBytes := [64]byte(privKey)
|
||||
extra25519.PrivateKeyToCurve25519(keyCurve25519, &privKeyBytes)
|
||||
return keyCurve25519
|
||||
}
|
||||
|
||||
func (privKey PrivKeyEd25519) String() string {
|
||||
return Fmt("PrivKeyEd25519{*****}")
|
||||
}
|
||||
|
||||
// Deterministically generates new priv-key bytes from key.
|
||||
func (key PrivKeyEd25519) Generate(index int) PrivKeyEd25519 {
|
||||
newBytes := wire.BinarySha256(struct {
|
||||
PrivKey [64]byte
|
||||
Index int
|
||||
}{key, index})
|
||||
var newKey [64]byte
|
||||
copy(newKey[:], newBytes)
|
||||
return PrivKeyEd25519(newKey)
|
||||
}
|
||||
|
||||
func GenPrivKeyEd25519() PrivKeyEd25519 {
|
||||
privKeyBytes := new([64]byte)
|
||||
copy(privKeyBytes[:32], CRandBytes(32))
|
||||
ed25519.MakePublicKey(privKeyBytes)
|
||||
return PrivKeyEd25519(*privKeyBytes)
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
|
||||
package account
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/tendermint/ed25519"
|
||||
"github.com/tendermint/ed25519/extra25519"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
"github.com/tendermint/go-wire"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
// PubKey is part of Account and Validator.
|
||||
type PubKey interface {
|
||||
Address() []byte
|
||||
VerifyBytes(msg []byte, sig Signature) bool
|
||||
}
|
||||
|
||||
// Types of PubKey implementations
|
||||
const (
|
||||
PubKeyTypeEd25519 = byte(0x01)
|
||||
)
|
||||
|
||||
// for wire.readReflect
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ PubKey }{},
|
||||
wire.ConcreteType{PubKeyEd25519{}, PubKeyTypeEd25519},
|
||||
)
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// Implements PubKey
|
||||
type PubKeyEd25519 [32]byte
|
||||
|
||||
// TODO: Slicing the array gives us length prefixing but loses the type byte.
|
||||
// Revisit if we add more pubkey types.
|
||||
// For now, we artificially append the type byte in front to give us backwards
|
||||
// compatibility for when the pubkey wasn't fixed length array
|
||||
func (pubKey PubKeyEd25519) Address() []byte {
|
||||
w, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
wire.WriteBinary(pubKey[:], w, n, err)
|
||||
if *err != nil {
|
||||
PanicCrisis(*err)
|
||||
}
|
||||
// append type byte
|
||||
encodedPubkey := append([]byte{1}, w.Bytes()...)
|
||||
hasher := ripemd160.New()
|
||||
hasher.Write(encodedPubkey) // does not error
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
// TODO: Consider returning a reason for failure, or logging a runtime type mismatch.
|
||||
func (pubKey PubKeyEd25519) VerifyBytes(msg []byte, sig_ Signature) bool {
|
||||
sig, ok := sig_.(SignatureEd25519)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
pubKeyBytes := [32]byte(pubKey)
|
||||
sigBytes := [64]byte(sig)
|
||||
return ed25519.Verify(&pubKeyBytes, msg, &sigBytes)
|
||||
}
|
||||
|
||||
// For use with golang/crypto/nacl/box
|
||||
// If error, returns nil.
|
||||
func (pubKey PubKeyEd25519) ToCurve25519() *[32]byte {
|
||||
keyCurve25519, pubKeyBytes := new([32]byte), [32]byte(pubKey)
|
||||
ok := extra25519.PublicKeyToCurve25519(keyCurve25519, &pubKeyBytes)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return keyCurve25519
|
||||
}
|
||||
|
||||
func (pubKey PubKeyEd25519) String() string {
|
||||
return Fmt("PubKeyEd25519{%X}", pubKey[:])
|
||||
}
|
||||
|
||||
// Must return the full bytes in hex.
|
||||
// Used for map keying, etc.
|
||||
func (pubKey PubKeyEd25519) KeyString() string {
|
||||
return Fmt("%X", pubKey[:])
|
||||
}
|
||||
|
||||
func (pubKey PubKeyEd25519) Equals(other PubKey) bool {
|
||||
if otherEd, ok := other.(PubKeyEd25519); ok {
|
||||
return bytes.Equal(pubKey[:], otherEd[:])
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
// Signature is a part of Txs and consensus Votes.
|
||||
type Signature interface {
|
||||
IsZero() bool
|
||||
String() string
|
||||
}
|
||||
|
||||
// Types of Signature implementations
|
||||
const (
|
||||
SignatureTypeEd25519 = byte(0x01)
|
||||
)
|
||||
|
||||
// for wire.readReflect
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ Signature }{},
|
||||
wire.ConcreteType{SignatureEd25519{}, SignatureTypeEd25519},
|
||||
)
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// Implements Signature
|
||||
type SignatureEd25519 [64]byte
|
||||
|
||||
func (sig SignatureEd25519) IsZero() bool { return len(sig) == 0 }
|
||||
|
||||
func (sig SignatureEd25519) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) }
|
@ -1,71 +0,0 @@
|
||||
|
||||
package account
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/ed25519"
|
||||
"github.com/tendermint/go-wire"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
func TestSignAndValidate(t *testing.T) {
|
||||
|
||||
privAccount := GenPrivAccount()
|
||||
pubKey := privAccount.PubKey
|
||||
privKey := privAccount.PrivKey
|
||||
|
||||
msg := CRandBytes(128)
|
||||
sig := privKey.Sign(msg)
|
||||
t.Logf("msg: %X, sig: %X", msg, sig)
|
||||
|
||||
// Test the signature
|
||||
if !pubKey.VerifyBytes(msg, sig) {
|
||||
t.Errorf("Account message signature verification failed")
|
||||
}
|
||||
|
||||
// Mutate the signature, just one bit.
|
||||
sigEd := sig.(SignatureEd25519)
|
||||
sigEd[0] ^= byte(0x01)
|
||||
sig = Signature(sigEd)
|
||||
|
||||
if pubKey.VerifyBytes(msg, sig) {
|
||||
t.Errorf("Account message signature verification should have failed but passed instead")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryDecode(t *testing.T) {
|
||||
|
||||
privAccount := GenPrivAccount()
|
||||
pubKey := privAccount.PubKey
|
||||
privKey := privAccount.PrivKey
|
||||
|
||||
msg := CRandBytes(128)
|
||||
sig := privKey.Sign(msg)
|
||||
t.Logf("msg: %X, sig: %X", msg, sig)
|
||||
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
wire.WriteBinary(sig, buf, n, err)
|
||||
if *err != nil {
|
||||
t.Fatalf("Failed to write Signature: %v", err)
|
||||
}
|
||||
|
||||
if len(buf.Bytes()) != ed25519.SignatureSize+1 {
|
||||
// 1 byte TypeByte, 64 bytes signature bytes
|
||||
t.Fatalf("Unexpected signature write size: %v", len(buf.Bytes()))
|
||||
}
|
||||
if buf.Bytes()[0] != SignatureTypeEd25519 {
|
||||
t.Fatalf("Unexpected signature type byte")
|
||||
}
|
||||
|
||||
sig2, ok := wire.ReadBinary(SignatureEd25519{}, buf, n, err).(SignatureEd25519)
|
||||
if !ok || *err != nil {
|
||||
t.Fatalf("Failed to read Signature: %v", err)
|
||||
}
|
||||
|
||||
// Test the signature
|
||||
if !pubKey.VerifyBytes(msg, sig2) {
|
||||
t.Errorf("Account message signature verification failed")
|
||||
}
|
||||
}
|
@ -8,11 +8,11 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/go-p2p"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -217,7 +217,7 @@ FOR_LOOP:
|
||||
firstParts := first.MakePartSet()
|
||||
firstPartsHeader := firstParts.Header()
|
||||
// Finally, verify the first block using the second's validation.
|
||||
err := bcR.state.BondedValidators.VerifyValidation(
|
||||
err := bcR.state.Validators.VerifyValidation(
|
||||
bcR.state.ChainID, first.Hash(), firstPartsHeader, first.Height, second.LastValidation)
|
||||
if err != nil {
|
||||
log.Info("error in validation", "error", err)
|
||||
|
@ -2,13 +2,13 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
type AuthCommand struct {
|
||||
CommandJSONStr string
|
||||
Signatures []acm.Signature
|
||||
Signatures []crypto.Signature
|
||||
}
|
||||
|
||||
type NoncedCommand struct {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
VotingPower int64
|
||||
PubKey acm.PubKey
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
. "github.com/tendermint/tendermint/cmd/barak/types"
|
||||
)
|
||||
|
||||
func validate(signBytes []byte, validators []Validator, signatures []acm.Signature) bool {
|
||||
func validate(signBytes []byte, validators []Validator, signatures []crypto.Signature) bool {
|
||||
var signedPower int64
|
||||
var totalPower int64
|
||||
for i, val := range validators {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
btypes "github.com/tendermint/tendermint/cmd/barak/types"
|
||||
. "github.com/tendermint/go-common"
|
||||
@ -20,57 +20,57 @@ import (
|
||||
// and then it is broadcast).
|
||||
// TODO: Implement a reasonable workflow with multiple validators.
|
||||
|
||||
func StartProcess(privKey acm.PrivKey, remote string, command btypes.CommandStartProcess) (response btypes.ResponseStartProcess, err error) {
|
||||
func StartProcess(privKey crypto.PrivKey, remote string, command btypes.CommandStartProcess) (response btypes.ResponseStartProcess, err error) {
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []acm.Signature{signature}, &response)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []crypto.Signature{signature}, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func StopProcess(privKey acm.PrivKey, remote string, command btypes.CommandStopProcess) (response btypes.ResponseStopProcess, err error) {
|
||||
func StopProcess(privKey crypto.PrivKey, remote string, command btypes.CommandStopProcess) (response btypes.ResponseStopProcess, err error) {
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []acm.Signature{signature}, &response)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []crypto.Signature{signature}, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func ListProcesses(privKey acm.PrivKey, remote string, command btypes.CommandListProcesses) (response btypes.ResponseListProcesses, err error) {
|
||||
func ListProcesses(privKey crypto.PrivKey, remote string, command btypes.CommandListProcesses) (response btypes.ResponseListProcesses, err error) {
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []acm.Signature{signature}, &response)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []crypto.Signature{signature}, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func OpenListener(privKey acm.PrivKey, remote string, command btypes.CommandOpenListener) (response btypes.ResponseOpenListener, err error) {
|
||||
func OpenListener(privKey crypto.PrivKey, remote string, command btypes.CommandOpenListener) (response btypes.ResponseOpenListener, err error) {
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []acm.Signature{signature}, &response)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []crypto.Signature{signature}, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func CloseListener(privKey acm.PrivKey, remote string, command btypes.CommandCloseListener) (response btypes.ResponseCloseListener, err error) {
|
||||
func CloseListener(privKey crypto.PrivKey, remote string, command btypes.CommandCloseListener) (response btypes.ResponseCloseListener, err error) {
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []acm.Signature{signature}, &response)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []crypto.Signature{signature}, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func DownloadFile(privKey acm.PrivKey, remote string, command btypes.CommandServeFile, outPath string) (n int64, err error) {
|
||||
func DownloadFile(privKey crypto.PrivKey, remote string, command btypes.CommandServeFile, outPath string) (n int64, err error) {
|
||||
// Create authCommandJSONBytes
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
@ -79,7 +79,7 @@ func DownloadFile(privKey acm.PrivKey, remote string, command btypes.CommandServ
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
authCommand := btypes.AuthCommand{
|
||||
CommandJSONStr: string(commandBytes),
|
||||
Signatures: []acm.Signature{signature},
|
||||
Signatures: []crypto.Signature{signature},
|
||||
}
|
||||
authCommandJSONBytes := wire.JSONBytes(authCommand)
|
||||
// Make request and write to outPath.
|
||||
@ -100,13 +100,13 @@ func DownloadFile(privKey acm.PrivKey, remote string, command btypes.CommandServ
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func Quit(privKey acm.PrivKey, remote string, command btypes.CommandQuit) (response btypes.ResponseQuit, err error) {
|
||||
func Quit(privKey crypto.PrivKey, remote string, command btypes.CommandQuit) (response btypes.ResponseQuit, err error) {
|
||||
nonce, err := GetNonce(remote)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
commandBytes, signature := SignCommand(privKey, nonce+1, command)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []acm.Signature{signature}, &response)
|
||||
_, err = RunAuthCommand(remote, commandBytes, []crypto.Signature{signature}, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ func GetStatus(remote string) (response btypes.ResponseStatus, err error) {
|
||||
}
|
||||
|
||||
// Each developer runs this
|
||||
func SignCommand(privKey acm.PrivKey, nonce int64, command btypes.Command) ([]byte, acm.Signature) {
|
||||
func SignCommand(privKey crypto.PrivKey, nonce int64, command btypes.Command) ([]byte, crypto.Signature) {
|
||||
noncedCommand := btypes.NoncedCommand{
|
||||
Nonce: nonce,
|
||||
Command: command,
|
||||
@ -139,7 +139,7 @@ func SignCommand(privKey acm.PrivKey, nonce int64, command btypes.Command) ([]by
|
||||
}
|
||||
|
||||
// Somebody aggregates the signatures and calls this.
|
||||
func RunAuthCommand(remote string, commandJSONBytes []byte, signatures []acm.Signature, dest interface{}) (interface{}, error) {
|
||||
func RunAuthCommand(remote string, commandJSONBytes []byte, signatures []crypto.Signature, dest interface{}) (interface{}, error) {
|
||||
authCommand := btypes.AuthCommand{
|
||||
CommandJSONStr: string(commandJSONBytes),
|
||||
Signatures: signatures,
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
btypes "github.com/tendermint/tendermint/cmd/barak/types"
|
||||
. "github.com/tendermint/go-common"
|
||||
cfg "github.com/tendermint/go-config"
|
||||
@ -29,7 +29,7 @@ func remoteNick(remote string) string {
|
||||
|
||||
var Config = struct {
|
||||
Remotes []string
|
||||
PrivKey acm.PrivKey
|
||||
PrivKey crypto.PrivKey
|
||||
}{}
|
||||
|
||||
func main() {
|
||||
|
@ -1,10 +0,0 @@
|
||||
Simulate the Economy of Texas.
|
||||
|
||||
```bash
|
||||
sim_txs --priv-key "PRIV_KEY_HEX" --num-accounts 1000 --remote localhost:46657
|
||||
```
|
||||
|
||||
The above uses the rpc-host to fetch the account state for the account with given priv-key,
|
||||
then deterministically generates num-accounts more accounts to spread the coins around.
|
||||
|
||||
It's a test utility.
|
@ -1,169 +0,0 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
cclient "github.com/tendermint/tendermint/rpc/core_client"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
const Version = "0.0.1"
|
||||
const sleepSeconds = 1 // Every second
|
||||
|
||||
// Parse command-line options
|
||||
func parseFlags() (privKeyHex string, numAccounts int, remote string) {
|
||||
var version bool
|
||||
flag.StringVar(&privKeyHex, "priv-key", "", "Private key bytes in HEX")
|
||||
flag.IntVar(&numAccounts, "num-accounts", 1000, "Deterministically generates this many sub-accounts")
|
||||
flag.StringVar(&remote, "remote", "localhost:46657", "Remote RPC host:port")
|
||||
flag.BoolVar(&version, "version", false, "Version")
|
||||
flag.Parse()
|
||||
if version {
|
||||
Exit(Fmt("sim_txs version %v", Version))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Read options
|
||||
privKeyHex, numAccounts, remote := parseFlags()
|
||||
|
||||
// Print args.
|
||||
// fmt.Println(privKeyHex, numAccounts, remote)
|
||||
|
||||
privKeyBytes, err := hex.DecodeString(privKeyHex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
root := acm.GenPrivAccountFromPrivKeyBytes(privKeyBytes)
|
||||
fmt.Println("Computed address: %X", root.Address)
|
||||
|
||||
// Get root account.
|
||||
rootAccount, err := getAccount(remote, root.Address)
|
||||
if err != nil {
|
||||
fmt.Println(Fmt("Root account %X does not exist: %v", root.Address, err))
|
||||
return
|
||||
} else {
|
||||
fmt.Println("Root account", rootAccount)
|
||||
}
|
||||
|
||||
// Load all accounts
|
||||
accounts := make([]*acm.Account, numAccounts+1)
|
||||
accounts[0] = rootAccount
|
||||
privAccounts := make([]*acm.PrivAccount, numAccounts+1)
|
||||
privAccounts[0] = root
|
||||
for i := 1; i < numAccounts+1; i++ {
|
||||
privAccounts[i] = root.Generate(i)
|
||||
account, err := getAccount(remote, privAccounts[i].Address)
|
||||
if err != nil {
|
||||
fmt.Println("Error", err)
|
||||
return
|
||||
} else {
|
||||
accounts[i] = account
|
||||
}
|
||||
}
|
||||
|
||||
// Test: send from root to accounts[1]
|
||||
sendTx := makeRandomTransaction(10, rootAccount.Sequence+1, root, 2, accounts)
|
||||
fmt.Println(sendTx)
|
||||
|
||||
wsClient := cclient.NewWSClient("ws://" + remote + "/websocket")
|
||||
_, err = wsClient.Start()
|
||||
if err != nil {
|
||||
Exit(Fmt("Failed to establish websocket connection: %v", err))
|
||||
}
|
||||
wsClient.Subscribe(types.EventStringAccInput(sendTx.Inputs[0].Address))
|
||||
|
||||
go func() {
|
||||
for {
|
||||
foo := <-wsClient.EventsCh
|
||||
fmt.Println("!!", foo)
|
||||
}
|
||||
}()
|
||||
|
||||
err = broadcastSendTx(remote, sendTx)
|
||||
if err != nil {
|
||||
Exit(Fmt("Failed to broadcast SendTx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Trap signal
|
||||
TrapSignal(func() {
|
||||
fmt.Println("sim_txs shutting down")
|
||||
})
|
||||
}
|
||||
|
||||
func getAccount(remote string, address []byte) (*acm.Account, error) {
|
||||
// var account *acm.Account = new(acm.Account)
|
||||
account, err := rpcclient.Call("http://"+remote, "get_account", []interface{}{address}, (*acm.Account)(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if account.(*acm.Account) == nil {
|
||||
return &acm.Account{Address: address}, nil
|
||||
} else {
|
||||
return account.(*acm.Account), nil
|
||||
}
|
||||
}
|
||||
|
||||
func broadcastSendTx(remote string, sendTx *types.SendTx) error {
|
||||
receipt, err := rpcclient.Call("http://"+remote, "broadcast_tx", []interface{}{sendTx}, (*ctypes.Receipt)(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Broadcast receipt:", receipt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make a random send transaction from srcIndex to N other accounts.
|
||||
// balance: balance to send from input
|
||||
// sequence: sequence to sign with
|
||||
// inputPriv: input privAccount
|
||||
func makeRandomTransaction(balance int64, sequence int, inputPriv *acm.PrivAccount, sendCount int, accounts []*acm.Account) *types.SendTx {
|
||||
|
||||
if sendCount >= len(accounts) {
|
||||
PanicSanity("Cannot make tx with sendCount >= len(accounts)")
|
||||
}
|
||||
|
||||
// Remember which accounts were chosen
|
||||
accMap := map[string]struct{}{}
|
||||
accMap[string(inputPriv.Address)] = struct{}{}
|
||||
|
||||
// Find a selection of accounts to send to
|
||||
outputs := []*acm.Account{}
|
||||
for i := 0; i < sendCount; i++ {
|
||||
for {
|
||||
idx := RandInt() % len(accounts)
|
||||
account := accounts[idx]
|
||||
if _, ok := accMap[string(account.Address)]; ok {
|
||||
continue
|
||||
}
|
||||
accMap[string(account.Address)] = struct{}{}
|
||||
outputs = append(outputs, account)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Construct SendTx
|
||||
sendTx := types.NewSendTx()
|
||||
err := sendTx.AddInputWithNonce(inputPriv.PubKey, balance, sequence)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, output := range outputs {
|
||||
sendTx.AddOutput(output.Address, balance/int64(len(outputs)))
|
||||
}
|
||||
|
||||
// Sign SendTx
|
||||
sendTx.SignInput("tendermint_testnet_9", 0, inputPriv)
|
||||
|
||||
return sendTx
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
func gen_account() {
|
||||
|
||||
secret, err := Prompt(`Enter your desired secret, or just hit <Enter> to generate a random account.
|
||||
IMPORTANT: If you don't know what a dictionary attack is, just hit Enter
|
||||
> `, "")
|
||||
if err != nil {
|
||||
Exit(Fmt("Not sure what happened: %v", err))
|
||||
}
|
||||
|
||||
if secret == "" {
|
||||
privAccount := acm.GenPrivAccount()
|
||||
privAccountJSONBytes := wire.JSONBytes(privAccount)
|
||||
fmt.Printf(`Generated a new random account!
|
||||
|
||||
%v
|
||||
|
||||
`,
|
||||
string(privAccountJSONBytes),
|
||||
)
|
||||
} else {
|
||||
privAccount := acm.GenPrivAccountFromSecret(secret)
|
||||
privAccountJSONBytes := wire.JSONBytes(privAccount)
|
||||
fmt.Printf(`Generated a new account from secret: [%v]!
|
||||
|
||||
%v
|
||||
|
||||
`,
|
||||
secret,
|
||||
string(privAccountJSONBytes),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
cclient "github.com/tendermint/tendermint/rpc/core_client"
|
||||
)
|
||||
|
||||
func get_account() {
|
||||
addrHex, err := Prompt("Enter the address of the account in HEX (e.g. 9FCBA7F840A0BFEBBE755E853C9947270A912D04):\n> ", "")
|
||||
if err != nil {
|
||||
Exit(Fmt("Error: %v", err))
|
||||
}
|
||||
cli := cclient.NewClient("http://localhost:46657", "JSONRPC")
|
||||
address, err := hex.DecodeString(addrHex)
|
||||
if err != nil {
|
||||
Exit(Fmt("Address was not hex: %v", addrHex))
|
||||
}
|
||||
res, err := cli.GetAccount(address)
|
||||
if res == nil {
|
||||
Exit(Fmt("Account does not exist: %X", address))
|
||||
}
|
||||
if err != nil {
|
||||
Exit(Fmt("Error fetching account: %v", err))
|
||||
}
|
||||
acc := res.Account
|
||||
|
||||
fmt.Printf(`
|
||||
Address: %X
|
||||
PubKey: %v
|
||||
Sequence: %v
|
||||
Balance: %v
|
||||
Permissions: %v
|
||||
`,
|
||||
acc.Address,
|
||||
acc.PubKey,
|
||||
acc.Sequence,
|
||||
acc.Balance,
|
||||
acc.Permissions)
|
||||
}
|
@ -19,10 +19,7 @@ func main() {
|
||||
Commands:
|
||||
node Run the tendermint node
|
||||
show_validator Show this node's validator info
|
||||
gen_account Generate new account keypair
|
||||
gen_validator Generate new validator keypair
|
||||
get_account Get account balance
|
||||
send_tx Sign and publish a SendTx
|
||||
probe_upnp Test UPnP functionality
|
||||
version Show version info
|
||||
`)
|
||||
@ -39,14 +36,8 @@ Commands:
|
||||
node.RunNode()
|
||||
case "show_validator":
|
||||
show_validator()
|
||||
case "gen_account":
|
||||
gen_account()
|
||||
case "gen_validator":
|
||||
gen_validator()
|
||||
case "get_account":
|
||||
get_account()
|
||||
case "send_tx":
|
||||
send_tx()
|
||||
case "probe_upnp":
|
||||
probe_upnp()
|
||||
case "unsafe_reset_priv_validator":
|
||||
|
@ -1,120 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
cclient "github.com/tendermint/tendermint/rpc/core_client"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func getString(prompt string) string {
|
||||
input, err := Prompt(prompt, "")
|
||||
if err != nil {
|
||||
Exit(Fmt("Error reading input: %v", err))
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func getByteSliceFromHex(prompt string) []byte {
|
||||
input := getString(prompt)
|
||||
bytes, err := hex.DecodeString(input)
|
||||
if err != nil {
|
||||
Exit(Fmt("Not in hex format: %v\nError: %v\n", input, err))
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func getInt(prompt string) int {
|
||||
input := getString(prompt)
|
||||
i, err := strconv.Atoi(input)
|
||||
if err != nil {
|
||||
Exit(Fmt("Not a valid int64 amount: %v\nError: %v\n", input, err))
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func getInt64(prompt string) int64 {
|
||||
return int64(getInt(prompt))
|
||||
}
|
||||
|
||||
func send_tx() {
|
||||
|
||||
// Get PrivAccount
|
||||
var privAccount *acm.PrivAccount
|
||||
secret := getString("Enter your secret, or just hit <Enter> to enter a private key in HEX.\n> ")
|
||||
if secret == "" {
|
||||
privKeyBytes := getByteSliceFromHex("Enter your private key in HEX (e.g. E353CAD81134A301A542AEBE2D2E4EF1A64A145117EC72743AE9C9D171A4AA69F3A7DD670A9E9307AAED000D97D5B3C07D90276BFCEEDA5ED11DA089A4E87A81):\n> ")
|
||||
privAccount = acm.GenPrivAccountFromPrivKeyBytes(privKeyBytes)
|
||||
} else {
|
||||
// Auto-detect private key hex
|
||||
if len(secret) == 128 {
|
||||
privKeyBytes, err := hex.DecodeString(secret)
|
||||
if err == nil {
|
||||
fmt.Println("Detected priv-key bytes...")
|
||||
privAccount = acm.GenPrivAccountFromPrivKeyBytes(privKeyBytes)
|
||||
} else {
|
||||
fmt.Println("That's a long seed...")
|
||||
privAccount = acm.GenPrivAccountFromSecret(secret)
|
||||
}
|
||||
} else {
|
||||
privAccount = acm.GenPrivAccountFromSecret(secret)
|
||||
}
|
||||
}
|
||||
pubKey := privAccount.PubKey
|
||||
|
||||
// Get account data
|
||||
cli := cclient.NewClient("http://localhost:46657", "JSONRPC")
|
||||
res, err := cli.GetAccount(privAccount.Address)
|
||||
if err != nil {
|
||||
Exit(Fmt("Error fetching account: %v", err))
|
||||
}
|
||||
if res == nil {
|
||||
Exit(Fmt("No account was found with that secret/private-key"))
|
||||
}
|
||||
inputAcc := res.Account
|
||||
fmt.Printf(`
|
||||
Source account:
|
||||
Address: %X
|
||||
PubKey: %v
|
||||
Sequence: %v
|
||||
Balance: %v
|
||||
Permissions: %v
|
||||
`,
|
||||
inputAcc.Address,
|
||||
pubKey,
|
||||
inputAcc.Sequence,
|
||||
inputAcc.Balance,
|
||||
inputAcc.Permissions)
|
||||
|
||||
output := getByteSliceFromHex("\nEnter the output address in HEX:\n> ")
|
||||
amount := getInt64("Enter the amount to send:\n> ")
|
||||
|
||||
// Construct transaction
|
||||
tx := types.NewSendTx()
|
||||
tx.AddInputWithNonce(pubKey, amount, inputAcc.Sequence+1)
|
||||
tx.AddOutput(output, amount)
|
||||
tx.Inputs[0].Signature = privAccount.Sign(config.GetString("chain_id"), tx)
|
||||
fmt.Println("Signed SendTx!: ", tx)
|
||||
|
||||
// Sign up for events
|
||||
wsCli := cclient.NewWSClient("ws://localhost:46657/websocket")
|
||||
wsCli.Start()
|
||||
err = wsCli.Subscribe(types.EventStringAccInput(inputAcc.Address))
|
||||
if err != nil {
|
||||
Exit(Fmt("Error subscribing to account send event: %v", err))
|
||||
}
|
||||
|
||||
// Broadcast transaction
|
||||
_, err = cli.BroadcastTx(tx)
|
||||
if err != nil {
|
||||
Exit(Fmt("Error broadcasting transaction: %v", err))
|
||||
}
|
||||
fmt.Println("Waiting for confirmation...")
|
||||
|
||||
_ = <-wsCli.EventsCh
|
||||
fmt.Println("Confirmed.")
|
||||
}
|
@ -66,7 +66,6 @@ func GetConfig(rootDir string) cfg.Config {
|
||||
mapConfig.SetDefault("rpc_laddr", "0.0.0.0:46657")
|
||||
mapConfig.SetDefault("prof_laddr", "")
|
||||
mapConfig.SetDefault("revision_file", rootDir+"/revision")
|
||||
mapConfig.SetDefault("local_routing", false)
|
||||
return mapConfig
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
// Import this in all *_test.go files to initialize ~/.tendermint_test.
|
||||
|
||||
package tendermint_test
|
||||
@ -72,7 +71,6 @@ func GetConfig(rootDir string) cfg.Config {
|
||||
mapConfig.SetDefault("rpc_laddr", "0.0.0.0:36657")
|
||||
mapConfig.SetDefault("prof_laddr", "")
|
||||
mapConfig.SetDefault("revision_file", rootDir+"/revision")
|
||||
mapConfig.SetDefault("local_routing", false)
|
||||
return mapConfig
|
||||
}
|
||||
|
||||
|
@ -1,153 +1,3 @@
|
||||
/*
|
||||
|
||||
* Terms:
|
||||
|
||||
NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep).
|
||||
|
||||
To "prevote/precommit" something means to broadcast a prevote/precommit vote for something.
|
||||
|
||||
(H,R) means a particular height H and round R. A vote "at (H,R)" is a vote signed with (H,R).
|
||||
|
||||
* Proposals:
|
||||
|
||||
A proposal is signed and published by the designated proposer at each round.
|
||||
A proposal at (H,R) is composed of a proposed block of height H, and optionally a POL round number.
|
||||
The POL round number R' (where R' < R) is set to the latest POL round known to the proposer.
|
||||
If the proposer is locked on a block, it must include a POL round for the proposal.
|
||||
|
||||
* POL and Justification of votes:
|
||||
|
||||
A set of +2/3 of prevotes for a particular block or <nil> at (H,R) is called a POL (proof-of-lock).
|
||||
A POL for <nil> might instead be called a proof-of-unlock, but it's better to have a single terminology for both.
|
||||
|
||||
Each precommit which changes the lock at round R must be justified by a POL
|
||||
where +2/3 prevoted for some block or <nil> at some round, equal to or less than R,
|
||||
but greater than the last round at which the lock was changed.
|
||||
|
||||
POL = Proof-of-Lock = +2/3 prevotes for block B (or +2/3 prevotes for <nil>) at (H,R)
|
||||
|
||||
lastLockChangeRound < POLRound <= newLockChangeRound
|
||||
|
||||
Without the POLRound <= newLockChangeRound condition, an unlock would be possible from a
|
||||
future condition that hasn't happened yet, so it destroys deterministic accountability.
|
||||
|
||||
The point of the above inequality is to ensure that changes in the lock (locking/unlocking/lock-changing)
|
||||
are always justified by something that happened "in the past" by round numbers, so if there is a problem,
|
||||
we can deterministically figure out "when it was caused" and by who.
|
||||
|
||||
If there is a blockchain halt or fork, the blame will fall on +1/3 of Byzantine voting power =
|
||||
who cannot push the blame into earlier rounds. (See lemma 4).
|
||||
|
||||
* Block commits:
|
||||
|
||||
The set of +2/3 of precommits at the same round for the same block is called a commit.
|
||||
|
||||
A block contains the last block's commit which is comprised of +2/3 precommit votes at (H-1,R).
|
||||
While all the precommits in the commit are from the same height & round (ordered by validator index),
|
||||
some precommits may be absent (e.g. if the validator's precommit vote didn't reach the proposer in time),
|
||||
or some precommits may be for different blockhashes for the last block hash (which is fine).
|
||||
|
||||
* Consensus State Machine Overview:
|
||||
|
||||
During NewHeight/NewRound/Propose/Prevote/Precommit:
|
||||
* Nodes gossip the proposal block proposed by the designated proposer at round.
|
||||
* Nodes gossip prevotes/precommits at rounds [0...currentRound+1] (currentRound+1 to allow round-skipping)
|
||||
* Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed.
|
||||
* Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup)
|
||||
|
||||
Upon each state transition, the height/round/step is broadcast to neighboring peers.
|
||||
|
||||
* NewRound(height:H,round:R):
|
||||
* Set up new round. --> goto Propose(H,R)
|
||||
* NOTE: Not much happens in this step. It exists for clarity.
|
||||
|
||||
* Propose(height:H,round:R):
|
||||
* Upon entering Propose:
|
||||
* The designated proposer proposes a block at (H,R).
|
||||
* The Propose step ends:
|
||||
* After `timeoutPropose` after entering Propose. --> goto Prevote(H,R)
|
||||
* After receiving proposal block and all POL prevotes. --> goto Prevote(H,R)
|
||||
* After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
|
||||
* After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
|
||||
* After +2/3 precommits received for a particular block. --> goto Commit(H)
|
||||
|
||||
* Prevote(height:H,round:R):
|
||||
* Upon entering Prevote, each validator broadcasts its prevote vote.
|
||||
* If the validator is locked on a block, it prevotes that.
|
||||
* Else, if the proposed block from Propose(H,R) is good, it prevotes that.
|
||||
* Else, if the proposal is invalid or wasn't received on time, it prevotes <nil>.
|
||||
* The Prevote step ends:
|
||||
* After +2/3 prevotes for a particular block or <nil>. --> goto Precommit(H,R)
|
||||
* After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto Precommit(H,R)
|
||||
* After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
|
||||
* After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
|
||||
* After +2/3 precommits received for a particular block. --> goto Commit(H)
|
||||
|
||||
* Precommit(height:H,round:R):
|
||||
* Upon entering Precommit, each validator broadcasts its precommit vote.
|
||||
* If the validator had seen +2/3 of prevotes for a particular block from Prevote(H,R),
|
||||
it locks (changes lock to) that block and precommits that block.
|
||||
* Else, if the validator had seen +2/3 of prevotes for <nil>, it unlocks and precommits <nil>.
|
||||
* Else, if +2/3 of prevotes for a particular block or <nil> is not received on time,
|
||||
it precommits <nil>.
|
||||
* The Precommit step ends:
|
||||
* After +2/3 precommits for a particular block. --> goto Commit(H)
|
||||
* After +2/3 precommits for <nil>. --> goto NewRound(H,R+1)
|
||||
* After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto NewRound(H,R+1)
|
||||
* After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
|
||||
* After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
|
||||
|
||||
* Commit(height:H):
|
||||
* Set CommitTime = now
|
||||
* Wait until block is received. --> goto NewHeight(H+1)
|
||||
|
||||
* NewHeight(height:H):
|
||||
* Move Precommits to LastCommit and increment height.
|
||||
* Set StartTime = CommitTime+timeoutCommit
|
||||
* Wait until `StartTime` to receive straggler commits. --> goto NewRound(H,0)
|
||||
|
||||
* Proof of Safety:
|
||||
If a good validator commits at round R, it's because it saw +2/3 of precommits at round R.
|
||||
This implies that (assuming tolerance bounds) +1/3 of honest nodes are still locked at round R+1.
|
||||
These locked validators will remain locked until they see +2/3 prevote for something
|
||||
else, but this won't happen because +1/3 are locked and honest.
|
||||
|
||||
* Proof of Liveness:
|
||||
Lemma 1: If +1/3 good nodes are locked on two different blocks, the proposers' POLRound will
|
||||
eventually cause nodes locked from the earlier round to unlock.
|
||||
-> `timeoutProposalR` increments with round R, while the block.size && POL prevote size
|
||||
are fixed, so eventually we'll be able to "fully gossip" the block & POL.
|
||||
TODO: cap the block.size at something reasonable.
|
||||
Lemma 2: If a good node is at round R, neighboring good nodes will soon catch up to round R.
|
||||
Lemma 3: If a node at (H,R) receives +2/3 prevotes for a block (or +2/3 for <nil>) at (H,R+1),
|
||||
it will enter NewRound(H,R+1).
|
||||
Lemma 4: Terminal conditions imply the existence of deterministic accountability, for
|
||||
a synchronous (fixed-duration) protocol extension (judgement).
|
||||
TODO: define terminal conditions (fork and non-decision).
|
||||
TODO: define new assumptions for the synchronous judgement period.
|
||||
|
||||
|
||||
+-------------------------------------+
|
||||
v |(Wait til `CommmitTime+timeoutCommit`)
|
||||
+-----------+ +-----+-----+
|
||||
+----------> | Propose +--------------+ | NewHeight |
|
||||
| +-----------+ | +-----------+
|
||||
| | ^
|
||||
|(Else, after timeoutPrecommit) v |
|
||||
+-----+-----+ +-----------+ |
|
||||
| Precommit | <------------------------+ Prevote | |
|
||||
+-----+-----+ +-----------+ |
|
||||
|(When +2/3 Precommits for block found) |
|
||||
v |
|
||||
+--------------------------------------------------------------------+
|
||||
| Commit |
|
||||
| |
|
||||
| * Set CommitTime = now; |
|
||||
| * Wait for block, then stage/save/commit block; |
|
||||
+--------------------------------------------------------------------+
|
||||
|
||||
*/
|
||||
|
||||
package consensus
|
||||
|
||||
import (
|
||||
@ -157,14 +7,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -328,7 +177,6 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto
|
||||
cs.updateToState(state)
|
||||
// Don't call scheduleRound0 yet.
|
||||
// We do that upon Start().
|
||||
cs.maybeRebond()
|
||||
cs.reconstructLastCommit(state)
|
||||
cs.BaseService = *NewBaseService(log, "ConsensusState", cs)
|
||||
return cs
|
||||
@ -340,7 +188,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) {
|
||||
if state.LastBlockHeight == 0 {
|
||||
return
|
||||
}
|
||||
lastPrecommits := types.NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators)
|
||||
lastPrecommits := types.NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastValidators)
|
||||
seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight)
|
||||
for idx, precommit := range seenValidation.Precommits {
|
||||
if precommit == nil {
|
||||
@ -425,7 +273,7 @@ func (cs *ConsensusState) updateToState(state *sm.State) {
|
||||
}
|
||||
|
||||
// Reset fields based on state.
|
||||
validators := state.BondedValidators
|
||||
validators := state.Validators
|
||||
height := state.LastBlockHeight + 1 // next desired block height
|
||||
lastPrecommits := (*types.VoteSet)(nil)
|
||||
if cs.CommitRound > -1 && cs.Votes != nil {
|
||||
@ -460,7 +308,7 @@ func (cs *ConsensusState) updateToState(state *sm.State) {
|
||||
cs.Votes = NewHeightVoteSet(height, validators)
|
||||
cs.CommitRound = -1
|
||||
cs.LastCommit = lastPrecommits
|
||||
cs.LastValidators = state.LastBondedValidators
|
||||
cs.LastValidators = state.LastValidators
|
||||
|
||||
cs.state = state
|
||||
cs.stagedBlock = nil
|
||||
@ -470,30 +318,6 @@ func (cs *ConsensusState) updateToState(state *sm.State) {
|
||||
cs.newStepCh <- cs.getRoundState()
|
||||
}
|
||||
|
||||
// If we're unbonded, broadcast RebondTx.
|
||||
func (cs *ConsensusState) maybeRebond() {
|
||||
if cs.privValidator == nil || !cs.state.UnbondingValidators.HasAddress(cs.privValidator.Address) {
|
||||
return
|
||||
}
|
||||
rebondTx := &types.RebondTx{
|
||||
Address: cs.privValidator.Address,
|
||||
Height: cs.Height,
|
||||
}
|
||||
err := cs.privValidator.SignRebondTx(cs.state.ChainID, rebondTx)
|
||||
if err == nil {
|
||||
err := cs.mempoolReactor.BroadcastTx(rebondTx)
|
||||
if err != nil {
|
||||
log.Error("Failed to broadcast RebondTx",
|
||||
"height", cs.Height, "round", cs.Round, "tx", rebondTx, "error", err)
|
||||
} else {
|
||||
log.Notice("Signed and broadcast RebondTx",
|
||||
"height", cs.Height, "round", cs.Round, "tx", rebondTx)
|
||||
}
|
||||
} else {
|
||||
log.Warn("Error signing RebondTx", "height", cs.Height, "round", cs.Round, "tx", rebondTx, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ConsensusState) SetPrivValidator(priv *types.PrivValidator) {
|
||||
cs.mtx.Lock()
|
||||
defer cs.mtx.Unlock()
|
||||
@ -1011,8 +835,6 @@ func (cs *ConsensusState) FinalizeCommit(height int) {
|
||||
// cs.StartTime is already set.
|
||||
// Schedule Round0 to start soon.
|
||||
go cs.scheduleRound0(height + 1)
|
||||
// If we're unbonded, broadcast RebondTx.
|
||||
cs.maybeRebond()
|
||||
|
||||
// By here,
|
||||
// * cs.Height has been increment to height+1
|
||||
@ -1049,7 +871,7 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal) error {
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
if !cs.Validators.Proposer().PubKey.VerifyBytes(acm.SignBytes(cs.state.ChainID, proposal), proposal.Signature) {
|
||||
if !cs.Validators.Proposer().PubKey.VerifyBytes(types.SignBytes(cs.state.ChainID, proposal), proposal.Signature) {
|
||||
return ErrInvalidProposalSignature
|
||||
}
|
||||
|
||||
@ -1098,21 +920,23 @@ func (cs *ConsensusState) AddProposalBlockPart(height int, part *types.Part) (ad
|
||||
|
||||
// Attempt to add the vote. if its a duplicate signature, dupeout the validator
|
||||
func (cs *ConsensusState) TryAddVote(valIndex int, vote *types.Vote, peerKey string) (bool, error) {
|
||||
added, address, err := cs.AddVote(valIndex, vote, peerKey)
|
||||
added, _, err := cs.AddVote(valIndex, vote, peerKey)
|
||||
if err != nil {
|
||||
// If the vote height is off, we'll just ignore it,
|
||||
// But if it's a conflicting sig, broadcast evidence tx for slashing.
|
||||
// If it's otherwise invalid, punish peer.
|
||||
if err == ErrVoteHeightMismatch {
|
||||
return added, err
|
||||
} else if errDupe, ok := err.(*types.ErrVoteConflictingSignature); ok {
|
||||
} else if _, ok := err.(*types.ErrVoteConflictingSignature); ok {
|
||||
log.Warn("Found conflicting vote. Publish evidence")
|
||||
/* XXX
|
||||
evidenceTx := &types.DupeoutTx{
|
||||
Address: address,
|
||||
VoteA: *errDupe.VoteA,
|
||||
VoteB: *errDupe.VoteB,
|
||||
}
|
||||
cs.mempoolReactor.BroadcastTx(evidenceTx) // shouldn't need to check returned err
|
||||
*/
|
||||
return added, err
|
||||
} else {
|
||||
// Probably an invalid signature. Bad peer.
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
package consensus
|
||||
|
||||
import (
|
||||
@ -1032,21 +1031,7 @@ func TestSlashingPrevotes(t *testing.T) {
|
||||
// add the conflicting vote
|
||||
signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header())
|
||||
|
||||
// conflicting vote should cause us to broadcast dupeout tx on mempool
|
||||
txs := cs1.mempoolReactor.Mempool.GetProposalTxs()
|
||||
if len(txs) != 1 {
|
||||
t.Fatal("expected to find a transaction in the mempool after double signing")
|
||||
}
|
||||
dupeoutTx, ok := txs[0].(*types.DupeoutTx)
|
||||
if !ok {
|
||||
t.Fatal("expected to find DupeoutTx in mempool after double signing")
|
||||
}
|
||||
|
||||
if !bytes.Equal(dupeoutTx.Address, cs2.privValidator.Address) {
|
||||
t.Fatalf("expected DupeoutTx for %X, got %X", cs2.privValidator.Address, dupeoutTx.Address)
|
||||
}
|
||||
|
||||
// TODO: validate the sig
|
||||
// XXX: Check for existence of Dupeout info
|
||||
}
|
||||
|
||||
func TestSlashingPrecommits(t *testing.T) {
|
||||
@ -1079,22 +1064,7 @@ func TestSlashingPrecommits(t *testing.T) {
|
||||
// add precommit from cs2
|
||||
signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header())
|
||||
|
||||
// conflicting vote should cause us to broadcast dupeout tx on mempool
|
||||
txs := cs1.mempoolReactor.Mempool.GetProposalTxs()
|
||||
if len(txs) != 1 {
|
||||
t.Fatal("expected to find a transaction in the mempool after double signing")
|
||||
}
|
||||
dupeoutTx, ok := txs[0].(*types.DupeoutTx)
|
||||
if !ok {
|
||||
t.Fatal("expected to find DupeoutTx in mempool after double signing")
|
||||
}
|
||||
|
||||
if !bytes.Equal(dupeoutTx.Address, cs2.privValidator.Address) {
|
||||
t.Fatalf("expected DupeoutTx for %X, got %X", cs2.privValidator.Address, dupeoutTx.Address)
|
||||
}
|
||||
|
||||
// TODO: validate the sig
|
||||
|
||||
// XXX: Check for existence of Dupeout info
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
@ -6,11 +6,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
dbm "github.com/tendermint/go-db"
|
||||
"github.com/tendermint/go-p2p"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
"github.com/tendermint/go-p2p"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
@ -186,10 +186,9 @@ func validatePrevoteAndPrecommit(t *testing.T, cs *ConsensusState, thisRound, lo
|
||||
|
||||
func simpleConsensusState(nValidators int) ([]*ConsensusState, []*types.PrivValidator) {
|
||||
// Get State
|
||||
state, privAccs, privVals := sm.RandGenesisState(10, true, 1000, nValidators, false, 10)
|
||||
_, _ = privAccs, privVals
|
||||
state, privVals := sm.RandGenesisState(nValidators, false, 10)
|
||||
|
||||
fmt.Println(state.BondedValidators)
|
||||
fmt.Println(state.Validators)
|
||||
|
||||
css := make([]*ConsensusState, nValidators)
|
||||
for i := 0; i < nValidators; i++ {
|
||||
@ -220,7 +219,7 @@ func simpleConsensusState(nValidators int) ([]*ConsensusState, []*types.PrivVali
|
||||
}
|
||||
|
||||
func randConsensusState() (*ConsensusState, []*types.PrivValidator) {
|
||||
state, _, privValidators := sm.RandGenesisState(20, false, 1000, 10, false, 1000)
|
||||
state, privValidators := sm.RandGenesisState(10, false, 1000)
|
||||
blockStore := bc.NewBlockStore(dbm.NewMemDB())
|
||||
mempool := mempl.NewMempool(state)
|
||||
mempoolReactor := mempl.NewMempoolReactor(mempool)
|
||||
|
312
crawler/crawl.go
312
crawler/crawl.go
@ -1,312 +0,0 @@
|
||||
package crawler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
cclient "github.com/tendermint/tendermint/rpc/core_client"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
const (
|
||||
CheckQueueBufferSize = 100
|
||||
NodeQueueBufferSize = 100
|
||||
GetPeersTickerSeconds = 5
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------------------
|
||||
// crawler.Node
|
||||
|
||||
// A node is a peer on the network
|
||||
type Node struct {
|
||||
Host string
|
||||
P2PPort uint16
|
||||
RPCPort uint16
|
||||
|
||||
failed int
|
||||
connected bool
|
||||
|
||||
client *NodeClient
|
||||
|
||||
LastSeen time.Time
|
||||
ChainID string
|
||||
BlockHeight int
|
||||
BlockHistory map[int]time.Time // when we saw each block
|
||||
NetInfo *ctypes.ResultNetInfo
|
||||
|
||||
Validator bool
|
||||
|
||||
// other peers we heard about this peer from
|
||||
heardFrom map[string]struct{}
|
||||
}
|
||||
|
||||
func (n *Node) Address() string {
|
||||
return fmt.Sprintf("%s:%d", n.Host, n.RPCPort)
|
||||
}
|
||||
|
||||
// Set the basic status and chain_id info for a node from RPC responses
|
||||
func (n *Node) SetInfo(status *ctypes.ResultStatus, netinfo *ctypes.ResultNetInfo) {
|
||||
n.LastSeen = time.Now()
|
||||
n.ChainID = status.NodeInfo.ChainID
|
||||
n.BlockHeight = status.LatestBlockHeight
|
||||
n.NetInfo = netinfo
|
||||
// n.Validator
|
||||
}
|
||||
|
||||
// A node client is used to talk to a node over rpc and websockets
|
||||
type NodeClient struct {
|
||||
rpc cclient.Client
|
||||
ws *cclient.WSClient
|
||||
}
|
||||
|
||||
// Create a new client for the node at the given addr
|
||||
func NewNodeClient(addr string) *NodeClient {
|
||||
return &NodeClient{
|
||||
rpc: cclient.NewClient("http://"+addr, "JSONRPC"),
|
||||
ws: cclient.NewWSClient("ws://" + addr + "/events"),
|
||||
}
|
||||
}
|
||||
|
||||
// A simple wrapper for mediating access to the maps
|
||||
type nodeInfo struct {
|
||||
host string // the new nodes address
|
||||
port uint16 // node's listening port
|
||||
from string // the peer that told us about this node
|
||||
connected bool // move node from nodePool to nodes
|
||||
disconnected bool // move node from nodes to nodePool
|
||||
}
|
||||
|
||||
func (ni nodeInfo) unpack() (string, uint16, string, bool, bool) {
|
||||
return ni.host, ni.port, ni.from, ni.connected, ni.disconnected
|
||||
}
|
||||
|
||||
// crawler.Node
|
||||
//---------------------------------------------------------------------------------------
|
||||
// crawler.Crawler
|
||||
|
||||
// A crawler has a local node, a set of potential nodes in the nodePool, and connected nodes.
|
||||
// Maps are only accessed by one go-routine, mediated by the checkQueue
|
||||
type Crawler struct {
|
||||
QuitService
|
||||
|
||||
self *Node
|
||||
client *NodeClient
|
||||
|
||||
checkQueue chan nodeInfo
|
||||
nodePool map[string]*Node
|
||||
nodes map[string]*Node
|
||||
|
||||
nodeQueue chan *Node
|
||||
}
|
||||
|
||||
// Create a new Crawler using the local RPC server at addr
|
||||
func NewCrawler(host string, port uint16) *Crawler {
|
||||
crawler := &Crawler{
|
||||
self: &Node{Host: host, RPCPort: port, client: NewNodeClient(fmt.Sprintf("%s:%d", host, port))},
|
||||
checkQueue: make(chan nodeInfo, CheckQueueBufferSize),
|
||||
nodePool: make(map[string]*Node),
|
||||
nodes: make(map[string]*Node),
|
||||
nodeQueue: make(chan *Node, NodeQueueBufferSize),
|
||||
}
|
||||
crawler.QuitService = *NewQuitService(log, "Crawler", crawler)
|
||||
return crawler
|
||||
}
|
||||
|
||||
func (c *Crawler) OnStart() error {
|
||||
// connect to local node first, set info,
|
||||
// and fire peers onto the checkQueue
|
||||
if err := c.pollNode(c.self); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// connect to weboscket, subscribe to local events
|
||||
// and run the read loop to listen for new blocks
|
||||
_, err := c.self.client.ws.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.self.client.ws.Subscribe(types.EventStringNewBlock()); err != nil {
|
||||
return err
|
||||
}
|
||||
go c.readLoop(c.self)
|
||||
|
||||
// add ourselves to the nodes list
|
||||
c.nodes[c.self.Address()] = c.self
|
||||
|
||||
// nodes we hear about get put on the checkQueue
|
||||
// by pollNode and are handled in the checkLoop.
|
||||
// if its a node we're not already connected to,
|
||||
// it gets put on the nodeQueue and
|
||||
// we attempt to connect in the connectLoop
|
||||
go c.checkLoop()
|
||||
go c.connectLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listen for events from the node and ping it for peers on a ticker
|
||||
func (c *Crawler) readLoop(node *Node) {
|
||||
eventsCh := node.client.ws.EventsCh
|
||||
getPeersTicker := time.Tick(time.Second * GetPeersTickerSeconds)
|
||||
|
||||
for {
|
||||
select {
|
||||
case eventMsg := <-eventsCh:
|
||||
// update the node with his new info
|
||||
if err := c.consumeMessage(eventMsg, node); err != nil {
|
||||
// lost the node, put him back on the checkQueu
|
||||
c.checkNode(nodeInfo{
|
||||
host: node.Host,
|
||||
port: node.RPCPort,
|
||||
disconnected: true,
|
||||
})
|
||||
|
||||
}
|
||||
case <-getPeersTicker:
|
||||
if err := c.pollNode(node); err != nil {
|
||||
// lost the node, put him back on the checkQueu
|
||||
c.checkNode(nodeInfo{
|
||||
host: node.Host,
|
||||
port: node.RPCPort,
|
||||
disconnected: true,
|
||||
})
|
||||
}
|
||||
case <-c.Quit:
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Crawler) consumeMessage(eventMsg ctypes.ResultEvent, node *Node) error {
|
||||
block := eventMsg.Data.(*types.EventDataNewBlock).Block
|
||||
node.LastSeen = time.Now()
|
||||
node.BlockHeight = block.Height
|
||||
node.BlockHistory[block.Height] = node.LastSeen
|
||||
return nil
|
||||
}
|
||||
|
||||
// check nodes against the nodePool map one at a time
|
||||
// acts as a mutex on nodePool and nodes
|
||||
func (c *Crawler) checkLoop() {
|
||||
for {
|
||||
select {
|
||||
case ni := <-c.checkQueue:
|
||||
host, port, from, connected, disconnected := ni.unpack()
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
// check if we need to swap node between maps (eg. its connected or disconnected)
|
||||
// NOTE: once we hear about a node, we never forget ...
|
||||
if connected {
|
||||
n, _ := c.nodePool[addr]
|
||||
c.nodes[addr] = n
|
||||
delete(c.nodePool, addr)
|
||||
continue
|
||||
} else if disconnected {
|
||||
n, _ := c.nodes[addr]
|
||||
c.nodePool[addr] = n
|
||||
delete(c.nodes, addr)
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: if address is badly formed
|
||||
// we should punish ni.from
|
||||
_ = from
|
||||
|
||||
n, ok := c.nodePool[addr]
|
||||
// create the node if unknown
|
||||
if !ok {
|
||||
n = &Node{Host: host, RPCPort: port}
|
||||
c.nodePool[addr] = n
|
||||
} else if n.connected {
|
||||
// should be removed soon
|
||||
continue
|
||||
}
|
||||
|
||||
// queue it for connecting to
|
||||
c.nodeQueue <- n
|
||||
|
||||
case <-c.Quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read off the nodeQueue and attempt to connect to nodes
|
||||
func (c *Crawler) connectLoop() {
|
||||
for {
|
||||
select {
|
||||
case node := <-c.nodeQueue:
|
||||
go c.connectToNode(node)
|
||||
case <-c.Quit:
|
||||
// close all connections
|
||||
for addr, node := range c.nodes {
|
||||
_, _ = addr, node
|
||||
// TODO: close conn
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Crawler) connectToNode(node *Node) {
|
||||
|
||||
addr := node.Address()
|
||||
node.client = NewNodeClient(addr)
|
||||
_, err := node.client.ws.Start()
|
||||
if err != nil {
|
||||
fmt.Println("err on ws start:", err)
|
||||
// set failed, return
|
||||
}
|
||||
|
||||
// remove from nodePool, add to nodes
|
||||
c.checkNode(nodeInfo{
|
||||
host: node.Host,
|
||||
port: node.RPCPort,
|
||||
connected: true,
|
||||
})
|
||||
|
||||
if err := c.pollNode(node); err != nil {
|
||||
// TODO: we had a good ws con
|
||||
// but failed on rpc?!
|
||||
// try again or something ...
|
||||
// if we still fail, report and disconnect
|
||||
}
|
||||
|
||||
fmt.Println("Successfully connected to node", node.Address())
|
||||
|
||||
// blocks (until quit or err)
|
||||
c.readLoop(node)
|
||||
}
|
||||
|
||||
func (c *Crawler) checkNode(ni nodeInfo) {
|
||||
c.checkQueue <- ni
|
||||
}
|
||||
|
||||
func (c *Crawler) pollNode(node *Node) error {
|
||||
// get the status info
|
||||
status, err := node.client.rpc.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get peers and net info
|
||||
netinfo, err := node.client.rpc.NetInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the info for the node
|
||||
node.SetInfo(status, netinfo)
|
||||
|
||||
// fire each peer on the checkQueue
|
||||
for _, p := range netinfo.Peers {
|
||||
c.checkNode(nodeInfo{
|
||||
host: p.Host,
|
||||
port: p.RPCPort,
|
||||
from: node.Address(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package crawler
|
||||
|
||||
import (
|
||||
"github.com/tendermint/log15"
|
||||
)
|
||||
|
||||
var log = log15.New("module", "crawler")
|
Binary file not shown.
@ -18,14 +18,12 @@ import (
|
||||
type Mempool struct {
|
||||
mtx sync.Mutex
|
||||
state *sm.State
|
||||
cache *sm.BlockCache
|
||||
txs []types.Tx // TODO: we need to add a map to facilitate replace-by-fee
|
||||
}
|
||||
|
||||
func NewMempool(state *sm.State) *Mempool {
|
||||
return &Mempool{
|
||||
state: state,
|
||||
cache: sm.NewBlockCache(state),
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,10 +31,6 @@ func (mem *Mempool) GetState() *sm.State {
|
||||
return mem.state
|
||||
}
|
||||
|
||||
func (mem *Mempool) GetCache() *sm.BlockCache {
|
||||
return mem.cache
|
||||
}
|
||||
|
||||
func (mem *Mempool) GetHeight() int {
|
||||
mem.mtx.Lock()
|
||||
defer mem.mtx.Unlock()
|
||||
@ -47,7 +41,7 @@ func (mem *Mempool) GetHeight() int {
|
||||
func (mem *Mempool) AddTx(tx types.Tx) (err error) {
|
||||
mem.mtx.Lock()
|
||||
defer mem.mtx.Unlock()
|
||||
err = sm.ExecTx(mem.cache, tx, false, nil)
|
||||
err = sm.ExecTx(mem.state, tx, nil)
|
||||
if err != nil {
|
||||
log.Info("AddTx() error", "tx", tx, "error", err)
|
||||
return err
|
||||
@ -85,12 +79,11 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) R
|
||||
mem.mtx.Lock()
|
||||
defer mem.mtx.Unlock()
|
||||
mem.state = state.Copy()
|
||||
mem.cache = sm.NewBlockCache(mem.state)
|
||||
|
||||
// First, create a lookup map of txns in new block.
|
||||
blockTxsMap := make(map[string]struct{})
|
||||
for _, tx := range block.Data.Txs {
|
||||
blockTxsMap[string(types.TxID(state.ChainID, tx))] = struct{}{}
|
||||
blockTxsMap[string(tx)] = struct{}{}
|
||||
}
|
||||
|
||||
// Now we filter all txs from mem.txs that are in blockTxsMap,
|
||||
@ -101,20 +94,19 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) R
|
||||
var validTxs []types.Tx
|
||||
includedStart, invalidStart := -1, -1
|
||||
for i, tx := range mem.txs {
|
||||
txID := types.TxID(state.ChainID, tx)
|
||||
if _, ok := blockTxsMap[string(txID)]; ok {
|
||||
if _, ok := blockTxsMap[string(tx)]; ok {
|
||||
startRange(&includedStart, i) // start counting included txs
|
||||
endRange(&invalidStart, i, &ri.Invalid) // stop counting invalid txs
|
||||
log.Info("Filter out, already committed", "tx", tx, "txID", txID)
|
||||
log.Info("Filter out, already committed", "tx", tx)
|
||||
} else {
|
||||
endRange(&includedStart, i, &ri.Included) // stop counting included txs
|
||||
err := sm.ExecTx(mem.cache, tx, false, nil)
|
||||
err := sm.ExecTx(mem.state, tx, nil)
|
||||
if err != nil {
|
||||
startRange(&invalidStart, i) // start counting invalid txs
|
||||
log.Info("Filter out, no longer valid", "tx", tx, "error", err)
|
||||
} else {
|
||||
endRange(&invalidStart, i, &ri.Invalid) // stop counting invalid txs
|
||||
log.Info("Filter in, new, valid", "tx", tx, "txID", txID)
|
||||
log.Info("Filter in, new, valid", "tx", tx)
|
||||
validTxs = append(validTxs, tx)
|
||||
}
|
||||
}
|
||||
|
@ -1,273 +0,0 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var someAddr = []byte("ABCDEFGHIJABCDEFGHIJ")
|
||||
|
||||
// number of txs
|
||||
var nTxs = 100
|
||||
|
||||
// what the ResetInfo should look like after ResetForBlockAndState
|
||||
var TestResetInfoData = ResetInfo{
|
||||
Included: []Range{
|
||||
Range{0, 5},
|
||||
Range{10, 10},
|
||||
Range{30, 5},
|
||||
},
|
||||
Invalid: []Range{
|
||||
Range{5, 5},
|
||||
Range{20, 8}, // let 28 and 29 be valid
|
||||
Range{35, 64}, // let 99 be valid
|
||||
},
|
||||
}
|
||||
|
||||
// inverse of the ResetInfo
|
||||
var notInvalidNotIncluded = map[int]struct{}{
|
||||
28: struct{}{},
|
||||
29: struct{}{},
|
||||
99: struct{}{},
|
||||
}
|
||||
|
||||
func newSendTx(t *testing.T, mempool *Mempool, from *acm.PrivAccount, to []byte, amt int64) types.Tx {
|
||||
tx := types.NewSendTx()
|
||||
tx.AddInput(mempool.GetCache(), from.PubKey, amt)
|
||||
tx.AddOutput(to, amt)
|
||||
tx.SignInput(config.GetString("chain_id"), 0, from)
|
||||
if err := mempool.AddTx(tx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
func addTxs(t *testing.T, mempool *Mempool, lastAcc *acm.PrivAccount, privAccs []*acm.PrivAccount) []types.Tx {
|
||||
txs := make([]types.Tx, nTxs)
|
||||
for i := 0; i < nTxs; i++ {
|
||||
if _, ok := notInvalidNotIncluded[i]; ok {
|
||||
txs[i] = newSendTx(t, mempool, lastAcc, someAddr, 10)
|
||||
} else {
|
||||
txs[i] = newSendTx(t, mempool, privAccs[i%len(privAccs)], privAccs[(i+1)%len(privAccs)].Address, 5)
|
||||
}
|
||||
}
|
||||
return txs
|
||||
}
|
||||
|
||||
func makeBlock(mempool *Mempool) *types.Block {
|
||||
txs := mempool.GetProposalTxs()
|
||||
var includedTxs []types.Tx
|
||||
for _, rid := range TestResetInfoData.Included {
|
||||
includedTxs = append(includedTxs, txs[rid.Start:rid.Start+rid.Length]...)
|
||||
}
|
||||
|
||||
mempool.mtx.Lock()
|
||||
state := mempool.state
|
||||
state.LastBlockHeight += 1
|
||||
mempool.mtx.Unlock()
|
||||
return &types.Block{
|
||||
Header: &types.Header{
|
||||
ChainID: state.ChainID,
|
||||
Height: state.LastBlockHeight,
|
||||
NumTxs: len(includedTxs),
|
||||
},
|
||||
Data: &types.Data{
|
||||
Txs: includedTxs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Add txs. Grab chunks to put in block. All the others become invalid because of nonce errors except those in notInvalidNotIncluded
|
||||
func TestResetInfo(t *testing.T) {
|
||||
amtPerAccount := int64(100000)
|
||||
state, privAccs, _ := sm.RandGenesisState(6, false, amtPerAccount, 1, true, 100)
|
||||
|
||||
mempool := NewMempool(state)
|
||||
|
||||
lastAcc := privAccs[5] // we save him (his tx wont become invalid)
|
||||
privAccs = privAccs[:5]
|
||||
|
||||
txs := addTxs(t, mempool, lastAcc, privAccs)
|
||||
|
||||
// its actually an invalid block since we're skipping nonces
|
||||
// but all we care about is how the mempool responds after
|
||||
block := makeBlock(mempool)
|
||||
|
||||
ri := mempool.ResetForBlockAndState(block, state)
|
||||
|
||||
if len(ri.Included) != len(TestResetInfoData.Included) {
|
||||
t.Fatalf("invalid number of included ranges. Got %d, expected %d\n", len(ri.Included), len(TestResetInfoData.Included))
|
||||
}
|
||||
|
||||
if len(ri.Invalid) != len(TestResetInfoData.Invalid) {
|
||||
t.Fatalf("invalid number of invalid ranges. Got %d, expected %d\n", len(ri.Invalid), len(TestResetInfoData.Invalid))
|
||||
}
|
||||
|
||||
for i, rid := range ri.Included {
|
||||
inc := TestResetInfoData.Included[i]
|
||||
if rid.Start != inc.Start {
|
||||
t.Fatalf("Invalid start of range. Got %d, expected %d\n", inc.Start, rid.Start)
|
||||
}
|
||||
if rid.Length != inc.Length {
|
||||
t.Fatalf("Invalid length of range. Got %d, expected %d\n", inc.Length, rid.Length)
|
||||
}
|
||||
}
|
||||
|
||||
txs = mempool.GetProposalTxs()
|
||||
if len(txs) != len(notInvalidNotIncluded) {
|
||||
t.Fatalf("Expected %d txs left in mempool. Got %d", len(notInvalidNotIncluded), len(txs))
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
type TestPeer struct {
|
||||
sync.Mutex
|
||||
running bool
|
||||
height int
|
||||
|
||||
t *testing.T
|
||||
|
||||
received int
|
||||
txs map[string]int
|
||||
|
||||
timeoutFail int
|
||||
|
||||
done chan int
|
||||
}
|
||||
|
||||
func newPeer(t *testing.T, state *sm.State) *TestPeer {
|
||||
return &TestPeer{
|
||||
running: true,
|
||||
height: state.LastBlockHeight,
|
||||
t: t,
|
||||
txs: make(map[string]int),
|
||||
done: make(chan int),
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TestPeer) IsRunning() bool {
|
||||
tp.Lock()
|
||||
defer tp.Unlock()
|
||||
return tp.running
|
||||
}
|
||||
|
||||
func (tp *TestPeer) SetRunning(running bool) {
|
||||
tp.Lock()
|
||||
defer tp.Unlock()
|
||||
tp.running = running
|
||||
}
|
||||
|
||||
func (tp *TestPeer) Send(chID byte, msg interface{}) bool {
|
||||
if tp.timeoutFail > 0 {
|
||||
time.Sleep(time.Second * time.Duration(tp.timeoutFail))
|
||||
return false
|
||||
}
|
||||
tx := msg.(*TxMessage).Tx
|
||||
id := types.TxID(config.GetString("chain_id"), tx)
|
||||
if _, ok := tp.txs[string(id)]; ok {
|
||||
tp.t.Fatal("received the same tx twice!")
|
||||
}
|
||||
tp.txs[string(id)] = tp.received
|
||||
tp.received += 1
|
||||
tp.done <- tp.received
|
||||
return true
|
||||
}
|
||||
|
||||
func (tp *TestPeer) Get(key string) interface{} {
|
||||
return tp
|
||||
}
|
||||
|
||||
func (tp *TestPeer) GetHeight() int {
|
||||
return tp.height
|
||||
}
|
||||
|
||||
func TestBroadcast(t *testing.T) {
|
||||
state, privAccs, _ := sm.RandGenesisState(6, false, 10000, 1, true, 100)
|
||||
mempool := NewMempool(state)
|
||||
reactor := NewMempoolReactor(mempool)
|
||||
reactor.Start()
|
||||
|
||||
lastAcc := privAccs[5] // we save him (his tx wont become invalid)
|
||||
privAccs = privAccs[:5]
|
||||
|
||||
peer := newPeer(t, state)
|
||||
newBlockChan := make(chan ResetInfo)
|
||||
tickerChan := make(chan time.Time)
|
||||
go reactor.broadcastTxRoutine(tickerChan, newBlockChan, peer)
|
||||
|
||||
// we don't broadcast any before updating
|
||||
fmt.Println("dont broadcast any")
|
||||
addTxs(t, mempool, lastAcc, privAccs)
|
||||
block := makeBlock(mempool)
|
||||
ri := mempool.ResetForBlockAndState(block, state)
|
||||
newBlockChan <- ri
|
||||
peer.height = ri.Height
|
||||
tickerChan <- time.Now()
|
||||
pullTxs(t, peer, len(mempool.txs)) // should have sent whatever txs are left (3)
|
||||
|
||||
toBroadcast := []int{1, 3, 7, 9, 11, 12, 18, 20, 21, 28, 29, 30, 31, 34, 35, 36, 50, 90, 99, 100}
|
||||
for _, N := range toBroadcast {
|
||||
peer = resetPeer(t, reactor, mempool, state, tickerChan, newBlockChan, peer)
|
||||
|
||||
// we broadcast N txs before updating
|
||||
fmt.Println("broadcast", N)
|
||||
addTxs(t, mempool, lastAcc, privAccs)
|
||||
txsToSendPerCheck = N
|
||||
tickerChan <- time.Now()
|
||||
pullTxs(t, peer, txsToSendPerCheck) // should have sent N txs
|
||||
block = makeBlock(mempool)
|
||||
ri := mempool.ResetForBlockAndState(block, state)
|
||||
newBlockChan <- ri
|
||||
peer.height = ri.Height
|
||||
txsToSendPerCheck = 100
|
||||
tickerChan <- time.Now()
|
||||
left := len(mempool.txs)
|
||||
if N > 99 {
|
||||
left -= 3
|
||||
} else if N > 29 {
|
||||
left -= 2
|
||||
} else if N > 28 {
|
||||
left -= 1
|
||||
}
|
||||
pullTxs(t, peer, left) // should have sent whatever txs are left that havent been sent
|
||||
}
|
||||
}
|
||||
|
||||
func pullTxs(t *testing.T, peer *TestPeer, N int) {
|
||||
timer := time.NewTicker(time.Second * 2)
|
||||
for i := 0; i < N; i++ {
|
||||
select {
|
||||
case <-peer.done:
|
||||
case <-timer.C:
|
||||
panic(fmt.Sprintf("invalid number of received messages. Got %d, expected %d\n", i, N))
|
||||
}
|
||||
}
|
||||
|
||||
if N == 0 {
|
||||
select {
|
||||
case <-peer.done:
|
||||
t.Fatalf("should not have sent any more txs")
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resetPeer(t *testing.T, reactor *MempoolReactor, mempool *Mempool, state *sm.State, tickerChan chan time.Time, newBlockChan chan ResetInfo, peer *TestPeer) *TestPeer {
|
||||
// reset peer
|
||||
mempool.txs = []types.Tx{}
|
||||
mempool.state = state
|
||||
mempool.cache = sm.NewBlockCache(state)
|
||||
peer.SetRunning(false)
|
||||
tickerChan <- time.Now()
|
||||
peer = newPeer(t, state)
|
||||
go reactor.broadcastTxRoutine(tickerChan, newBlockChan, peer)
|
||||
return peer
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NodeID struct {
|
||||
Name string
|
||||
PubKey acm.PubKey
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
|
||||
type PrivNodeID struct {
|
||||
NodeID
|
||||
PrivKey acm.PrivKey
|
||||
PrivKey crypto.PrivKey
|
||||
}
|
||||
|
||||
type NodeGreeting struct {
|
||||
@ -25,7 +25,7 @@ type NodeGreeting struct {
|
||||
|
||||
type SignedNodeGreeting struct {
|
||||
NodeGreeting
|
||||
Signature acm.Signature
|
||||
Signature crypto.Signature
|
||||
}
|
||||
|
||||
func (pnid *PrivNodeID) SignGreeting() *SignedNodeGreeting {
|
||||
|
58
node/node.go
58
node/node.go
@ -10,22 +10,20 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/consensus"
|
||||
"github.com/tendermint/go-crypto"
|
||||
dbm "github.com/tendermint/go-db"
|
||||
"github.com/tendermint/go-p2p"
|
||||
"github.com/tendermint/go-wire"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
"github.com/tendermint/tendermint/consensus"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
"github.com/tendermint/go-p2p"
|
||||
"github.com/tendermint/tendermint/rpc"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
"github.com/tendermint/tendermint/rpc/server"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
stypes "github.com/tendermint/tendermint/state/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/vm"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
import _ "net/http/pprof"
|
||||
@ -41,8 +39,8 @@ type Node struct {
|
||||
consensusState *consensus.ConsensusState
|
||||
consensusReactor *consensus.ConsensusReactor
|
||||
privValidator *types.PrivValidator
|
||||
genDoc *stypes.GenesisDoc
|
||||
privKey acm.PrivKeyEd25519
|
||||
genDoc *types.GenesisDoc
|
||||
privKey crypto.PrivKeyEd25519
|
||||
}
|
||||
|
||||
func NewNode() *Node {
|
||||
@ -53,19 +51,19 @@ func NewNode() *Node {
|
||||
// Get State
|
||||
stateDB := dbm.GetDB("state")
|
||||
state := sm.LoadState(stateDB)
|
||||
var genDoc *stypes.GenesisDoc
|
||||
var genDoc *types.GenesisDoc
|
||||
if state == nil {
|
||||
genDoc, state = sm.MakeGenesisStateFromFile(stateDB, config.GetString("genesis_file"))
|
||||
state.Save()
|
||||
// write the gendoc to db
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
wire.WriteJSON(genDoc, buf, n, err)
|
||||
stateDB.Set(stypes.GenDocKey, buf.Bytes())
|
||||
stateDB.Set(types.GenDocKey, buf.Bytes())
|
||||
if *err != nil {
|
||||
Exit(Fmt("Unable to write gendoc to db: %v", err))
|
||||
}
|
||||
} else {
|
||||
genDocBytes := stateDB.Get(stypes.GenDocKey)
|
||||
genDocBytes := stateDB.Get(types.GenDocKey)
|
||||
err := new(error)
|
||||
wire.ReadJSONPtr(&genDoc, genDocBytes, err)
|
||||
if *err != nil {
|
||||
@ -80,7 +78,7 @@ func NewNode() *Node {
|
||||
privValidator := types.LoadOrGenPrivValidator(privValidatorFile)
|
||||
|
||||
// Generate node PrivKey
|
||||
privKey := acm.GenPrivKeyEd25519()
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
|
||||
// Make event switch
|
||||
eventSwitch := events.NewEventSwitch()
|
||||
@ -116,7 +114,7 @@ func NewNode() *Node {
|
||||
|
||||
// add the event switch to all services
|
||||
// they should all satisfy events.Eventable
|
||||
SetFireable(eventSwitch, pexReactor, bcReactor, mempoolReactor, consensusReactor)
|
||||
SetFireable(eventSwitch, bcReactor, mempoolReactor, consensusReactor)
|
||||
|
||||
// run the profile server
|
||||
profileHost := config.GetString("prof_laddr")
|
||||
@ -126,9 +124,6 @@ func NewNode() *Node {
|
||||
}()
|
||||
}
|
||||
|
||||
// set vm log level
|
||||
vm.SetDebug(config.GetBool("vm_log"))
|
||||
|
||||
return &Node{
|
||||
sw: sw,
|
||||
evsw: eventSwitch,
|
||||
@ -243,23 +238,23 @@ func (n *Node) EventSwitch() *events.EventSwitch {
|
||||
return n.evsw
|
||||
}
|
||||
|
||||
func makeNodeInfo(sw *p2p.Switch, privKey acm.PrivKeyEd25519) *types.NodeInfo {
|
||||
func makeNodeInfo(sw *p2p.Switch, privKey crypto.PrivKeyEd25519) *p2p.NodeInfo {
|
||||
|
||||
nodeInfo := &types.NodeInfo{
|
||||
PubKey: privKey.PubKey().(acm.PubKeyEd25519),
|
||||
nodeInfo := &p2p.NodeInfo{
|
||||
PubKey: privKey.PubKey().(crypto.PubKeyEd25519),
|
||||
Moniker: config.GetString("moniker"),
|
||||
ChainID: config.GetString("chain_id"),
|
||||
Version: types.Versions{
|
||||
Tendermint: Version,
|
||||
P2P: p2p.Version,
|
||||
RPC: rpc.Version,
|
||||
Wire: wire.Version,
|
||||
Network: config.GetString("chain_id"),
|
||||
Version: Version,
|
||||
Other: []string{
|
||||
Fmt("p2p_version=%v" + p2p.Version),
|
||||
Fmt("rpc_version=%v" + rpc.Version),
|
||||
Fmt("wire_version=%v" + wire.Version),
|
||||
},
|
||||
}
|
||||
|
||||
// include git hash in the nodeInfo if available
|
||||
if rev, err := ReadFile(config.GetString("revision_file")); err == nil {
|
||||
nodeInfo.Version.Revision = string(rev)
|
||||
nodeInfo.Other = append(nodeInfo.Other, Fmt("revision=%v", string(rev)))
|
||||
}
|
||||
|
||||
if !sw.IsListening() {
|
||||
@ -279,9 +274,8 @@ func makeNodeInfo(sw *p2p.Switch, privKey acm.PrivKeyEd25519) *types.NodeInfo {
|
||||
// We assume that the rpcListener has the same ExternalAddress.
|
||||
// This is probably true because both P2P and RPC listeners use UPnP,
|
||||
// except of course if the rpc is only bound to localhost
|
||||
nodeInfo.Host = p2pHost
|
||||
nodeInfo.P2PPort = p2pPort
|
||||
nodeInfo.RPCPort = uint16(rpcPort)
|
||||
nodeInfo.Address = Fmt("%v:%v", p2pHost, p2pPort)
|
||||
nodeInfo.Other = append(nodeInfo.Other, Fmt("rpc_port=%v", rpcPort))
|
||||
return nodeInfo
|
||||
}
|
||||
|
||||
@ -302,7 +296,7 @@ func RunNode() {
|
||||
if err != nil {
|
||||
Exit(Fmt("Couldn't read GenesisDoc file: %v", err))
|
||||
}
|
||||
genDoc := stypes.GenesisDocFromJSON(jsonBlob)
|
||||
genDoc := types.GenesisDocFromJSON(jsonBlob)
|
||||
if genDoc.ChainID == "" {
|
||||
PanicSanity(Fmt("Genesis doc %v must include non-empty chain_id", genDocFile))
|
||||
}
|
||||
@ -313,7 +307,7 @@ func RunNode() {
|
||||
|
||||
// Create & start node
|
||||
n := NewNode()
|
||||
l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr"))
|
||||
l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr"), config.GetBool("skip_upnp"))
|
||||
n.AddListener(l)
|
||||
err := n.Start()
|
||||
if err != nil {
|
||||
|
@ -4,14 +4,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
"github.com/tendermint/go-p2p"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
)
|
||||
|
||||
func TestNodeStartStop(t *testing.T) {
|
||||
// Create & start node
|
||||
n := NewNode()
|
||||
l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr"))
|
||||
l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr"), config.GetBool("skip_upnp"))
|
||||
n.AddListener(l)
|
||||
n.Start()
|
||||
log.Notice("Started node", "nodeInfo", n.sw.NodeInfo())
|
||||
|
@ -1,23 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// Some errors
|
||||
|
||||
// permission number out of bounds
|
||||
type ErrInvalidPermission PermFlag
|
||||
|
||||
func (e ErrInvalidPermission) Error() string {
|
||||
return fmt.Sprintf("invalid permission %d", e)
|
||||
}
|
||||
|
||||
// set=false. This error should be caught and the global
|
||||
// value fetched for the permission by the caller
|
||||
type ErrValueNotSet PermFlag
|
||||
|
||||
func (e ErrValueNotSet) Error() string {
|
||||
return fmt.Sprintf("the value for permission %d is not set", e)
|
||||
}
|
@ -1,238 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
GlobalPermissionsAddress = Zero256[:20]
|
||||
GlobalPermissionsAddress256 = Zero256
|
||||
)
|
||||
|
||||
// A particular permission
|
||||
type PermFlag uint64
|
||||
|
||||
// Base permission references are like unix (the index is already bit shifted)
|
||||
const (
|
||||
// chain permissions
|
||||
Root PermFlag = 1 << iota // 1
|
||||
Send // 2
|
||||
Call // 4
|
||||
CreateContract // 8
|
||||
CreateAccount // 16
|
||||
Bond // 32
|
||||
Name // 64
|
||||
|
||||
// moderator permissions
|
||||
HasBase
|
||||
SetBase
|
||||
UnsetBase
|
||||
SetGlobal
|
||||
HasRole
|
||||
AddRole
|
||||
RmRole
|
||||
|
||||
NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64
|
||||
|
||||
TopPermFlag PermFlag = 1 << (NumPermissions - 1)
|
||||
AllPermFlags PermFlag = TopPermFlag | (TopPermFlag - 1)
|
||||
DefaultPermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole
|
||||
)
|
||||
|
||||
var (
|
||||
ZeroBasePermissions = BasePermissions{0, 0}
|
||||
ZeroAccountPermissions = AccountPermissions{
|
||||
Base: ZeroBasePermissions,
|
||||
}
|
||||
DefaultAccountPermissions = AccountPermissions{
|
||||
Base: BasePermissions{
|
||||
Perms: DefaultPermFlags,
|
||||
SetBit: AllPermFlags,
|
||||
},
|
||||
Roles: []string{},
|
||||
}
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
// Base chain permissions struct
|
||||
type BasePermissions struct {
|
||||
// bit array with "has"/"doesn't have" for each permission
|
||||
Perms PermFlag `json:"perms"`
|
||||
|
||||
// bit array with "set"/"not set" for each permission (not-set should fall back to global)
|
||||
SetBit PermFlag `json:"set"`
|
||||
}
|
||||
|
||||
// Get a permission value. ty should be a power of 2.
|
||||
// ErrValueNotSet is returned if the permission's set bit is off,
|
||||
// and should be caught by caller so the global permission can be fetched
|
||||
func (p *BasePermissions) Get(ty PermFlag) (bool, error) {
|
||||
if ty == 0 {
|
||||
return false, ErrInvalidPermission(ty)
|
||||
}
|
||||
if p.SetBit&ty == 0 {
|
||||
return false, ErrValueNotSet(ty)
|
||||
}
|
||||
return p.Perms&ty > 0, nil
|
||||
}
|
||||
|
||||
// Set a permission bit. Will set the permission's set bit to true.
|
||||
func (p *BasePermissions) Set(ty PermFlag, value bool) error {
|
||||
if ty == 0 {
|
||||
return ErrInvalidPermission(ty)
|
||||
}
|
||||
p.SetBit |= ty
|
||||
if value {
|
||||
p.Perms |= ty
|
||||
} else {
|
||||
p.Perms &= ^ty
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the permission's set bit to false
|
||||
func (p *BasePermissions) Unset(ty PermFlag) error {
|
||||
if ty == 0 {
|
||||
return ErrInvalidPermission(ty)
|
||||
}
|
||||
p.SetBit &= ^ty
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the permission is set
|
||||
func (p *BasePermissions) IsSet(ty PermFlag) bool {
|
||||
if ty == 0 {
|
||||
return false
|
||||
}
|
||||
return p.SetBit&ty > 0
|
||||
}
|
||||
|
||||
func (p BasePermissions) String() string {
|
||||
return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit)
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
type AccountPermissions struct {
|
||||
Base BasePermissions `json:"base"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
// Returns true if the role is found
|
||||
func (aP *AccountPermissions) HasRole(role string) bool {
|
||||
role = string(LeftPadBytes([]byte(role), 32))
|
||||
for _, r := range aP.Roles {
|
||||
if r == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if the role is added, and false if it already exists
|
||||
func (aP *AccountPermissions) AddRole(role string) bool {
|
||||
role = string(LeftPadBytes([]byte(role), 32))
|
||||
for _, r := range aP.Roles {
|
||||
if r == role {
|
||||
return false
|
||||
}
|
||||
}
|
||||
aP.Roles = append(aP.Roles, role)
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true if the role is removed, and false if it is not found
|
||||
func (aP *AccountPermissions) RmRole(role string) bool {
|
||||
role = string(LeftPadBytes([]byte(role), 32))
|
||||
for i, r := range aP.Roles {
|
||||
if r == role {
|
||||
post := []string{}
|
||||
if len(aP.Roles) > i+1 {
|
||||
post = aP.Roles[i+1:]
|
||||
}
|
||||
aP.Roles = append(aP.Roles[:i], post...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// string utilities
|
||||
|
||||
// PermFlagToString assumes the permFlag is valid, else returns "#-UNKNOWN-#"
|
||||
func PermFlagToString(pf PermFlag) (perm string) {
|
||||
switch pf {
|
||||
case Root:
|
||||
perm = "root"
|
||||
case Send:
|
||||
perm = "send"
|
||||
case Call:
|
||||
perm = "call"
|
||||
case CreateContract:
|
||||
perm = "create_contract"
|
||||
case CreateAccount:
|
||||
perm = "create_account"
|
||||
case Bond:
|
||||
perm = "bond"
|
||||
case Name:
|
||||
perm = "name"
|
||||
case HasBase:
|
||||
perm = "has_base"
|
||||
case SetBase:
|
||||
perm = "set_base"
|
||||
case UnsetBase:
|
||||
perm = "unset_base"
|
||||
case SetGlobal:
|
||||
perm = "set_global"
|
||||
case HasRole:
|
||||
perm = "has_role"
|
||||
case AddRole:
|
||||
perm = "add_role"
|
||||
case RmRole:
|
||||
perm = "rm_role"
|
||||
default:
|
||||
perm = "#-UNKNOWN-#"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PermStringToFlag(perm string) (pf PermFlag, err error) {
|
||||
switch perm {
|
||||
case "root":
|
||||
pf = Root
|
||||
case "send":
|
||||
pf = Send
|
||||
case "call":
|
||||
pf = Call
|
||||
case "create_contract":
|
||||
pf = CreateContract
|
||||
case "create_account":
|
||||
pf = CreateAccount
|
||||
case "bond":
|
||||
pf = Bond
|
||||
case "name":
|
||||
pf = Name
|
||||
case "has_base":
|
||||
pf = HasBase
|
||||
case "set_base":
|
||||
pf = SetBase
|
||||
case "unset_base":
|
||||
pf = UnsetBase
|
||||
case "set_global":
|
||||
pf = SetGlobal
|
||||
case "has_role":
|
||||
pf = HasRole
|
||||
case "add_role":
|
||||
pf = AddRole
|
||||
case "rm_role":
|
||||
pf = RmRole
|
||||
default:
|
||||
err = fmt.Errorf("Unknown permission %s", perm)
|
||||
}
|
||||
return
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
// PermissionsTx.PermArgs interface and argument encoding
|
||||
|
||||
// Arguments are a registered interface in the PermissionsTx,
|
||||
// so binary handles the arguments and each permission function gets a type-byte
|
||||
// PermFlag() maps the type-byte to the permission
|
||||
// The account sending the PermissionsTx must have this PermFlag set
|
||||
type PermArgs interface {
|
||||
PermFlag() PermFlag
|
||||
}
|
||||
|
||||
const (
|
||||
PermArgsTypeHasBase = byte(0x01)
|
||||
PermArgsTypeSetBase = byte(0x02)
|
||||
PermArgsTypeUnsetBase = byte(0x03)
|
||||
PermArgsTypeSetGlobal = byte(0x04)
|
||||
PermArgsTypeHasRole = byte(0x05)
|
||||
PermArgsTypeAddRole = byte(0x06)
|
||||
PermArgsTypeRmRole = byte(0x07)
|
||||
)
|
||||
|
||||
// for wire.readReflect
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ PermArgs }{},
|
||||
wire.ConcreteType{&HasBaseArgs{}, PermArgsTypeHasBase},
|
||||
wire.ConcreteType{&SetBaseArgs{}, PermArgsTypeSetBase},
|
||||
wire.ConcreteType{&UnsetBaseArgs{}, PermArgsTypeUnsetBase},
|
||||
wire.ConcreteType{&SetGlobalArgs{}, PermArgsTypeSetGlobal},
|
||||
wire.ConcreteType{&HasRoleArgs{}, PermArgsTypeHasRole},
|
||||
wire.ConcreteType{&AddRoleArgs{}, PermArgsTypeAddRole},
|
||||
wire.ConcreteType{&RmRoleArgs{}, PermArgsTypeRmRole},
|
||||
)
|
||||
|
||||
type HasBaseArgs struct {
|
||||
Address []byte `json:"address"`
|
||||
Permission PermFlag `json:"permission"`
|
||||
}
|
||||
|
||||
func (*HasBaseArgs) PermFlag() PermFlag {
|
||||
return HasBase
|
||||
}
|
||||
|
||||
type SetBaseArgs struct {
|
||||
Address []byte `json:"address"`
|
||||
Permission PermFlag `json:"permission"`
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
|
||||
func (*SetBaseArgs) PermFlag() PermFlag {
|
||||
return SetBase
|
||||
}
|
||||
|
||||
type UnsetBaseArgs struct {
|
||||
Address []byte `json:"address"`
|
||||
Permission PermFlag `json:"permission"`
|
||||
}
|
||||
|
||||
func (*UnsetBaseArgs) PermFlag() PermFlag {
|
||||
return UnsetBase
|
||||
}
|
||||
|
||||
type SetGlobalArgs struct {
|
||||
Permission PermFlag `json:"permission"`
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
|
||||
func (*SetGlobalArgs) PermFlag() PermFlag {
|
||||
return SetGlobal
|
||||
}
|
||||
|
||||
type HasRoleArgs struct {
|
||||
Address []byte `json:"address"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func (*HasRoleArgs) PermFlag() PermFlag {
|
||||
return HasRole
|
||||
}
|
||||
|
||||
type AddRoleArgs struct {
|
||||
Address []byte `json:"address"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func (*AddRoleArgs) PermFlag() PermFlag {
|
||||
return AddRole
|
||||
}
|
||||
|
||||
type RmRoleArgs struct {
|
||||
Address []byte `json:"address"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func (*RmRoleArgs) PermFlag() PermFlag {
|
||||
return RmRole
|
||||
}
|
@ -8,8 +8,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
. "github.com/tendermint/tendermint/rpc/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
. "github.com/tendermint/tendermint/rpc/types"
|
||||
)
|
||||
|
||||
func Call(remote string, method string, params []interface{}, dest interface{}) (interface{}, error) {
|
||||
|
@ -1,67 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
func GenPrivAccount() (*ctypes.ResultGenPrivAccount, error) {
|
||||
return &ctypes.ResultGenPrivAccount{acm.GenPrivAccount()}, nil
|
||||
}
|
||||
|
||||
// If the account is not known, returns nil, nil.
|
||||
func GetAccount(address []byte) (*ctypes.ResultGetAccount, error) {
|
||||
cache := mempoolReactor.Mempool.GetCache()
|
||||
account := cache.GetAccount(address)
|
||||
if account == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &ctypes.ResultGetAccount{account}, nil
|
||||
}
|
||||
|
||||
func GetStorage(address, key []byte) (*ctypes.ResultGetStorage, error) {
|
||||
state := consensusState.GetState()
|
||||
account := state.GetAccount(address)
|
||||
if account == nil {
|
||||
return nil, fmt.Errorf("UnknownAddress: %X", address)
|
||||
}
|
||||
storageRoot := account.StorageRoot
|
||||
storageTree := state.LoadStorage(storageRoot)
|
||||
|
||||
_, value := storageTree.Get(LeftPadWord256(key).Bytes())
|
||||
if value == nil {
|
||||
return &ctypes.ResultGetStorage{key, nil}, nil
|
||||
}
|
||||
return &ctypes.ResultGetStorage{key, value.([]byte)}, nil
|
||||
}
|
||||
|
||||
func ListAccounts() (*ctypes.ResultListAccounts, error) {
|
||||
var blockHeight int
|
||||
var accounts []*acm.Account
|
||||
state := consensusState.GetState()
|
||||
blockHeight = state.LastBlockHeight
|
||||
state.GetAccounts().Iterate(func(key interface{}, value interface{}) bool {
|
||||
accounts = append(accounts, value.(*acm.Account))
|
||||
return false
|
||||
})
|
||||
return &ctypes.ResultListAccounts{blockHeight, accounts}, nil
|
||||
}
|
||||
|
||||
func DumpStorage(address []byte) (*ctypes.ResultDumpStorage, error) {
|
||||
state := consensusState.GetState()
|
||||
account := state.GetAccount(address)
|
||||
if account == nil {
|
||||
return nil, fmt.Errorf("UnknownAddress: %X", address)
|
||||
}
|
||||
storageRoot := account.StorageRoot
|
||||
storageTree := state.LoadStorage(storageRoot)
|
||||
storageItems := []ctypes.StorageItem{}
|
||||
storageTree.Iterate(func(key interface{}, value interface{}) bool {
|
||||
storageItems = append(storageItems, ctypes.StorageItem{
|
||||
key.([]byte), value.([]byte)})
|
||||
return false
|
||||
})
|
||||
return &ctypes.ResultDumpStorage{storageRoot, storageItems}, nil
|
||||
}
|
@ -1,29 +1,24 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-wire"
|
||||
cm "github.com/tendermint/tendermint/consensus"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
func ListValidators() (*ctypes.ResultListValidators, error) {
|
||||
var blockHeight int
|
||||
var bondedValidators []*types.Validator
|
||||
var unbondingValidators []*types.Validator
|
||||
var validators []*types.Validator
|
||||
|
||||
state := consensusState.GetState()
|
||||
blockHeight = state.LastBlockHeight
|
||||
state.BondedValidators.Iterate(func(index int, val *types.Validator) bool {
|
||||
bondedValidators = append(bondedValidators, val)
|
||||
return false
|
||||
})
|
||||
state.UnbondingValidators.Iterate(func(index int, val *types.Validator) bool {
|
||||
unbondingValidators = append(unbondingValidators, val)
|
||||
state.Validators.Iterate(func(index int, val *types.Validator) bool {
|
||||
validators = append(validators, val)
|
||||
return false
|
||||
})
|
||||
|
||||
return &ctypes.ResultListValidators{blockHeight, bondedValidators, unbondingValidators}, nil
|
||||
return &ctypes.ResultListValidators{blockHeight, validators}, nil
|
||||
}
|
||||
|
||||
func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||
|
@ -3,7 +3,6 @@ package core
|
||||
import (
|
||||
"fmt"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@ -15,18 +14,7 @@ func BroadcastTx(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
|
||||
}
|
||||
|
||||
txHash := types.TxID(mempoolReactor.Mempool.GetState().ChainID, tx)
|
||||
var createsContract uint8
|
||||
var contractAddr []byte
|
||||
// check if creates new contract
|
||||
if callTx, ok := tx.(*types.CallTx); ok {
|
||||
if len(callTx.Address) == 0 {
|
||||
createsContract = 1
|
||||
contractAddr = state.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence)
|
||||
}
|
||||
}
|
||||
return &ctypes.ResultBroadcastTx{ctypes.Receipt{txHash, createsContract, contractAddr}}, nil
|
||||
return &ctypes.ResultBroadcastTx{}, nil
|
||||
}
|
||||
|
||||
func ListUnconfirmedTxs() (*ctypes.ResultListUnconfirmedTxs, error) {
|
||||
|
@ -1,29 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func GetName(name string) (*ctypes.ResultGetName, error) {
|
||||
st := consensusState.GetState() // performs a copy
|
||||
entry := st.GetNameRegEntry(name)
|
||||
if entry == nil {
|
||||
return nil, fmt.Errorf("Name %s not found", name)
|
||||
}
|
||||
return &ctypes.ResultGetName{entry}, nil
|
||||
}
|
||||
|
||||
func ListNames() (*ctypes.ResultListNames, error) {
|
||||
var blockHeight int
|
||||
var names []*types.NameRegEntry
|
||||
state := consensusState.GetState()
|
||||
blockHeight = state.LastBlockHeight
|
||||
state.GetNames().Iterate(func(key interface{}, value interface{}) bool {
|
||||
names = append(names, value.(*types.NameRegEntry))
|
||||
return false
|
||||
})
|
||||
return &ctypes.ResultListNames{blockHeight, names}, nil
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-p2p"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
"github.com/tendermint/tendermint/consensus"
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
"github.com/tendermint/go-p2p"
|
||||
stypes "github.com/tendermint/tendermint/state/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@ -15,7 +14,7 @@ var consensusReactor *consensus.ConsensusReactor
|
||||
var mempoolReactor *mempl.MempoolReactor
|
||||
var p2pSwitch *p2p.Switch
|
||||
var privValidator *types.PrivValidator
|
||||
var genDoc *stypes.GenesisDoc // cache the genesis structure
|
||||
var genDoc *types.GenesisDoc // cache the genesis structure
|
||||
|
||||
func SetBlockStore(bs *bc.BlockStore) {
|
||||
blockStore = bs
|
||||
@ -41,6 +40,6 @@ func SetPrivValidator(pv *types.PrivValidator) {
|
||||
privValidator = pv
|
||||
}
|
||||
|
||||
func SetGenDoc(doc *stypes.GenesisDoc) {
|
||||
func SetGenDoc(doc *types.GenesisDoc) {
|
||||
genDoc = doc
|
||||
}
|
||||
|
@ -6,24 +6,14 @@ import (
|
||||
|
||||
// TODO: eliminate redundancy between here and reading code from core/
|
||||
var Routes = map[string]*rpc.RPCFunc{
|
||||
"status": rpc.NewRPCFunc(Status, []string{}),
|
||||
"net_info": rpc.NewRPCFunc(NetInfo, []string{}),
|
||||
"blockchain": rpc.NewRPCFunc(BlockchainInfo, []string{"minHeight", "maxHeight"}),
|
||||
"genesis": rpc.NewRPCFunc(Genesis, []string{}),
|
||||
"get_block": rpc.NewRPCFunc(GetBlock, []string{"height"}),
|
||||
"get_account": rpc.NewRPCFunc(GetAccount, []string{"address"}),
|
||||
"get_storage": rpc.NewRPCFunc(GetStorage, []string{"address", "key"}),
|
||||
"call": rpc.NewRPCFunc(Call, []string{"fromAddress", "toAddress", "data"}),
|
||||
"call_code": rpc.NewRPCFunc(CallCode, []string{"fromAddress", "code", "data"}),
|
||||
"list_validators": rpc.NewRPCFunc(ListValidators, []string{}),
|
||||
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, []string{}),
|
||||
"dump_storage": rpc.NewRPCFunc(DumpStorage, []string{"address"}),
|
||||
"broadcast_tx": rpc.NewRPCFunc(BroadcastTx, []string{"tx"}),
|
||||
"list_unconfirmed_txs": rpc.NewRPCFunc(ListUnconfirmedTxs, []string{}),
|
||||
"list_accounts": rpc.NewRPCFunc(ListAccounts, []string{}),
|
||||
"get_name": rpc.NewRPCFunc(GetName, []string{"name"}),
|
||||
"list_names": rpc.NewRPCFunc(ListNames, []string{}),
|
||||
"unsafe/gen_priv_account": rpc.NewRPCFunc(GenPrivAccount, []string{}),
|
||||
"unsafe/sign_tx": rpc.NewRPCFunc(SignTx, []string{"tx", "privAccounts"}),
|
||||
"status": rpc.NewRPCFunc(Status, []string{}),
|
||||
"net_info": rpc.NewRPCFunc(NetInfo, []string{}),
|
||||
"blockchain": rpc.NewRPCFunc(BlockchainInfo, []string{"minHeight", "maxHeight"}),
|
||||
"genesis": rpc.NewRPCFunc(Genesis, []string{}),
|
||||
"get_block": rpc.NewRPCFunc(GetBlock, []string{"height"}),
|
||||
"list_validators": rpc.NewRPCFunc(ListValidators, []string{}),
|
||||
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, []string{}),
|
||||
"broadcast_tx": rpc.NewRPCFunc(BroadcastTx, []string{"tx"}),
|
||||
"list_unconfirmed_txs": rpc.NewRPCFunc(ListUnconfirmedTxs, []string{}),
|
||||
// subscribe/unsubscribe are reserved for websocket events.
|
||||
}
|
||||
|
116
rpc/core/txs.go
116
rpc/core/txs.go
@ -1,116 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
func toVMAccount(acc *acm.Account) *vm.Account {
|
||||
return &vm.Account{
|
||||
Address: LeftPadWord256(acc.Address),
|
||||
Balance: acc.Balance,
|
||||
Code: acc.Code, // This is crazy.
|
||||
Nonce: int64(acc.Sequence),
|
||||
Other: acc.PubKey,
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Run a contract's code on an isolated and unpersisted state
|
||||
// Cannot be used to create new contracts
|
||||
func Call(fromAddress, toAddress, data []byte) (*ctypes.ResultCall, error) {
|
||||
st := consensusState.GetState() // performs a copy
|
||||
cache := state.NewBlockCache(st)
|
||||
outAcc := cache.GetAccount(toAddress)
|
||||
if outAcc == nil {
|
||||
return nil, fmt.Errorf("Account %x does not exist", toAddress)
|
||||
}
|
||||
callee := toVMAccount(outAcc)
|
||||
caller := &vm.Account{Address: LeftPadWord256(fromAddress)}
|
||||
txCache := state.NewTxCache(cache)
|
||||
params := vm.Params{
|
||||
BlockHeight: int64(st.LastBlockHeight),
|
||||
BlockHash: LeftPadWord256(st.LastBlockHash),
|
||||
BlockTime: st.LastBlockTime.Unix(),
|
||||
GasLimit: st.GetGasLimit(),
|
||||
}
|
||||
|
||||
vmach := vm.NewVM(txCache, params, caller.Address, nil)
|
||||
gas := st.GetGasLimit()
|
||||
ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultCall{Return: ret}, nil
|
||||
}
|
||||
|
||||
// Run the given code on an isolated and unpersisted state
|
||||
// Cannot be used to create new contracts
|
||||
func CallCode(fromAddress, code, data []byte) (*ctypes.ResultCall, error) {
|
||||
|
||||
st := consensusState.GetState() // performs a copy
|
||||
cache := mempoolReactor.Mempool.GetCache()
|
||||
callee := &vm.Account{Address: LeftPadWord256(fromAddress)}
|
||||
caller := &vm.Account{Address: LeftPadWord256(fromAddress)}
|
||||
txCache := state.NewTxCache(cache)
|
||||
params := vm.Params{
|
||||
BlockHeight: int64(st.LastBlockHeight),
|
||||
BlockHash: LeftPadWord256(st.LastBlockHash),
|
||||
BlockTime: st.LastBlockTime.Unix(),
|
||||
GasLimit: st.GetGasLimit(),
|
||||
}
|
||||
|
||||
vmach := vm.NewVM(txCache, params, caller.Address, nil)
|
||||
gas := st.GetGasLimit()
|
||||
ret, err := vmach.Call(caller, callee, code, data, 0, &gas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultCall{Return: ret}, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func SignTx(tx types.Tx, privAccounts []*acm.PrivAccount) (*ctypes.ResultSignTx, error) {
|
||||
// more checks?
|
||||
|
||||
for i, privAccount := range privAccounts {
|
||||
if privAccount == nil || privAccount.PrivKey == nil {
|
||||
return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i)
|
||||
}
|
||||
}
|
||||
switch tx.(type) {
|
||||
case *types.SendTx:
|
||||
sendTx := tx.(*types.SendTx)
|
||||
for i, input := range sendTx.Inputs {
|
||||
input.PubKey = privAccounts[i].PubKey
|
||||
input.Signature = privAccounts[i].Sign(config.GetString("chain_id"), sendTx)
|
||||
}
|
||||
case *types.CallTx:
|
||||
callTx := tx.(*types.CallTx)
|
||||
callTx.Input.PubKey = privAccounts[0].PubKey
|
||||
callTx.Input.Signature = privAccounts[0].Sign(config.GetString("chain_id"), callTx)
|
||||
case *types.BondTx:
|
||||
bondTx := tx.(*types.BondTx)
|
||||
// the first privaccount corresponds to the BondTx pub key.
|
||||
// the rest to the inputs
|
||||
bondTx.Signature = privAccounts[0].Sign(config.GetString("chain_id"), bondTx).(acm.SignatureEd25519)
|
||||
for i, input := range bondTx.Inputs {
|
||||
input.PubKey = privAccounts[i+1].PubKey
|
||||
input.Signature = privAccounts[i+1].Sign(config.GetString("chain_id"), bondTx)
|
||||
}
|
||||
case *types.UnbondTx:
|
||||
unbondTx := tx.(*types.UnbondTx)
|
||||
unbondTx.Signature = privAccounts[0].Sign(config.GetString("chain_id"), unbondTx).(acm.SignatureEd25519)
|
||||
case *types.RebondTx:
|
||||
rebondTx := tx.(*types.RebondTx)
|
||||
rebondTx.Signature = privAccounts[0].Sign(config.GetString("chain_id"), rebondTx).(acm.SignatureEd25519)
|
||||
}
|
||||
return &ctypes.ResultSignTx{tx}, nil
|
||||
}
|
@ -1,38 +1,12 @@
|
||||
package core_types
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
stypes "github.com/tendermint/tendermint/state/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-p2p"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
type ResultGetStorage struct {
|
||||
Key []byte `json:"key"`
|
||||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
type ResultCall struct {
|
||||
Return []byte `json:"return"`
|
||||
GasUsed int64 `json:"gas_used"`
|
||||
// TODO ...
|
||||
}
|
||||
|
||||
type ResultListAccounts struct {
|
||||
BlockHeight int `json:"block_height"`
|
||||
Accounts []*acm.Account `json:"accounts"`
|
||||
}
|
||||
|
||||
type ResultDumpStorage struct {
|
||||
StorageRoot []byte `json:"storage_root"`
|
||||
StorageItems []StorageItem `json:"storage_items"`
|
||||
}
|
||||
|
||||
type StorageItem struct {
|
||||
Key []byte `json:"key"`
|
||||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
type ResultBlockchainInfo struct {
|
||||
LastHeight int `json:"last_height"`
|
||||
BlockMetas []*types.BlockMeta `json:"block_metas"`
|
||||
@ -44,12 +18,12 @@ type ResultGetBlock struct {
|
||||
}
|
||||
|
||||
type ResultStatus struct {
|
||||
NodeInfo *types.NodeInfo `json:"node_info"`
|
||||
GenesisHash []byte `json:"genesis_hash"`
|
||||
PubKey acm.PubKey `json:"pub_key"`
|
||||
LatestBlockHash []byte `json:"latest_block_hash"`
|
||||
LatestBlockHeight int `json:"latest_block_height"`
|
||||
LatestBlockTime int64 `json:"latest_block_time"` // nano
|
||||
NodeInfo *p2p.NodeInfo `json:"node_info"`
|
||||
GenesisHash []byte `json:"genesis_hash"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
LatestBlockHash []byte `json:"latest_block_hash"`
|
||||
LatestBlockHeight int `json:"latest_block_height"`
|
||||
LatestBlockTime int64 `json:"latest_block_time"` // nano
|
||||
}
|
||||
|
||||
type ResultNetInfo struct {
|
||||
@ -59,14 +33,13 @@ type ResultNetInfo struct {
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
types.NodeInfo `json:"node_info"`
|
||||
IsOutbound bool `json:"is_outbound"`
|
||||
p2p.NodeInfo `json:"node_info"`
|
||||
IsOutbound bool `json:"is_outbound"`
|
||||
}
|
||||
|
||||
type ResultListValidators struct {
|
||||
BlockHeight int `json:"block_height"`
|
||||
BondedValidators []*types.Validator `json:"bonded_validators"`
|
||||
UnbondingValidators []*types.Validator `json:"unbonding_validators"`
|
||||
BlockHeight int `json:"block_height"`
|
||||
Validators []*types.Validator `json:"validators"`
|
||||
}
|
||||
|
||||
type ResultDumpConsensusState struct {
|
||||
@ -74,27 +47,7 @@ type ResultDumpConsensusState struct {
|
||||
PeerRoundStates []string `json:"peer_round_states"`
|
||||
}
|
||||
|
||||
type ResultListNames struct {
|
||||
BlockHeight int `json:"block_height"`
|
||||
Names []*types.NameRegEntry `json:"names"`
|
||||
}
|
||||
|
||||
type ResultGenPrivAccount struct {
|
||||
PrivAccount *acm.PrivAccount `json:"priv_account"`
|
||||
}
|
||||
|
||||
type ResultGetAccount struct {
|
||||
Account *acm.Account `json:"account"`
|
||||
}
|
||||
|
||||
type ResultBroadcastTx struct {
|
||||
Receipt Receipt `json:"receipt"`
|
||||
}
|
||||
|
||||
type Receipt struct {
|
||||
TxHash []byte `json:"tx_hash"`
|
||||
CreatesContract uint8 `json:"creates_contract"`
|
||||
ContractAddr []byte `json:"contract_addr"`
|
||||
}
|
||||
|
||||
type ResultListUnconfirmedTxs struct {
|
||||
@ -102,16 +55,8 @@ type ResultListUnconfirmedTxs struct {
|
||||
Txs []types.Tx `json:"txs"`
|
||||
}
|
||||
|
||||
type ResultGetName struct {
|
||||
Entry *types.NameRegEntry `json:"entry"`
|
||||
}
|
||||
|
||||
type ResultGenesis struct {
|
||||
Genesis *stypes.GenesisDoc `json:"genesis"`
|
||||
}
|
||||
|
||||
type ResultSignTx struct {
|
||||
Tx types.Tx `json:"tx"`
|
||||
Genesis *types.GenesisDoc `json:"genesis"`
|
||||
}
|
||||
|
||||
type ResultEvent struct {
|
||||
@ -130,24 +75,15 @@ type Response struct {
|
||||
}
|
||||
|
||||
const (
|
||||
ResultTypeGetStorage = byte(0x01)
|
||||
ResultTypeCall = byte(0x02)
|
||||
ResultTypeListAccounts = byte(0x03)
|
||||
ResultTypeDumpStorage = byte(0x04)
|
||||
ResultTypeBlockchainInfo = byte(0x05)
|
||||
ResultTypeGetBlock = byte(0x06)
|
||||
ResultTypeStatus = byte(0x07)
|
||||
ResultTypeNetInfo = byte(0x08)
|
||||
ResultTypeListValidators = byte(0x09)
|
||||
ResultTypeDumpConsensusState = byte(0x0A)
|
||||
ResultTypeListNames = byte(0x0B)
|
||||
ResultTypeGenPrivAccount = byte(0x0C)
|
||||
ResultTypeGetAccount = byte(0x0D)
|
||||
ResultTypeBroadcastTx = byte(0x0E)
|
||||
ResultTypeListUnconfirmedTxs = byte(0x0F)
|
||||
ResultTypeGetName = byte(0x10)
|
||||
ResultTypeGenesis = byte(0x11)
|
||||
ResultTypeSignTx = byte(0x12)
|
||||
ResultTypeEvent = byte(0x13) // so websockets can respond to rpc functions
|
||||
)
|
||||
|
||||
@ -156,23 +92,14 @@ type Result interface{}
|
||||
// for wire.readReflect
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ Result }{},
|
||||
wire.ConcreteType{&ResultGetStorage{}, ResultTypeGetStorage},
|
||||
wire.ConcreteType{&ResultCall{}, ResultTypeCall},
|
||||
wire.ConcreteType{&ResultListAccounts{}, ResultTypeListAccounts},
|
||||
wire.ConcreteType{&ResultDumpStorage{}, ResultTypeDumpStorage},
|
||||
wire.ConcreteType{&ResultBlockchainInfo{}, ResultTypeBlockchainInfo},
|
||||
wire.ConcreteType{&ResultGetBlock{}, ResultTypeGetBlock},
|
||||
wire.ConcreteType{&ResultStatus{}, ResultTypeStatus},
|
||||
wire.ConcreteType{&ResultNetInfo{}, ResultTypeNetInfo},
|
||||
wire.ConcreteType{&ResultListValidators{}, ResultTypeListValidators},
|
||||
wire.ConcreteType{&ResultDumpConsensusState{}, ResultTypeDumpConsensusState},
|
||||
wire.ConcreteType{&ResultListNames{}, ResultTypeListNames},
|
||||
wire.ConcreteType{&ResultGenPrivAccount{}, ResultTypeGenPrivAccount},
|
||||
wire.ConcreteType{&ResultGetAccount{}, ResultTypeGetAccount},
|
||||
wire.ConcreteType{&ResultBroadcastTx{}, ResultTypeBroadcastTx},
|
||||
wire.ConcreteType{&ResultListUnconfirmedTxs{}, ResultTypeListUnconfirmedTxs},
|
||||
wire.ConcreteType{&ResultGetName{}, ResultTypeGetName},
|
||||
wire.ConcreteType{&ResultGenesis{}, ResultTypeGenesis},
|
||||
wire.ConcreteType{&ResultSignTx{}, ResultTypeSignTx},
|
||||
wire.ConcreteType{&ResultEvent{}, ResultTypeEvent},
|
||||
)
|
||||
|
@ -1,236 +0,0 @@
|
||||
package core_client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
//"reflect"
|
||||
// Uncomment to use go:generate
|
||||
// _ "github.com/tendermint/go-rpc-gen"
|
||||
)
|
||||
|
||||
// maps camel-case function names to lower case rpc version
|
||||
var reverseFuncMap = map[string]string{
|
||||
"Status": "status",
|
||||
"NetInfo": "net_info",
|
||||
"BlockchainInfo": "blockchain",
|
||||
"Genesis": "genesis",
|
||||
"GetBlock": "get_block",
|
||||
"GetAccount": "get_account",
|
||||
"GetStorage": "get_storage",
|
||||
"Call": "call",
|
||||
"CallCode": "call_code",
|
||||
"ListValidators": "list_validators",
|
||||
"DumpConsensusState": "dump_consensus_state",
|
||||
"DumpStorage": "dump_storage",
|
||||
"BroadcastTx": "broadcast_tx",
|
||||
"ListUnconfirmedTxs": "list_unconfirmed_txs",
|
||||
"ListAccounts": "list_accounts",
|
||||
"GetName": "get_name",
|
||||
"ListNames": "list_names",
|
||||
"GenPrivAccount": "unsafe/gen_priv_account",
|
||||
"SignTx": "unsafe/sign_tx",
|
||||
}
|
||||
|
||||
/*
|
||||
// fill the map from camelcase to lowercase
|
||||
func fillReverseFuncMap() map[string]string {
|
||||
fMap := make(map[string]string)
|
||||
for name, f := range core.Routes {
|
||||
camelName := runtime.FuncForPC(f.f.Pointer()).Name()
|
||||
spl := strings.Split(camelName, ".")
|
||||
if len(spl) > 1 {
|
||||
camelName = spl[len(spl)-1]
|
||||
}
|
||||
fMap[camelName] = name
|
||||
}
|
||||
return fMap
|
||||
}
|
||||
*/
|
||||
|
||||
type Response struct {
|
||||
Status string
|
||||
Data interface{}
|
||||
Error string
|
||||
}
|
||||
|
||||
//go:generate go-rpc-gen -interface Client -dir ../core -pkg core -type *ClientHTTP,*ClientJSON -exclude pipe.go -out-pkg core_client
|
||||
|
||||
type ClientJSON struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
type ClientHTTP struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
func NewClient(addr, typ string) Client {
|
||||
switch typ {
|
||||
case "HTTP":
|
||||
return &ClientHTTP{addr}
|
||||
case "JSONRPC":
|
||||
return &ClientJSON{addr}
|
||||
default:
|
||||
panic("Unknown client type " + typ + ". Select HTTP or JSONRPC")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func argsToJson(args ...interface{}) ([]string, error) {
|
||||
l := len(args)
|
||||
jsons := make([]string, l)
|
||||
n, err := new(int64), new(error)
|
||||
for i, a := range args {
|
||||
buf := new(bytes.Buffer)
|
||||
wire.WriteJSON(a, buf, n, err)
|
||||
if *err != nil {
|
||||
return nil, *err
|
||||
}
|
||||
jsons[i] = string(buf.Bytes())
|
||||
}
|
||||
return jsons, nil
|
||||
}
|
||||
|
||||
func (c *ClientJSON) RequestResponse(s rpctypes.RPCRequest) (b []byte, err error) {
|
||||
b = wire.JSONBytes(s)
|
||||
buf := bytes.NewBuffer(b)
|
||||
resp, err := http.Post(c.addr, "text/json", buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
/*
|
||||
What follows is used by `rpc-gen` when `go generate` is called
|
||||
to populate the rpc client methods
|
||||
*/
|
||||
|
||||
// first we define the base interface, which rpc-gen will further populate with generated methods
|
||||
|
||||
/*rpc-gen:define-interface Client
|
||||
type Client interface {
|
||||
Address() string // returns the remote address
|
||||
}
|
||||
*/
|
||||
|
||||
// encoding functions
|
||||
|
||||
func binaryWriter(args ...interface{}) ([]interface{}, error) {
|
||||
list := []interface{}{}
|
||||
for _, a := range args {
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
wire.WriteJSON(a, buf, n, err)
|
||||
if *err != nil {
|
||||
return nil, *err
|
||||
}
|
||||
list = append(list, buf.Bytes())
|
||||
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func argsToURLValues(argNames []string, args ...interface{}) (url.Values, error) {
|
||||
values := make(url.Values)
|
||||
if len(argNames) == 0 {
|
||||
return values, nil
|
||||
}
|
||||
if len(argNames) != len(args) {
|
||||
return nil, fmt.Errorf("argNames and args have different lengths: %d, %d", len(argNames), len(args))
|
||||
}
|
||||
slice, err := argsToJson(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, name := range argNames {
|
||||
s := slice[i]
|
||||
values.Set(name, s) // s[0]
|
||||
/*for j := 1; j < len(s); j++ {
|
||||
values.Add(name, s[j])
|
||||
}*/
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func unmarshalCheckResponse(body []byte) (response *ctypes.Response, err error) {
|
||||
response = new(ctypes.Response)
|
||||
wire.ReadJSON(response, body, &err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return nil, fmt.Errorf(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// import statements we will need for the templates
|
||||
|
||||
/*rpc-gen:imports:
|
||||
rpctypes github.com/tendermint/tendermint/rpc/types
|
||||
net/http
|
||||
io/ioutil
|
||||
fmt
|
||||
*/
|
||||
|
||||
// Template functions to be filled in
|
||||
|
||||
/*rpc-gen:template:*ClientJSON func (c *ClientJSON) {{name}}({{args.def}}) ({{response}}) {
|
||||
request := rpctypes.RPCRequest{
|
||||
JSONRPC: "2.0",
|
||||
Method: reverseFuncMap["{{name}}"],
|
||||
Params: []interface{}{ {{args.ident}} },
|
||||
ID: "",
|
||||
}
|
||||
body, err := c.RequestResponse(request)
|
||||
if err != nil{
|
||||
return nil, err
|
||||
}
|
||||
response, err := unmarshalCheckResponse(body)
|
||||
if err != nil{
|
||||
return nil, err
|
||||
}
|
||||
if response.Result == nil {
|
||||
return nil, nil
|
||||
}
|
||||
result, ok := response.Result.({{response.0}})
|
||||
if !ok{
|
||||
return nil, fmt.Errorf("response result was wrong type")
|
||||
}
|
||||
return result, nil
|
||||
}*/
|
||||
|
||||
/*rpc-gen:template:*ClientHTTP func (c *ClientHTTP) {{name}}({{args.def}}) ({{response}}){
|
||||
values, err := argsToURLValues({{args.name}}, {{args.ident}})
|
||||
if err != nil{
|
||||
return nil, err
|
||||
}
|
||||
resp, err := http.PostForm(c.addr+reverseFuncMap["{{name}}"], values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := unmarshalCheckResponse(body)
|
||||
if err != nil{
|
||||
return nil, err
|
||||
}
|
||||
if response.Result == nil {
|
||||
return nil, nil
|
||||
}
|
||||
result, ok := response.Result.({{response.0}})
|
||||
if !ok{
|
||||
return nil, fmt.Errorf("response result was wrong type")
|
||||
}
|
||||
return result, nil
|
||||
}*/
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
||||
|
||||
package core_client
|
||||
|
||||
import (
|
||||
"github.com/tendermint/log15"
|
||||
)
|
||||
|
||||
var log = log15.New("module", "core_client")
|
@ -1,119 +0,0 @@
|
||||
package core_client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
. "github.com/tendermint/go-common"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/rpc/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
const (
|
||||
wsEventsChannelCapacity = 10
|
||||
wsResultsChannelCapacity = 10
|
||||
wsWriteTimeoutSeconds = 10
|
||||
)
|
||||
|
||||
type WSClient struct {
|
||||
QuitService
|
||||
Address string
|
||||
*websocket.Conn
|
||||
EventsCh chan ctypes.ResultEvent
|
||||
ResultsCh chan ctypes.Result
|
||||
}
|
||||
|
||||
// create a new connection
|
||||
func NewWSClient(addr string) *WSClient {
|
||||
wsClient := &WSClient{
|
||||
Address: addr,
|
||||
Conn: nil,
|
||||
EventsCh: make(chan ctypes.ResultEvent, wsEventsChannelCapacity),
|
||||
ResultsCh: make(chan ctypes.Result, wsResultsChannelCapacity),
|
||||
}
|
||||
wsClient.QuitService = *NewQuitService(log, "WSClient", wsClient)
|
||||
return wsClient
|
||||
}
|
||||
|
||||
func (wsc *WSClient) OnStart() error {
|
||||
wsc.QuitService.OnStart()
|
||||
err := wsc.dial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go wsc.receiveEventsRoutine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wsc *WSClient) dial() error {
|
||||
// Dial
|
||||
dialer := websocket.DefaultDialer
|
||||
rHeader := http.Header{}
|
||||
con, _, err := dialer.Dial(wsc.Address, rHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Set the ping/pong handlers
|
||||
con.SetPingHandler(func(m string) error {
|
||||
con.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds))
|
||||
return nil
|
||||
})
|
||||
con.SetPongHandler(func(m string) error {
|
||||
return nil
|
||||
})
|
||||
wsc.Conn = con
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wsc *WSClient) OnStop() {
|
||||
wsc.QuitService.OnStop()
|
||||
}
|
||||
|
||||
func (wsc *WSClient) receiveEventsRoutine() {
|
||||
for {
|
||||
_, data, err := wsc.ReadMessage()
|
||||
if err != nil {
|
||||
log.Info("WSClient failed to read message", "error", err, "data", string(data))
|
||||
wsc.Stop()
|
||||
break
|
||||
} else {
|
||||
var response ctypes.Response
|
||||
wire.ReadJSON(&response, data, &err)
|
||||
if err != nil {
|
||||
log.Info("WSClient failed to parse message", "error", err)
|
||||
wsc.Stop()
|
||||
break
|
||||
}
|
||||
if strings.HasSuffix(response.ID, "#event") {
|
||||
wsc.EventsCh <- *response.Result.(*ctypes.ResultEvent)
|
||||
} else {
|
||||
wsc.ResultsCh <- response.Result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe to an event
|
||||
func (wsc *WSClient) Subscribe(eventid string) error {
|
||||
err := wsc.WriteJSON(rpctypes.RPCRequest{
|
||||
JSONRPC: "2.0",
|
||||
ID: "",
|
||||
Method: "subscribe",
|
||||
Params: []interface{}{eventid},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// unsubscribe from an event
|
||||
func (wsc *WSClient) Unsubscribe(eventid string) error {
|
||||
err := wsc.WriteJSON(rpctypes.RPCRequest{
|
||||
JSONRPC: "2.0",
|
||||
ID: "",
|
||||
Method: "unsubscribe",
|
||||
Params: []interface{}{eventid},
|
||||
})
|
||||
return err
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// When run with `-test.short` we only run:
|
||||
// TestHTTPStatus, TestHTTPBroadcast, TestJSONStatus, TestJSONBroadcast, TestWSConnect, TestWSSend
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Test the HTTP client
|
||||
|
||||
func TestHTTPStatus(t *testing.T) {
|
||||
testStatus(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPGenPriv(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testGenPriv(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPGetAccount(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testGetAccount(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPSignedTx(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testSignedTx(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPBroadcastTx(t *testing.T) {
|
||||
testBroadcastTx(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPGetStorage(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testGetStorage(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPCallCode(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testCallCode(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPCallContract(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testCall(t, "HTTP")
|
||||
}
|
||||
|
||||
func TestHTTPNameReg(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testNameReg(t, "HTTP")
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Test the JSONRPC client
|
||||
|
||||
func TestJSONStatus(t *testing.T) {
|
||||
testStatus(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONGenPriv(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testGenPriv(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONGetAccount(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testGetAccount(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONSignedTx(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testSignedTx(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONBroadcastTx(t *testing.T) {
|
||||
testBroadcastTx(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONGetStorage(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testGetStorage(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONCallCode(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testCallCode(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONCallContract(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testCall(t, "JSONRPC")
|
||||
}
|
||||
|
||||
func TestJSONNameReg(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
testNameReg(t, "JSONRPC")
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var wsTyp = "JSONRPC"
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Test the websocket service
|
||||
|
||||
// make a simple connection to the server
|
||||
func TestWSConnect(t *testing.T) {
|
||||
con := newWSCon(t)
|
||||
con.Close()
|
||||
}
|
||||
|
||||
// receive a new block message
|
||||
func TestWSNewBlock(t *testing.T) {
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringNewBlock()
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error {
|
||||
fmt.Println("Check:", string(b))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// receive a few new block messages in a row, with increasing height
|
||||
func TestWSBlockchainGrowth(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringNewBlock()
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
// listen for NewBlock, ensure height increases by 1
|
||||
unmarshalValidateBlockchain(t, con, eid)
|
||||
}
|
||||
|
||||
// send a transaction and validate the events from listening for both sender and receiver
|
||||
func TestWSSend(t *testing.T) {
|
||||
toAddr := user[1].Address
|
||||
amt := int64(100)
|
||||
|
||||
con := newWSCon(t)
|
||||
eidInput := types.EventStringAccInput(user[0].Address)
|
||||
eidOutput := types.EventStringAccOutput(toAddr)
|
||||
subscribe(t, con, eidInput)
|
||||
subscribe(t, con, eidOutput)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eidInput)
|
||||
unsubscribe(t, con, eidOutput)
|
||||
con.Close()
|
||||
}()
|
||||
waitForEvent(t, con, eidInput, true, func() {
|
||||
tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt)
|
||||
broadcastTx(t, wsTyp, tx)
|
||||
}, unmarshalValidateSend(amt, toAddr))
|
||||
waitForEvent(t, con, eidOutput, true, func() {}, unmarshalValidateSend(amt, toAddr))
|
||||
}
|
||||
|
||||
// ensure events are only fired once for a given transaction
|
||||
func TestWSDoubleFire(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringAccInput(user[0].Address)
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
amt := int64(100)
|
||||
toAddr := user[1].Address
|
||||
// broadcast the transaction, wait to hear about it
|
||||
waitForEvent(t, con, eid, true, func() {
|
||||
tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt)
|
||||
broadcastTx(t, wsTyp, tx)
|
||||
}, func(eid string, b []byte) error {
|
||||
return nil
|
||||
})
|
||||
// but make sure we don't hear about it twice
|
||||
waitForEvent(t, con, eid, false, func() {
|
||||
}, func(eid string, b []byte) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// create a contract, wait for the event, and send it a msg, validate the return
|
||||
func TestWSCallWait(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
con := newWSCon(t)
|
||||
eid1 := types.EventStringAccInput(user[0].Address)
|
||||
subscribe(t, con, eid1)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid1)
|
||||
con.Close()
|
||||
}()
|
||||
amt, gasLim, fee := int64(10000), int64(1000), int64(1000)
|
||||
code, returnCode, returnVal := simpleContract()
|
||||
var contractAddr []byte
|
||||
// wait for the contract to be created
|
||||
waitForEvent(t, con, eid1, true, func() {
|
||||
tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
|
||||
receipt := broadcastTx(t, wsTyp, tx)
|
||||
contractAddr = receipt.ContractAddr
|
||||
}, unmarshalValidateTx(amt, returnCode))
|
||||
|
||||
// susbscribe to the new contract
|
||||
amt = int64(10001)
|
||||
eid2 := types.EventStringAccOutput(contractAddr)
|
||||
subscribe(t, con, eid2)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid2)
|
||||
}()
|
||||
// get the return value from a call
|
||||
data := []byte{0x1}
|
||||
waitForEvent(t, con, eid2, true, func() {
|
||||
tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee)
|
||||
receipt := broadcastTx(t, wsTyp, tx)
|
||||
contractAddr = receipt.ContractAddr
|
||||
}, unmarshalValidateTx(amt, returnVal))
|
||||
}
|
||||
|
||||
// create a contract and send it a msg without waiting. wait for contract event
|
||||
// and validate return
|
||||
func TestWSCallNoWait(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
con := newWSCon(t)
|
||||
amt, gasLim, fee := int64(10000), int64(1000), int64(1000)
|
||||
code, _, returnVal := simpleContract()
|
||||
|
||||
tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
|
||||
receipt := broadcastTx(t, wsTyp, tx)
|
||||
contractAddr := receipt.ContractAddr
|
||||
|
||||
// susbscribe to the new contract
|
||||
amt = int64(10001)
|
||||
eid := types.EventStringAccOutput(contractAddr)
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
// get the return value from a call
|
||||
data := []byte{0x1}
|
||||
waitForEvent(t, con, eid, true, func() {
|
||||
tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee)
|
||||
broadcastTx(t, wsTyp, tx)
|
||||
}, unmarshalValidateTx(amt, returnVal))
|
||||
}
|
||||
|
||||
// create two contracts, one of which calls the other
|
||||
func TestWSCallCall(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
con := newWSCon(t)
|
||||
amt, gasLim, fee := int64(10000), int64(1000), int64(1000)
|
||||
code, _, returnVal := simpleContract()
|
||||
txid := new([]byte)
|
||||
|
||||
// deploy the two contracts
|
||||
tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
|
||||
receipt := broadcastTx(t, wsTyp, tx)
|
||||
contractAddr1 := receipt.ContractAddr
|
||||
|
||||
code, _, _ = simpleCallContract(contractAddr1)
|
||||
tx = makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
|
||||
receipt = broadcastTx(t, wsTyp, tx)
|
||||
contractAddr2 := receipt.ContractAddr
|
||||
|
||||
// susbscribe to the new contracts
|
||||
amt = int64(10001)
|
||||
eid1 := types.EventStringAccCall(contractAddr1)
|
||||
subscribe(t, con, eid1)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid1)
|
||||
con.Close()
|
||||
}()
|
||||
// call contract2, which should call contract1, and wait for ev1
|
||||
|
||||
// let the contract get created first
|
||||
waitForEvent(t, con, eid1, true, func() {
|
||||
}, func(eid string, b []byte) error {
|
||||
return nil
|
||||
})
|
||||
// call it
|
||||
waitForEvent(t, con, eid1, true, func() {
|
||||
tx := makeDefaultCallTx(t, wsTyp, contractAddr2, nil, amt, gasLim, fee)
|
||||
broadcastTx(t, wsTyp, tx)
|
||||
*txid = types.TxID(chainID, tx)
|
||||
}, unmarshalValidateCall(user[0].Address, returnVal, txid))
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
cfg "github.com/tendermint/go-config"
|
||||
)
|
||||
|
||||
var config cfg.Config = nil
|
||||
|
||||
func init() {
|
||||
cfg.OnConfig(func(newConfig cfg.Config) {
|
||||
config = newConfig
|
||||
})
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/go-p2p"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
cclient "github.com/tendermint/tendermint/rpc/core_client"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// global variables for use across all tests
|
||||
var (
|
||||
rpcAddr = "127.0.0.1:36657" // Not 46657
|
||||
requestAddr = "http://" + rpcAddr + "/"
|
||||
websocketAddr = "ws://" + rpcAddr + "/websocket"
|
||||
|
||||
node *nm.Node
|
||||
|
||||
mempoolCount = 0
|
||||
|
||||
// make keys
|
||||
user = makeUsers(5)
|
||||
|
||||
chainID string
|
||||
|
||||
clients = map[string]cclient.Client{
|
||||
"JSONRPC": cclient.NewClient(requestAddr, "JSONRPC"),
|
||||
"HTTP": cclient.NewClient(requestAddr, "HTTP"),
|
||||
}
|
||||
)
|
||||
|
||||
// deterministic account generation, synced with genesis file in config/tendermint_test/config.go
|
||||
func makeUsers(n int) []*acm.PrivAccount {
|
||||
accounts := []*acm.PrivAccount{}
|
||||
for i := 0; i < n; i++ {
|
||||
secret := ("mysecret" + strconv.Itoa(i))
|
||||
user := acm.GenPrivAccountFromSecret(secret)
|
||||
accounts = append(accounts, user)
|
||||
}
|
||||
return accounts
|
||||
}
|
||||
|
||||
// create a new node and sleep forever
|
||||
func newNode(ready chan struct{}) {
|
||||
// Create & start node
|
||||
node = nm.NewNode()
|
||||
l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr"))
|
||||
node.AddListener(l)
|
||||
node.Start()
|
||||
|
||||
// Run the RPC server.
|
||||
node.StartRPC()
|
||||
ready <- struct{}{}
|
||||
|
||||
// Sleep forever
|
||||
ch := make(chan struct{})
|
||||
<-ch
|
||||
}
|
||||
|
||||
// initialize config and create new node
|
||||
func init() {
|
||||
chainID = config.GetString("chain_id")
|
||||
|
||||
// Save new priv_validator file.
|
||||
priv := &types.PrivValidator{
|
||||
Address: user[0].Address,
|
||||
PubKey: acm.PubKeyEd25519(user[0].PubKey.(acm.PubKeyEd25519)),
|
||||
PrivKey: acm.PrivKeyEd25519(user[0].PrivKey.(acm.PrivKeyEd25519)),
|
||||
}
|
||||
priv.SetFile(config.GetString("priv_validator_file"))
|
||||
priv.Save()
|
||||
|
||||
// TODO: change consensus/state.go timeouts to be shorter
|
||||
|
||||
// start a node
|
||||
ready := make(chan struct{})
|
||||
go newNode(ready)
|
||||
<-ready
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// some default transaction functions
|
||||
|
||||
func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt int64) *types.SendTx {
|
||||
nonce := getNonce(t, typ, user[0].Address)
|
||||
tx := types.NewSendTx()
|
||||
tx.AddInputWithNonce(user[0].PubKey, amt, nonce+1)
|
||||
tx.AddOutput(addr, amt)
|
||||
return tx
|
||||
}
|
||||
|
||||
func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, amt int64) *types.SendTx {
|
||||
tx := makeDefaultSendTx(t, typ, addr, amt)
|
||||
tx.SignInput(chainID, 0, user[0])
|
||||
return tx
|
||||
}
|
||||
|
||||
func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, fee int64) *types.CallTx {
|
||||
nonce := getNonce(t, typ, user[0].Address)
|
||||
tx := types.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce+1)
|
||||
tx.Sign(chainID, user[0])
|
||||
return tx
|
||||
}
|
||||
|
||||
func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee int64) *types.NameTx {
|
||||
nonce := getNonce(t, typ, user[0].Address)
|
||||
tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1)
|
||||
tx.Sign(chainID, user[0])
|
||||
return tx
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// rpc call wrappers (fail on err)
|
||||
|
||||
// get an account's nonce
|
||||
func getNonce(t *testing.T, typ string, addr []byte) int {
|
||||
client := clients[typ]
|
||||
ac, err := client.GetAccount(addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ac.Account == nil {
|
||||
return 0
|
||||
}
|
||||
return ac.Account.Sequence
|
||||
}
|
||||
|
||||
// get the account
|
||||
func getAccount(t *testing.T, typ string, addr []byte) *acm.Account {
|
||||
client := clients[typ]
|
||||
ac, err := client.GetAccount(addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ac.Account
|
||||
}
|
||||
|
||||
// sign transaction
|
||||
func signTx(t *testing.T, typ string, tx types.Tx, privAcc *acm.PrivAccount) types.Tx {
|
||||
client := clients[typ]
|
||||
signedTx, err := client.SignTx(tx, []*acm.PrivAccount{privAcc})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return signedTx.Tx
|
||||
}
|
||||
|
||||
// broadcast transaction
|
||||
func broadcastTx(t *testing.T, typ string, tx types.Tx) ctypes.Receipt {
|
||||
client := clients[typ]
|
||||
rec, err := client.BroadcastTx(tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mempoolCount += 1
|
||||
return rec.Receipt
|
||||
}
|
||||
|
||||
// dump all storage for an account. currently unused
|
||||
func dumpStorage(t *testing.T, addr []byte) ctypes.ResultDumpStorage {
|
||||
client := clients["HTTP"]
|
||||
resp, err := client.DumpStorage(addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return *resp
|
||||
}
|
||||
|
||||
func getStorage(t *testing.T, typ string, addr, key []byte) []byte {
|
||||
client := clients[typ]
|
||||
resp, err := client.GetStorage(addr, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return resp.Value
|
||||
}
|
||||
|
||||
func callCode(t *testing.T, client cclient.Client, fromAddress, code, data, expected []byte) {
|
||||
resp, err := client.CallCode(fromAddress, code, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret := resp.Return
|
||||
// NOTE: we don't flip memory when it comes out of RETURN (?!)
|
||||
if bytes.Compare(ret, LeftPadWord256(expected).Bytes()) != 0 {
|
||||
t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func callContract(t *testing.T, client cclient.Client, fromAddress, toAddress, data, expected []byte) {
|
||||
resp, err := client.Call(fromAddress, toAddress, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret := resp.Return
|
||||
// NOTE: we don't flip memory when it comes out of RETURN (?!)
|
||||
if bytes.Compare(ret, LeftPadWord256(expected).Bytes()) != 0 {
|
||||
t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// get the namereg entry
|
||||
func getNameRegEntry(t *testing.T, typ string, name string) *types.NameRegEntry {
|
||||
client := clients[typ]
|
||||
entry, err := client.GetName(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return entry.Entry
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// utility verification function
|
||||
|
||||
func checkTx(t *testing.T, fromAddr []byte, priv *acm.PrivAccount, tx *types.SendTx) {
|
||||
if bytes.Compare(tx.Inputs[0].Address, fromAddr) != 0 {
|
||||
t.Fatal("Tx input addresses don't match!")
|
||||
}
|
||||
|
||||
signBytes := acm.SignBytes(chainID, tx)
|
||||
in := tx.Inputs[0] //(*types.SendTx).Inputs[0]
|
||||
|
||||
if err := in.ValidateBasic(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check signatures
|
||||
// acc := getAccount(t, byteAddr)
|
||||
// NOTE: using the acc here instead of the in fails; it is nil.
|
||||
if !in.PubKey.VerifyBytes(signBytes, in.Signature) {
|
||||
t.Fatal(types.ErrTxInvalidSignature)
|
||||
}
|
||||
}
|
||||
|
||||
// simple contract returns 5 + 6 = 0xb
|
||||
func simpleContract() ([]byte, []byte, []byte) {
|
||||
// this is the code we want to run when the contract is called
|
||||
contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
|
||||
// the is the code we need to return the contractCode when the contract is initialized
|
||||
lenCode := len(contractCode)
|
||||
// push code to the stack
|
||||
//code := append([]byte{byte(0x60 + lenCode - 1)}, RightPadWord256(contractCode).Bytes()...)
|
||||
code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...)
|
||||
// store it in memory
|
||||
code = append(code, []byte{0x60, 0x0, 0x52}...)
|
||||
// return whats in memory
|
||||
//code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...)
|
||||
code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...)
|
||||
// return init code, contract code, expected return
|
||||
return code, contractCode, LeftPadBytes([]byte{0xb}, 32)
|
||||
}
|
||||
|
||||
// simple call contract calls another contract
|
||||
func simpleCallContract(addr []byte) ([]byte, []byte, []byte) {
|
||||
gas1, gas2 := byte(0x1), byte(0x1)
|
||||
value := byte(0x1)
|
||||
inOff, inSize := byte(0x0), byte(0x0) // no call data
|
||||
retOff, retSize := byte(0x0), byte(0x20)
|
||||
// this is the code we want to run (call a contract and return)
|
||||
contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73}
|
||||
contractCode = append(contractCode, addr...)
|
||||
contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...)
|
||||
|
||||
// the is the code we need to return; the contractCode when the contract is initialized
|
||||
// it should copy the code from the input into memory
|
||||
lenCode := len(contractCode)
|
||||
memOff := byte(0x0)
|
||||
inOff = byte(0xc) // length of code before codeContract
|
||||
length := byte(lenCode)
|
||||
|
||||
code := []byte{0x60, length, 0x60, inOff, 0x60, memOff, 0x37}
|
||||
// return whats in memory
|
||||
code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...)
|
||||
code = append(code, contractCode...)
|
||||
// return init code, contract code, expected return
|
||||
return code, contractCode, LeftPadBytes([]byte{0xb}, 32)
|
||||
}
|
@ -1,281 +0,0 @@
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var doNothing = func(eid string, b []byte) error { return nil }
|
||||
|
||||
func testStatus(t *testing.T, typ string) {
|
||||
client := clients[typ]
|
||||
resp, err := client.Status()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.NodeInfo.ChainID != chainID {
|
||||
t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s",
|
||||
resp.NodeInfo.ChainID, chainID))
|
||||
}
|
||||
}
|
||||
|
||||
func testGenPriv(t *testing.T, typ string) {
|
||||
client := clients[typ]
|
||||
privAcc, err := client.GenPrivAccount()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(privAcc.PrivAccount.Address) == 0 {
|
||||
t.Fatal("Failed to generate an address")
|
||||
}
|
||||
}
|
||||
|
||||
func testGetAccount(t *testing.T, typ string) {
|
||||
acc := getAccount(t, typ, user[0].Address)
|
||||
if acc == nil {
|
||||
t.Fatalf("Account was nil")
|
||||
}
|
||||
if bytes.Compare(acc.Address, user[0].Address) != 0 {
|
||||
t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, user[0].Address)
|
||||
}
|
||||
}
|
||||
|
||||
func testSignedTx(t *testing.T, typ string) {
|
||||
amt := int64(100)
|
||||
toAddr := user[1].Address
|
||||
testOneSignTx(t, typ, toAddr, amt)
|
||||
|
||||
toAddr = user[2].Address
|
||||
testOneSignTx(t, typ, toAddr, amt)
|
||||
|
||||
toAddr = user[3].Address
|
||||
testOneSignTx(t, typ, toAddr, amt)
|
||||
}
|
||||
|
||||
func testOneSignTx(t *testing.T, typ string, addr []byte, amt int64) {
|
||||
tx := makeDefaultSendTx(t, typ, addr, amt)
|
||||
tx2 := signTx(t, typ, tx, user[0])
|
||||
tx2hash := types.TxID(chainID, tx2)
|
||||
tx.SignInput(chainID, 0, user[0])
|
||||
txhash := types.TxID(chainID, tx)
|
||||
if bytes.Compare(txhash, tx2hash) != 0 {
|
||||
t.Fatal("Got different signatures for signing via rpc vs tx_utils")
|
||||
}
|
||||
|
||||
tx_ := signTx(t, typ, tx, user[0])
|
||||
tx = tx_.(*types.SendTx)
|
||||
checkTx(t, user[0].Address, user[0], tx)
|
||||
}
|
||||
|
||||
func testBroadcastTx(t *testing.T, typ string) {
|
||||
amt := int64(100)
|
||||
toAddr := user[1].Address
|
||||
tx := makeDefaultSendTxSigned(t, typ, toAddr, amt)
|
||||
receipt := broadcastTx(t, typ, tx)
|
||||
if receipt.CreatesContract > 0 {
|
||||
t.Fatal("This tx does not create a contract")
|
||||
}
|
||||
if len(receipt.TxHash) == 0 {
|
||||
t.Fatal("Failed to compute tx hash")
|
||||
}
|
||||
pool := node.MempoolReactor().Mempool
|
||||
txs := pool.GetProposalTxs()
|
||||
if len(txs) != mempoolCount {
|
||||
t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount)
|
||||
}
|
||||
tx2 := txs[mempoolCount-1].(*types.SendTx)
|
||||
n, err := new(int64), new(error)
|
||||
buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer)
|
||||
tx.WriteSignBytes(chainID, buf1, n, err)
|
||||
tx2.WriteSignBytes(chainID, buf2, n, err)
|
||||
if bytes.Compare(buf1.Bytes(), buf2.Bytes()) != 0 {
|
||||
t.Fatal("inconsistent hashes for mempool tx and sent tx")
|
||||
}
|
||||
}
|
||||
|
||||
func testGetStorage(t *testing.T, typ string) {
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringNewBlock()
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
|
||||
amt, gasLim, fee := int64(1100), int64(1000), int64(1000)
|
||||
code := []byte{0x60, 0x5, 0x60, 0x1, 0x55}
|
||||
tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee)
|
||||
receipt := broadcastTx(t, typ, tx)
|
||||
if receipt.CreatesContract == 0 {
|
||||
t.Fatal("This tx creates a contract")
|
||||
}
|
||||
if len(receipt.TxHash) == 0 {
|
||||
t.Fatal("Failed to compute tx hash")
|
||||
}
|
||||
contractAddr := receipt.ContractAddr
|
||||
if len(contractAddr) == 0 {
|
||||
t.Fatal("Creates contract but resulting address is empty")
|
||||
}
|
||||
|
||||
// allow it to get mined
|
||||
waitForEvent(t, con, eid, true, func() {}, doNothing)
|
||||
mempoolCount = 0
|
||||
|
||||
v := getStorage(t, typ, contractAddr, []byte{0x1})
|
||||
got := LeftPadWord256(v)
|
||||
expected := LeftPadWord256([]byte{0x5})
|
||||
if got.Compare(expected) != 0 {
|
||||
t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func testCallCode(t *testing.T, typ string) {
|
||||
client := clients[typ]
|
||||
|
||||
// add two integers and return the result
|
||||
code := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
|
||||
data := []byte{}
|
||||
expected := []byte{0xb}
|
||||
callCode(t, client, user[0].PubKey.Address(), code, data, expected)
|
||||
|
||||
// pass two ints as calldata, add, and return the result
|
||||
code = []byte{0x60, 0x0, 0x35, 0x60, 0x20, 0x35, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
|
||||
data = append(LeftPadWord256([]byte{0x5}).Bytes(), LeftPadWord256([]byte{0x6}).Bytes()...)
|
||||
expected = []byte{0xb}
|
||||
callCode(t, client, user[0].PubKey.Address(), code, data, expected)
|
||||
}
|
||||
|
||||
func testCall(t *testing.T, typ string) {
|
||||
con := newWSCon(t)
|
||||
eid := types.EventStringNewBlock()
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
|
||||
client := clients[typ]
|
||||
|
||||
// create the contract
|
||||
amt, gasLim, fee := int64(6969), int64(1000), int64(1000)
|
||||
code, _, _ := simpleContract()
|
||||
tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee)
|
||||
receipt := broadcastTx(t, typ, tx)
|
||||
|
||||
if receipt.CreatesContract == 0 {
|
||||
t.Fatal("This tx creates a contract")
|
||||
}
|
||||
if len(receipt.TxHash) == 0 {
|
||||
t.Fatal("Failed to compute tx hash")
|
||||
}
|
||||
contractAddr := receipt.ContractAddr
|
||||
if len(contractAddr) == 0 {
|
||||
t.Fatal("Creates contract but resulting address is empty")
|
||||
}
|
||||
|
||||
// allow it to get mined
|
||||
waitForEvent(t, con, eid, true, func() {}, doNothing)
|
||||
mempoolCount = 0
|
||||
|
||||
// run a call through the contract
|
||||
data := []byte{}
|
||||
expected := []byte{0xb}
|
||||
callContract(t, client, user[0].PubKey.Address(), contractAddr, data, expected)
|
||||
}
|
||||
|
||||
func testNameReg(t *testing.T, typ string) {
|
||||
client := clients[typ]
|
||||
con := newWSCon(t)
|
||||
|
||||
types.MinNameRegistrationPeriod = 1
|
||||
|
||||
// register a new name, check if its there
|
||||
// since entries ought to be unique and these run against different clients, we append the typ
|
||||
name := "ye_old_domain_name_" + typ
|
||||
data := "if not now, when"
|
||||
fee := int64(1000)
|
||||
numDesiredBlocks := int64(2)
|
||||
amt := fee + numDesiredBlocks*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
|
||||
|
||||
eid := types.EventStringNameReg(name)
|
||||
subscribe(t, con, eid)
|
||||
|
||||
tx := makeDefaultNameTx(t, typ, name, data, amt, fee)
|
||||
broadcastTx(t, typ, tx)
|
||||
// verify the name by both using the event and by checking get_name
|
||||
waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error {
|
||||
// TODO: unmarshal the response
|
||||
tx, err := unmarshalResponseNameReg(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Name != name {
|
||||
t.Fatal(fmt.Sprintf("Err on received event tx.Name: Got %s, expected %s", tx.Name, name))
|
||||
}
|
||||
if tx.Data != data {
|
||||
t.Fatal(fmt.Sprintf("Err on received event tx.Data: Got %s, expected %s", tx.Data, data))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
mempoolCount = 0
|
||||
entry := getNameRegEntry(t, typ, name)
|
||||
if entry.Data != data {
|
||||
t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data))
|
||||
}
|
||||
if bytes.Compare(entry.Owner, user[0].Address) != 0 {
|
||||
t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[0].Address))
|
||||
}
|
||||
|
||||
unsubscribe(t, con, eid)
|
||||
|
||||
// for the rest we just use new block event
|
||||
// since we already tested the namereg event
|
||||
eid = types.EventStringNewBlock()
|
||||
subscribe(t, con, eid)
|
||||
defer func() {
|
||||
unsubscribe(t, con, eid)
|
||||
con.Close()
|
||||
}()
|
||||
|
||||
// update the data as the owner, make sure still there
|
||||
numDesiredBlocks = int64(2)
|
||||
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"
|
||||
amt = fee + numDesiredBlocks*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
|
||||
tx = makeDefaultNameTx(t, typ, name, data, amt, fee)
|
||||
broadcastTx(t, typ, tx)
|
||||
// commit block
|
||||
waitForEvent(t, con, eid, true, func() {}, doNothing)
|
||||
mempoolCount = 0
|
||||
entry = getNameRegEntry(t, typ, name)
|
||||
if entry.Data != data {
|
||||
t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data))
|
||||
}
|
||||
|
||||
// try to update as non owner, should fail
|
||||
nonce := getNonce(t, typ, user[1].Address)
|
||||
data2 := "this is not my beautiful house"
|
||||
tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1)
|
||||
tx.Sign(chainID, user[1])
|
||||
_, err := client.BroadcastTx(tx)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error on NameTx")
|
||||
}
|
||||
|
||||
// commit block
|
||||
waitForEvent(t, con, eid, true, func() {}, doNothing)
|
||||
|
||||
// now the entry should be expired, so we can update as non owner
|
||||
_, err = client.BroadcastTx(tx)
|
||||
waitForEvent(t, con, eid, true, func() {}, doNothing)
|
||||
mempoolCount = 0
|
||||
entry = getNameRegEntry(t, typ, name)
|
||||
if entry.Data != data2 {
|
||||
t.Fatal(fmt.Sprintf("Error on entry.Data: Got %s, expected %s", entry.Data, data2))
|
||||
}
|
||||
if bytes.Compare(entry.Owner, user[1].Address) != 0 {
|
||||
t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[1].Address))
|
||||
}
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
package rpctest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/tendermint/go-wire"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/rpc/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Utilities for testing the websocket service
|
||||
|
||||
// create a new connection
|
||||
func newWSCon(t *testing.T) *websocket.Conn {
|
||||
dialer := websocket.DefaultDialer
|
||||
rHeader := http.Header{}
|
||||
con, r, err := dialer.Dial(websocketAddr, rHeader)
|
||||
fmt.Println("response", r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return con
|
||||
}
|
||||
|
||||
// subscribe to an event
|
||||
func subscribe(t *testing.T, con *websocket.Conn, eventid string) {
|
||||
err := con.WriteJSON(rpctypes.RPCRequest{
|
||||
JSONRPC: "2.0",
|
||||
ID: "",
|
||||
Method: "subscribe",
|
||||
Params: []interface{}{eventid},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// unsubscribe from an event
|
||||
func unsubscribe(t *testing.T, con *websocket.Conn, eventid string) {
|
||||
err := con.WriteJSON(rpctypes.RPCRequest{
|
||||
JSONRPC: "2.0",
|
||||
ID: "",
|
||||
Method: "unsubscribe",
|
||||
Params: []interface{}{eventid},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for an event; do things that might trigger events, and check them when they are received
|
||||
// the check function takes an event id and the byte slice read off the ws
|
||||
func waitForEvent(t *testing.T, con *websocket.Conn, eventid string, dieOnTimeout bool, f func(), check func(string, []byte) error) {
|
||||
// go routine to wait for webscoket msg
|
||||
goodCh := make(chan []byte)
|
||||
errCh := make(chan error)
|
||||
quitCh := make(chan struct{})
|
||||
defer close(quitCh)
|
||||
|
||||
// Read message
|
||||
go func() {
|
||||
for {
|
||||
_, p, err := con.ReadMessage()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
break
|
||||
} else {
|
||||
// if the event id isnt what we're waiting on
|
||||
// ignore it
|
||||
var response ctypes.Response
|
||||
var err error
|
||||
wire.ReadJSON(&response, p, &err)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
break
|
||||
}
|
||||
event, ok := response.Result.(*ctypes.ResultEvent)
|
||||
if ok && event.Event == eventid {
|
||||
goodCh <- p
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// do stuff (transactions)
|
||||
f()
|
||||
|
||||
// wait for an event or timeout
|
||||
timeout := time.NewTimer(10 * time.Second)
|
||||
select {
|
||||
case <-timeout.C:
|
||||
if dieOnTimeout {
|
||||
con.Close()
|
||||
t.Fatalf("%s event was not received in time", eventid)
|
||||
}
|
||||
// else that's great, we didn't hear the event
|
||||
// and we shouldn't have
|
||||
case p := <-goodCh:
|
||||
if dieOnTimeout {
|
||||
// message was received and expected
|
||||
// run the check
|
||||
err := check(eventid, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
panic(err) // Show the stack trace.
|
||||
}
|
||||
} else {
|
||||
con.Close()
|
||||
t.Fatalf("%s event was not expected", eventid)
|
||||
}
|
||||
case err := <-errCh:
|
||||
t.Fatal(err)
|
||||
panic(err) // Show the stack trace.
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func unmarshalResponseNewBlock(b []byte) (*types.Block, error) {
|
||||
// unmarshall and assert somethings
|
||||
var response ctypes.Response
|
||||
var err error
|
||||
wire.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return nil, fmt.Errorf(response.Error)
|
||||
}
|
||||
block := response.Result.(*ctypes.ResultEvent).Data.(types.EventDataNewBlock).Block
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func unmarshalResponseNameReg(b []byte) (*types.NameTx, error) {
|
||||
// unmarshall and assert somethings
|
||||
var response ctypes.Response
|
||||
var err error
|
||||
wire.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return nil, fmt.Errorf(response.Error)
|
||||
}
|
||||
tx := response.Result.(*ctypes.ResultEvent).Data.(types.EventDataTx).Tx.(*types.NameTx)
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func unmarshalValidateBlockchain(t *testing.T, con *websocket.Conn, eid string) {
|
||||
var initBlockN int
|
||||
for i := 0; i < 2; i++ {
|
||||
waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error {
|
||||
block, err := unmarshalResponseNewBlock(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i == 0 {
|
||||
initBlockN = block.Header.Height
|
||||
} else {
|
||||
if block.Header.Height != initBlockN+i {
|
||||
return fmt.Errorf("Expected block %d, got block %d", i, block.Header.Height)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalValidateSend(amt int64, toAddr []byte) func(string, []byte) error {
|
||||
return func(eid string, b []byte) error {
|
||||
// unmarshal and assert correctness
|
||||
var response ctypes.Response
|
||||
var err error
|
||||
wire.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf(response.Error)
|
||||
}
|
||||
if eid != response.Result.(*ctypes.ResultEvent).Event {
|
||||
return fmt.Errorf("Eventid is not correct. Got %s, expected %s", response.Result.(*ctypes.ResultEvent).Event, eid)
|
||||
}
|
||||
tx := response.Result.(*ctypes.ResultEvent).Data.(types.EventDataTx).Tx.(*types.SendTx)
|
||||
if !bytes.Equal(tx.Inputs[0].Address, user[0].Address) {
|
||||
return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, user[0].Address)
|
||||
}
|
||||
if tx.Inputs[0].Amount != amt {
|
||||
return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt)
|
||||
}
|
||||
if !bytes.Equal(tx.Outputs[0].Address, toAddr) {
|
||||
return fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, user[0].Address)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalValidateTx(amt int64, returnCode []byte) func(string, []byte) error {
|
||||
return func(eid string, b []byte) error {
|
||||
// unmarshall and assert somethings
|
||||
var response ctypes.Response
|
||||
var err error
|
||||
wire.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf(response.Error)
|
||||
}
|
||||
var data = response.Result.(*ctypes.ResultEvent).Data.(types.EventDataTx)
|
||||
if data.Exception != "" {
|
||||
return fmt.Errorf(data.Exception)
|
||||
}
|
||||
tx := data.Tx.(*types.CallTx)
|
||||
if !bytes.Equal(tx.Input.Address, user[0].Address) {
|
||||
return fmt.Errorf("Senders do not match up! Got %x, expected %x",
|
||||
tx.Input.Address, user[0].Address)
|
||||
}
|
||||
if tx.Input.Amount != amt {
|
||||
return fmt.Errorf("Amt does not match up! Got %d, expected %d",
|
||||
tx.Input.Amount, amt)
|
||||
}
|
||||
ret := data.Return
|
||||
if !bytes.Equal(ret, returnCode) {
|
||||
return fmt.Errorf("Tx did not return correctly. Got %x, expected %x", ret, returnCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalValidateCall(origin, returnCode []byte, txid *[]byte) func(string, []byte) error {
|
||||
return func(eid string, b []byte) error {
|
||||
// unmarshall and assert somethings
|
||||
var response ctypes.Response
|
||||
var err error
|
||||
wire.ReadJSON(&response, b, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.Error != "" {
|
||||
return fmt.Errorf(response.Error)
|
||||
}
|
||||
var data = response.Result.(*ctypes.ResultEvent).Data.(types.EventDataCall)
|
||||
if data.Exception != "" {
|
||||
return fmt.Errorf(data.Exception)
|
||||
}
|
||||
if !bytes.Equal(data.Origin, origin) {
|
||||
return fmt.Errorf("Origin does not match up! Got %x, expected %x",
|
||||
data.Origin, origin)
|
||||
}
|
||||
ret := data.Return
|
||||
if !bytes.Equal(ret, returnCode) {
|
||||
return fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnCode)
|
||||
}
|
||||
if !bytes.Equal(data.TxID, *txid) {
|
||||
return fmt.Errorf("TxIDs do not match up! Got %x, expected %x",
|
||||
data.TxID, *txid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,293 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
dbm "github.com/tendermint/go-db"
|
||||
"github.com/tendermint/go-merkle"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
func makeStorage(db dbm.DB, root []byte) merkle.Tree {
|
||||
storage := merkle.NewIAVLTree(
|
||||
wire.BasicCodec,
|
||||
wire.BasicCodec,
|
||||
1024,
|
||||
db,
|
||||
)
|
||||
storage.Load(root)
|
||||
return storage
|
||||
}
|
||||
|
||||
// The blockcache helps prevent unnecessary IAVLTree updates and garbage generation.
|
||||
type BlockCache struct {
|
||||
db dbm.DB
|
||||
backend *State
|
||||
accounts map[string]accountInfo
|
||||
storages map[Tuple256]storageInfo
|
||||
names map[string]nameInfo
|
||||
}
|
||||
|
||||
func NewBlockCache(backend *State) *BlockCache {
|
||||
return &BlockCache{
|
||||
db: backend.DB,
|
||||
backend: backend,
|
||||
accounts: make(map[string]accountInfo),
|
||||
storages: make(map[Tuple256]storageInfo),
|
||||
names: make(map[string]nameInfo),
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *BlockCache) State() *State {
|
||||
return cache.backend
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// BlockCache.account
|
||||
|
||||
func (cache *BlockCache) GetAccount(addr []byte) *acm.Account {
|
||||
acc, _, removed, _ := cache.accounts[string(addr)].unpack()
|
||||
if removed {
|
||||
return nil
|
||||
} else if acc != nil {
|
||||
return acc
|
||||
} else {
|
||||
acc = cache.backend.GetAccount(addr)
|
||||
cache.accounts[string(addr)] = accountInfo{acc, nil, false, false}
|
||||
return acc
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *BlockCache) UpdateAccount(acc *acm.Account) {
|
||||
addr := acc.Address
|
||||
_, storage, removed, _ := cache.accounts[string(addr)].unpack()
|
||||
if removed {
|
||||
PanicSanity("UpdateAccount on a removed account")
|
||||
}
|
||||
cache.accounts[string(addr)] = accountInfo{acc, storage, false, true}
|
||||
}
|
||||
|
||||
func (cache *BlockCache) RemoveAccount(addr []byte) {
|
||||
_, _, removed, _ := cache.accounts[string(addr)].unpack()
|
||||
if removed {
|
||||
PanicSanity("RemoveAccount on a removed account")
|
||||
}
|
||||
cache.accounts[string(addr)] = accountInfo{nil, nil, true, false}
|
||||
}
|
||||
|
||||
// BlockCache.account
|
||||
//-------------------------------------
|
||||
// BlockCache.storage
|
||||
|
||||
func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) {
|
||||
// Check cache
|
||||
info, ok := cache.storages[Tuple256{addr, key}]
|
||||
if ok {
|
||||
return info.value
|
||||
}
|
||||
|
||||
// Get or load storage
|
||||
acc, storage, removed, dirty := cache.accounts[string(addr.Postfix(20))].unpack()
|
||||
if removed {
|
||||
PanicSanity("GetStorage() on removed account")
|
||||
}
|
||||
if acc != nil && storage == nil {
|
||||
storage = makeStorage(cache.db, acc.StorageRoot)
|
||||
cache.accounts[string(addr.Postfix(20))] = accountInfo{acc, storage, false, dirty}
|
||||
} else if acc == nil {
|
||||
return Zero256
|
||||
}
|
||||
|
||||
// Load and set cache
|
||||
_, val_ := storage.Get(key.Bytes())
|
||||
value = Zero256
|
||||
if val_ != nil {
|
||||
value = LeftPadWord256(val_.([]byte))
|
||||
}
|
||||
cache.storages[Tuple256{addr, key}] = storageInfo{value, false}
|
||||
return value
|
||||
}
|
||||
|
||||
// NOTE: Set value to zero to removed from the trie.
|
||||
func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) {
|
||||
_, _, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack()
|
||||
if removed {
|
||||
PanicSanity("SetStorage() on a removed account")
|
||||
}
|
||||
cache.storages[Tuple256{addr, key}] = storageInfo{value, true}
|
||||
}
|
||||
|
||||
// BlockCache.storage
|
||||
//-------------------------------------
|
||||
// BlockCache.names
|
||||
|
||||
func (cache *BlockCache) GetNameRegEntry(name string) *types.NameRegEntry {
|
||||
entry, removed, _ := cache.names[name].unpack()
|
||||
if removed {
|
||||
return nil
|
||||
} else if entry != nil {
|
||||
return entry
|
||||
} else {
|
||||
entry = cache.backend.GetNameRegEntry(name)
|
||||
cache.names[name] = nameInfo{entry, false, false}
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) {
|
||||
name := entry.Name
|
||||
cache.names[name] = nameInfo{entry, false, true}
|
||||
}
|
||||
|
||||
func (cache *BlockCache) RemoveNameRegEntry(name string) {
|
||||
_, removed, _ := cache.names[name].unpack()
|
||||
if removed {
|
||||
PanicSanity("RemoveNameRegEntry on a removed entry")
|
||||
}
|
||||
cache.names[name] = nameInfo{nil, true, false}
|
||||
}
|
||||
|
||||
// BlockCache.names
|
||||
//-------------------------------------
|
||||
|
||||
// CONTRACT the updates are in deterministic order.
|
||||
func (cache *BlockCache) Sync() {
|
||||
|
||||
// Determine order for storage updates
|
||||
// The address comes first so it'll be grouped.
|
||||
storageKeys := make([]Tuple256, 0, len(cache.storages))
|
||||
for keyTuple := range cache.storages {
|
||||
storageKeys = append(storageKeys, keyTuple)
|
||||
}
|
||||
Tuple256Slice(storageKeys).Sort()
|
||||
|
||||
// Update storage for all account/key.
|
||||
// Later we'll iterate over all the users and save storage + update storage root.
|
||||
var (
|
||||
curAddr Word256
|
||||
curAcc *acm.Account
|
||||
curAccRemoved bool
|
||||
curStorage merkle.Tree
|
||||
)
|
||||
for _, storageKey := range storageKeys {
|
||||
addr, key := Tuple256Split(storageKey)
|
||||
if addr != curAddr || curAcc == nil {
|
||||
acc, storage, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack()
|
||||
if !removed && storage == nil {
|
||||
storage = makeStorage(cache.db, acc.StorageRoot)
|
||||
}
|
||||
curAddr = addr
|
||||
curAcc = acc
|
||||
curAccRemoved = removed
|
||||
curStorage = storage
|
||||
}
|
||||
if curAccRemoved {
|
||||
continue
|
||||
}
|
||||
value, dirty := cache.storages[storageKey].unpack()
|
||||
if !dirty {
|
||||
continue
|
||||
}
|
||||
if value.IsZero() {
|
||||
curStorage.Remove(key.Bytes())
|
||||
} else {
|
||||
curStorage.Set(key.Bytes(), value.Bytes())
|
||||
cache.accounts[string(addr.Postfix(20))] = accountInfo{curAcc, curStorage, false, true}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine order for accounts
|
||||
addrStrs := []string{}
|
||||
for addrStr := range cache.accounts {
|
||||
addrStrs = append(addrStrs, addrStr)
|
||||
}
|
||||
sort.Strings(addrStrs)
|
||||
|
||||
// Update or delete accounts.
|
||||
for _, addrStr := range addrStrs {
|
||||
acc, storage, removed, dirty := cache.accounts[addrStr].unpack()
|
||||
if removed {
|
||||
removed := cache.backend.RemoveAccount([]byte(addrStr))
|
||||
if !removed {
|
||||
PanicCrisis(Fmt("Could not remove account to be removed: %X", acc.Address))
|
||||
}
|
||||
} else {
|
||||
if acc == nil {
|
||||
continue
|
||||
}
|
||||
if storage != nil {
|
||||
newStorageRoot := storage.Save()
|
||||
if !bytes.Equal(newStorageRoot, acc.StorageRoot) {
|
||||
acc.StorageRoot = newStorageRoot
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
if dirty {
|
||||
cache.backend.UpdateAccount(acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine order for names
|
||||
// note names may be of any length less than some limit
|
||||
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(nameStr)
|
||||
if !removed {
|
||||
PanicCrisis(Fmt("Could not remove namereg entry to be removed: %s", nameStr))
|
||||
}
|
||||
} else {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
if dirty {
|
||||
cache.backend.UpdateNameRegEntry(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type accountInfo struct {
|
||||
account *acm.Account
|
||||
storage merkle.Tree
|
||||
removed bool
|
||||
dirty bool
|
||||
}
|
||||
|
||||
func (accInfo accountInfo) unpack() (*acm.Account, merkle.Tree, bool, bool) {
|
||||
return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty
|
||||
}
|
||||
|
||||
type storageInfo struct {
|
||||
value Word256
|
||||
dirty bool
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
type AccountGetter interface {
|
||||
GetAccount(addr []byte) *acm.Account
|
||||
}
|
||||
|
||||
type VMAccountState interface {
|
||||
GetAccount(addr Word256) *vm.Account
|
||||
UpdateAccount(acc *vm.Account)
|
||||
RemoveAccount(acc *vm.Account)
|
||||
CreateAccount(creator *vm.Account) *vm.Account
|
||||
}
|
@ -3,14 +3,10 @@ package state
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ...
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
// NOTE: If an error occurs during block execution, state will be left
|
||||
@ -45,11 +41,11 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
return errors.New("Block at height 1 (first block) should have no LastValidation precommits")
|
||||
}
|
||||
} else {
|
||||
if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() {
|
||||
if len(block.LastValidation.Precommits) != s.LastValidators.Size() {
|
||||
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
|
||||
s.LastBondedValidators.Size(), len(block.LastValidation.Precommits)))
|
||||
s.LastValidators.Size(), len(block.LastValidation.Precommits)))
|
||||
}
|
||||
err := s.LastBondedValidators.VerifyValidation(
|
||||
err := s.LastValidators.VerifyValidation(
|
||||
s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -61,74 +57,34 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
if precommit == nil {
|
||||
continue
|
||||
}
|
||||
_, val := s.LastBondedValidators.GetByIndex(i)
|
||||
_, val := s.LastValidators.GetByIndex(i)
|
||||
if val == nil {
|
||||
PanicCrisis(Fmt("Failed to fetch validator at index %v", i))
|
||||
}
|
||||
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
|
||||
if _, val_ := s.Validators.GetByAddress(val.Address); val_ != nil {
|
||||
val_.LastCommitHeight = block.Height - 1
|
||||
updated := s.BondedValidators.Update(val_)
|
||||
updated := s.Validators.Update(val_)
|
||||
if !updated {
|
||||
PanicCrisis("Failed to update bonded validator LastCommitHeight")
|
||||
}
|
||||
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
|
||||
val_.LastCommitHeight = block.Height - 1
|
||||
updated := s.UnbondingValidators.Update(val_)
|
||||
if !updated {
|
||||
PanicCrisis("Failed to update unbonding validator LastCommitHeight")
|
||||
PanicCrisis("Failed to update validator LastCommitHeight")
|
||||
}
|
||||
} else {
|
||||
PanicCrisis("Could not find validator")
|
||||
}
|
||||
}
|
||||
|
||||
// Remember LastBondedValidators
|
||||
s.LastBondedValidators = s.BondedValidators.Copy()
|
||||
|
||||
// Create BlockCache to cache changes to state.
|
||||
blockCache := NewBlockCache(s)
|
||||
// Remember LastValidators
|
||||
s.LastValidators = s.Validators.Copy()
|
||||
|
||||
// Execute each tx
|
||||
for _, tx := range block.Data.Txs {
|
||||
err := ExecTx(blockCache, tx, true, s.evc)
|
||||
err := ExecTx(s, tx, s.evc)
|
||||
if err != nil {
|
||||
return InvalidTxError{tx, err}
|
||||
}
|
||||
}
|
||||
|
||||
// Now sync the BlockCache to the backend.
|
||||
blockCache.Sync()
|
||||
|
||||
// If any unbonding periods are over,
|
||||
// reward account with bonded coins.
|
||||
toRelease := []*types.Validator{}
|
||||
s.UnbondingValidators.Iterate(func(index int, val *types.Validator) bool {
|
||||
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
|
||||
toRelease = append(toRelease, val)
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, val := range toRelease {
|
||||
s.releaseValidator(val)
|
||||
}
|
||||
|
||||
// If any validators haven't signed in a while,
|
||||
// unbond them, they have timed out.
|
||||
toTimeout := []*types.Validator{}
|
||||
s.BondedValidators.Iterate(func(index int, val *types.Validator) bool {
|
||||
lastActivityHeight := MaxInt(val.BondHeight, val.LastCommitHeight)
|
||||
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
|
||||
log.Notice("Validator timeout", "validator", val, "height", block.Height)
|
||||
toTimeout = append(toTimeout, val)
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, val := range toTimeout {
|
||||
s.unbondValidator(val)
|
||||
}
|
||||
|
||||
// Increment validator AccumPowers
|
||||
s.BondedValidators.IncrementAccum(1)
|
||||
s.Validators.IncrementAccum(1)
|
||||
s.LastBlockHeight = block.Height
|
||||
s.LastBlockHash = block.Hash()
|
||||
s.LastBlockParts = blockPartsHeader
|
||||
@ -136,849 +92,17 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade
|
||||
return nil
|
||||
}
|
||||
|
||||
// The accounts from the TxInputs must either already have
|
||||
// acm.PubKey.(type) != nil, (it must be known),
|
||||
// or it must be specified in the TxInput. If redeclared,
|
||||
// the TxInput is modified and input.PubKey set to nil.
|
||||
func getInputs(state AccountGetter, ins []*types.TxInput) (map[string]*acm.Account, error) {
|
||||
accounts := map[string]*acm.Account{}
|
||||
for _, in := range ins {
|
||||
// Account shouldn't be duplicated
|
||||
if _, ok := accounts[string(in.Address)]; ok {
|
||||
return nil, types.ErrTxDuplicateAddress
|
||||
}
|
||||
acc := state.GetAccount(in.Address)
|
||||
if acc == nil {
|
||||
return nil, types.ErrTxInvalidAddress
|
||||
}
|
||||
// PubKey should be present in either "account" or "in"
|
||||
if err := checkInputPubKey(acc, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts[string(in.Address)] = acc
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, outs []*types.TxOutput) (map[string]*acm.Account, error) {
|
||||
if accounts == nil {
|
||||
accounts = make(map[string]*acm.Account)
|
||||
}
|
||||
|
||||
// we should err if an account is being created but the inputs don't have permission
|
||||
var checkedCreatePerms bool
|
||||
for _, out := range outs {
|
||||
// Account shouldn't be duplicated
|
||||
if _, ok := accounts[string(out.Address)]; ok {
|
||||
return nil, types.ErrTxDuplicateAddress
|
||||
}
|
||||
acc := state.GetAccount(out.Address)
|
||||
// output account may be nil (new)
|
||||
if acc == nil {
|
||||
if !checkedCreatePerms {
|
||||
if !hasCreateAccountPermission(state, accounts) {
|
||||
return nil, fmt.Errorf("At least one input does not have permission to create accounts")
|
||||
}
|
||||
checkedCreatePerms = true
|
||||
}
|
||||
acc = &acm.Account{
|
||||
Address: out.Address,
|
||||
PubKey: nil,
|
||||
Sequence: 0,
|
||||
Balance: 0,
|
||||
Permissions: ptypes.ZeroAccountPermissions,
|
||||
}
|
||||
}
|
||||
accounts[string(out.Address)] = acc
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func checkInputPubKey(acc *acm.Account, in *types.TxInput) error {
|
||||
if acc.PubKey == nil {
|
||||
if in.PubKey == nil {
|
||||
return types.ErrTxUnknownPubKey
|
||||
}
|
||||
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
|
||||
return types.ErrTxInvalidPubKey
|
||||
}
|
||||
acc.PubKey = in.PubKey
|
||||
} else {
|
||||
in.PubKey = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateInputs(accounts map[string]*acm.Account, signBytes []byte, ins []*types.TxInput) (total int64, err error) {
|
||||
for _, in := range ins {
|
||||
acc := accounts[string(in.Address)]
|
||||
if acc == nil {
|
||||
PanicSanity("validateInputs() expects account in accounts")
|
||||
}
|
||||
err = validateInput(acc, signBytes, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Good. Add amount to total
|
||||
total += in.Amount
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func validateInput(acc *acm.Account, signBytes []byte, in *types.TxInput) (err error) {
|
||||
// Check TxInput basic
|
||||
if err := in.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Check signatures
|
||||
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
|
||||
return types.ErrTxInvalidSignature
|
||||
}
|
||||
// Check sequences
|
||||
if acc.Sequence+1 != in.Sequence {
|
||||
return types.ErrTxInvalidSequence{
|
||||
Got: in.Sequence,
|
||||
Expected: acc.Sequence + 1,
|
||||
}
|
||||
}
|
||||
// Check amount
|
||||
if acc.Balance < in.Amount {
|
||||
return types.ErrTxInsufficientFunds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateOutputs(outs []*types.TxOutput) (total int64, err error) {
|
||||
for _, out := range outs {
|
||||
// Check TxOutput basic
|
||||
if err := out.ValidateBasic(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Good. Add amount to total
|
||||
total += out.Amount
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func adjustByInputs(accounts map[string]*acm.Account, ins []*types.TxInput) {
|
||||
for _, in := range ins {
|
||||
acc := accounts[string(in.Address)]
|
||||
if acc == nil {
|
||||
PanicSanity("adjustByInputs() expects account in accounts")
|
||||
}
|
||||
if acc.Balance < in.Amount {
|
||||
PanicSanity("adjustByInputs() expects sufficient funds")
|
||||
}
|
||||
acc.Balance -= in.Amount
|
||||
acc.Sequence += 1
|
||||
}
|
||||
}
|
||||
|
||||
func adjustByOutputs(accounts map[string]*acm.Account, outs []*types.TxOutput) {
|
||||
for _, out := range outs {
|
||||
acc := accounts[string(out.Address)]
|
||||
if acc == nil {
|
||||
PanicSanity("adjustByOutputs() expects account in accounts")
|
||||
}
|
||||
acc.Balance += out.Amount
|
||||
}
|
||||
}
|
||||
|
||||
// If the tx is invalid, an error will be returned.
|
||||
// Unlike ExecBlock(), state will not be altered.
|
||||
func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireable) (err error) {
|
||||
func ExecTx(s *State, tx types.Tx, evc events.Fireable) (err error) {
|
||||
|
||||
// TODO: do something with fees
|
||||
fees := int64(0)
|
||||
_s := blockCache.State() // hack to access validators and block height
|
||||
//fees := int64(0)
|
||||
//_s := blockCache.State() // hack to access validators and block height
|
||||
|
||||
// Exec tx
|
||||
switch tx := tx.(type) {
|
||||
case *types.SendTx:
|
||||
accounts, err := getInputs(blockCache, tx.Inputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// XXX Query ledger application
|
||||
return nil
|
||||
|
||||
// ensure all inputs have send permissions
|
||||
if !hasSendPermission(blockCache, accounts) {
|
||||
return fmt.Errorf("At least one input lacks permission for SendTx")
|
||||
}
|
||||
|
||||
// add outputs to accounts map
|
||||
// if any outputs don't exist, all inputs must have CreateAccount perm
|
||||
accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signBytes := acm.SignBytes(_s.ChainID, tx)
|
||||
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outTotal, err := validateOutputs(tx.Outputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if outTotal > inTotal {
|
||||
return types.ErrTxInsufficientFunds
|
||||
}
|
||||
fee := inTotal - outTotal
|
||||
fees += fee
|
||||
|
||||
// Good! Adjust accounts
|
||||
adjustByInputs(accounts, tx.Inputs)
|
||||
adjustByOutputs(accounts, tx.Outputs)
|
||||
for _, acc := range accounts {
|
||||
blockCache.UpdateAccount(acc)
|
||||
}
|
||||
|
||||
// if the evc is nil, nothing will happen
|
||||
if evc != nil {
|
||||
for _, i := range tx.Inputs {
|
||||
evc.FireEvent(types.EventStringAccInput(i.Address), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
|
||||
for _, o := range tx.Outputs {
|
||||
evc.FireEvent(types.EventStringAccOutput(o.Address), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *types.CallTx:
|
||||
var inAcc, outAcc *acm.Account
|
||||
|
||||
// Validate input
|
||||
inAcc = blockCache.GetAccount(tx.Input.Address)
|
||||
if inAcc == nil {
|
||||
log.Info(Fmt("Can't find in account %X", tx.Input.Address))
|
||||
return types.ErrTxInvalidAddress
|
||||
}
|
||||
|
||||
createContract := len(tx.Address) == 0
|
||||
if createContract {
|
||||
if !hasCreateContractPermission(blockCache, inAcc) {
|
||||
return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address)
|
||||
}
|
||||
} else {
|
||||
if !hasCallPermission(blockCache, inAcc) {
|
||||
return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address)
|
||||
}
|
||||
}
|
||||
|
||||
// pubKey should be present in either "inAcc" or "tx.Input"
|
||||
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
|
||||
log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
|
||||
return err
|
||||
}
|
||||
signBytes := acm.SignBytes(_s.ChainID, tx)
|
||||
err := validateInput(inAcc, signBytes, tx.Input)
|
||||
if err != nil {
|
||||
log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
|
||||
return err
|
||||
}
|
||||
if tx.Input.Amount < tx.Fee {
|
||||
log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
|
||||
return types.ErrTxInsufficientFunds
|
||||
}
|
||||
|
||||
if !createContract {
|
||||
// Validate output
|
||||
if len(tx.Address) != 20 {
|
||||
log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address))
|
||||
return types.ErrTxInvalidAddress
|
||||
}
|
||||
// check if its a native contract
|
||||
if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) {
|
||||
return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)")
|
||||
}
|
||||
|
||||
// Output account may be nil if we are still in mempool and contract was created in same block as this tx
|
||||
// but that's fine, because the account will be created properly when the create tx runs in the block
|
||||
// and then this won't return nil. otherwise, we take their fee
|
||||
outAcc = blockCache.GetAccount(tx.Address)
|
||||
}
|
||||
|
||||
log.Info(Fmt("Out account: %v", outAcc))
|
||||
|
||||
// Good!
|
||||
value := tx.Input.Amount - tx.Fee
|
||||
inAcc.Sequence += 1
|
||||
inAcc.Balance -= tx.Fee
|
||||
blockCache.UpdateAccount(inAcc)
|
||||
|
||||
// The logic in runCall MUST NOT return.
|
||||
if runCall {
|
||||
|
||||
// VM call variables
|
||||
var (
|
||||
gas int64 = tx.GasLimit
|
||||
err error = nil
|
||||
caller *vm.Account = toVMAccount(inAcc)
|
||||
callee *vm.Account = nil // initialized below
|
||||
code []byte = nil
|
||||
ret []byte = nil
|
||||
txCache = NewTxCache(blockCache)
|
||||
params = vm.Params{
|
||||
BlockHeight: int64(_s.LastBlockHeight),
|
||||
BlockHash: LeftPadWord256(_s.LastBlockHash),
|
||||
BlockTime: _s.LastBlockTime.Unix(),
|
||||
GasLimit: _s.GetGasLimit(),
|
||||
}
|
||||
)
|
||||
|
||||
if !createContract && (outAcc == nil || len(outAcc.Code) == 0) {
|
||||
// if you call an account that doesn't exist
|
||||
// or an account with no code then we take fees (sorry pal)
|
||||
// NOTE: it's fine to create a contract and call it within one
|
||||
// block (nonce will prevent re-ordering of those txs)
|
||||
// but to create with one contract and call with another
|
||||
// you have to wait a block to avoid a re-ordering attack
|
||||
// that will take your fees
|
||||
if outAcc == nil {
|
||||
log.Info(Fmt("%X tries to call %X but it does not exist.",
|
||||
inAcc.Address, tx.Address))
|
||||
} else {
|
||||
log.Info(Fmt("%X tries to call %X but code is blank.",
|
||||
inAcc.Address, tx.Address))
|
||||
}
|
||||
err = types.ErrTxInvalidAddress
|
||||
goto CALL_COMPLETE
|
||||
}
|
||||
|
||||
// get or create callee
|
||||
if createContract {
|
||||
// We already checked for permission
|
||||
callee = txCache.CreateAccount(caller)
|
||||
log.Info(Fmt("Created new contract %X", callee.Address))
|
||||
code = tx.Data
|
||||
} else {
|
||||
callee = toVMAccount(outAcc)
|
||||
log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
|
||||
code = callee.Code
|
||||
}
|
||||
log.Info(Fmt("Code for this contract: %X", code))
|
||||
|
||||
// Run VM call and sync txCache to blockCache.
|
||||
{ // Capture scope for goto.
|
||||
// Write caller/callee to txCache.
|
||||
txCache.UpdateAccount(caller)
|
||||
txCache.UpdateAccount(callee)
|
||||
vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(_s.ChainID, tx))
|
||||
vmach.SetFireable(evc)
|
||||
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
|
||||
ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas)
|
||||
if err != nil {
|
||||
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
|
||||
log.Info(Fmt("Error on execution: %v", err))
|
||||
goto CALL_COMPLETE
|
||||
}
|
||||
|
||||
log.Info("Successful execution")
|
||||
if createContract {
|
||||
callee.Code = ret
|
||||
}
|
||||
txCache.Sync()
|
||||
}
|
||||
|
||||
CALL_COMPLETE: // err may or may not be nil.
|
||||
|
||||
// Create a receipt from the ret and whether errored.
|
||||
log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
|
||||
|
||||
// Fire Events for sender and receiver
|
||||
// a separate event will be fired from vm for each additional call
|
||||
if evc != nil {
|
||||
exception := ""
|
||||
if err != nil {
|
||||
exception = err.Error()
|
||||
}
|
||||
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, ret, exception})
|
||||
evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventDataTx{tx, ret, exception})
|
||||
}
|
||||
} else {
|
||||
// The mempool does not call txs until
|
||||
// the proposer determines the order of txs.
|
||||
// So mempool will skip the actual .Call(),
|
||||
// and only deduct from the caller's balance.
|
||||
inAcc.Balance -= value
|
||||
if createContract {
|
||||
inAcc.Sequence += 1
|
||||
}
|
||||
blockCache.UpdateAccount(inAcc)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case *types.NameTx:
|
||||
var inAcc *acm.Account
|
||||
|
||||
// Validate input
|
||||
inAcc = blockCache.GetAccount(tx.Input.Address)
|
||||
if inAcc == nil {
|
||||
log.Info(Fmt("Can't find in account %X", tx.Input.Address))
|
||||
return types.ErrTxInvalidAddress
|
||||
}
|
||||
// check permission
|
||||
if !hasNamePermission(blockCache, inAcc) {
|
||||
return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address)
|
||||
}
|
||||
// pubKey should be present in either "inAcc" or "tx.Input"
|
||||
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
|
||||
log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
|
||||
return err
|
||||
}
|
||||
signBytes := acm.SignBytes(_s.ChainID, tx)
|
||||
err := validateInput(inAcc, signBytes, tx.Input)
|
||||
if err != nil {
|
||||
log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
|
||||
return err
|
||||
}
|
||||
// fee is in addition to the amount which is used to determine the TTL
|
||||
if tx.Input.Amount < tx.Fee {
|
||||
log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
|
||||
return types.ErrTxInsufficientFunds
|
||||
}
|
||||
|
||||
// validate the input strings
|
||||
if err := tx.ValidateStrings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := tx.Input.Amount - tx.Fee
|
||||
|
||||
// let's say cost of a name for one block is len(data) + 32
|
||||
costPerBlock := types.NameCostPerBlock(types.NameBaseCost(tx.Name, tx.Data))
|
||||
expiresIn := int(value / costPerBlock)
|
||||
lastBlockHeight := _s.LastBlockHeight
|
||||
|
||||
log.Info("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight)
|
||||
|
||||
// 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 > lastBlockHeight {
|
||||
// ensure we are owner
|
||||
if bytes.Compare(entry.Owner, tx.Input.Address) != 0 {
|
||||
log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name))
|
||||
return types.ErrTxPermissionDenied
|
||||
}
|
||||
} 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)
|
||||
log.Info("Removing namereg entry", "name", entry.Name)
|
||||
blockCache.RemoveNameRegEntry(entry.Name)
|
||||
} else {
|
||||
// update the entry by bumping the expiry
|
||||
// and changing the data
|
||||
if expired {
|
||||
if expiresIn < types.MinNameRegistrationPeriod {
|
||||
return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
|
||||
}
|
||||
entry.Expires = lastBlockHeight + expiresIn
|
||||
entry.Owner = tx.Input.Address
|
||||
log.Info("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner)
|
||||
} else {
|
||||
// since the size of the data may have changed
|
||||
// we use the total amount of "credit"
|
||||
oldCredit := int64(entry.Expires-lastBlockHeight) * types.NameBaseCost(entry.Name, entry.Data)
|
||||
credit := oldCredit + value
|
||||
expiresIn = int(credit / costPerBlock)
|
||||
if expiresIn < types.MinNameRegistrationPeriod {
|
||||
return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
|
||||
}
|
||||
entry.Expires = lastBlockHeight + expiresIn
|
||||
log.Info("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit)
|
||||
}
|
||||
entry.Data = tx.Data
|
||||
blockCache.UpdateNameRegEntry(entry)
|
||||
}
|
||||
} else {
|
||||
if expiresIn < types.MinNameRegistrationPeriod {
|
||||
return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
|
||||
}
|
||||
// entry does not exist, so create it
|
||||
entry = &types.NameRegEntry{
|
||||
Name: tx.Name,
|
||||
Owner: tx.Input.Address,
|
||||
Data: tx.Data,
|
||||
Expires: lastBlockHeight + expiresIn,
|
||||
}
|
||||
log.Info("Creating namereg entry", "name", entry.Name, "expiresIn", 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?
|
||||
|
||||
if evc != nil {
|
||||
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, nil, ""})
|
||||
evc.FireEvent(types.EventStringNameReg(tx.Name), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case *types.BondTx:
|
||||
valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address())
|
||||
if valInfo != nil {
|
||||
// TODO: In the future, check that the validator wasn't destroyed,
|
||||
// add funds, merge UnbondTo outputs, and unbond validator.
|
||||
return errors.New("Adding coins to existing validators not yet supported")
|
||||
}
|
||||
|
||||
accounts, err := getInputs(blockCache, tx.Inputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add outputs to accounts map
|
||||
// if any outputs don't exist, all inputs must have CreateAccount perm
|
||||
// though outputs aren't created until unbonding/release time
|
||||
canCreate := hasCreateAccountPermission(blockCache, accounts)
|
||||
for _, out := range tx.UnbondTo {
|
||||
acc := blockCache.GetAccount(out.Address)
|
||||
if acc == nil && !canCreate {
|
||||
return fmt.Errorf("At least one input does not have permission to create accounts")
|
||||
}
|
||||
}
|
||||
|
||||
bondAcc := blockCache.GetAccount(tx.PubKey.Address())
|
||||
if !hasBondPermission(blockCache, bondAcc) {
|
||||
return fmt.Errorf("The bonder does not have permission to bond")
|
||||
}
|
||||
|
||||
if !hasBondOrSendPermission(blockCache, accounts) {
|
||||
return fmt.Errorf("At least one input lacks permission to bond")
|
||||
}
|
||||
|
||||
signBytes := acm.SignBytes(_s.ChainID, tx)
|
||||
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) {
|
||||
return types.ErrTxInvalidSignature
|
||||
}
|
||||
outTotal, err := validateOutputs(tx.UnbondTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if outTotal > inTotal {
|
||||
return types.ErrTxInsufficientFunds
|
||||
}
|
||||
fee := inTotal - outTotal
|
||||
fees += fee
|
||||
|
||||
// Good! Adjust accounts
|
||||
adjustByInputs(accounts, tx.Inputs)
|
||||
for _, acc := range accounts {
|
||||
blockCache.UpdateAccount(acc)
|
||||
}
|
||||
// Add ValidatorInfo
|
||||
_s.SetValidatorInfo(&types.ValidatorInfo{
|
||||
Address: tx.PubKey.Address(),
|
||||
PubKey: tx.PubKey,
|
||||
UnbondTo: tx.UnbondTo,
|
||||
FirstBondHeight: _s.LastBlockHeight + 1,
|
||||
FirstBondAmount: outTotal,
|
||||
})
|
||||
// Add Validator
|
||||
added := _s.BondedValidators.Add(&types.Validator{
|
||||
Address: tx.PubKey.Address(),
|
||||
PubKey: tx.PubKey,
|
||||
BondHeight: _s.LastBlockHeight + 1,
|
||||
VotingPower: outTotal,
|
||||
Accum: 0,
|
||||
})
|
||||
if !added {
|
||||
PanicCrisis("Failed to add validator")
|
||||
}
|
||||
if evc != nil {
|
||||
// TODO: fire for all inputs
|
||||
evc.FireEvent(types.EventStringBond(), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
return nil
|
||||
|
||||
case *types.UnbondTx:
|
||||
// The validator must be active
|
||||
_, val := _s.BondedValidators.GetByAddress(tx.Address)
|
||||
if val == nil {
|
||||
return types.ErrTxInvalidAddress
|
||||
}
|
||||
|
||||
// Verify the signature
|
||||
signBytes := acm.SignBytes(_s.ChainID, tx)
|
||||
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
|
||||
return types.ErrTxInvalidSignature
|
||||
}
|
||||
|
||||
// tx.Height must be greater than val.LastCommitHeight
|
||||
if tx.Height <= val.LastCommitHeight {
|
||||
return errors.New("Invalid unbond height")
|
||||
}
|
||||
|
||||
// Good!
|
||||
_s.unbondValidator(val)
|
||||
if evc != nil {
|
||||
evc.FireEvent(types.EventStringUnbond(), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
return nil
|
||||
|
||||
case *types.RebondTx:
|
||||
// The validator must be inactive
|
||||
_, val := _s.UnbondingValidators.GetByAddress(tx.Address)
|
||||
if val == nil {
|
||||
return types.ErrTxInvalidAddress
|
||||
}
|
||||
|
||||
// Verify the signature
|
||||
signBytes := acm.SignBytes(_s.ChainID, tx)
|
||||
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
|
||||
return types.ErrTxInvalidSignature
|
||||
}
|
||||
|
||||
// tx.Height must be in a suitable range
|
||||
minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2)
|
||||
maxRebondHeight := _s.LastBlockHeight + 2
|
||||
if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) {
|
||||
return errors.New(Fmt("Rebond height not in range. Expected %v <= %v <= %v",
|
||||
minRebondHeight, tx.Height, maxRebondHeight))
|
||||
}
|
||||
|
||||
// Good!
|
||||
_s.rebondValidator(val)
|
||||
if evc != nil {
|
||||
evc.FireEvent(types.EventStringRebond(), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
return nil
|
||||
|
||||
case *types.DupeoutTx:
|
||||
// Verify the signatures
|
||||
_, accused := _s.BondedValidators.GetByAddress(tx.Address)
|
||||
if accused == nil {
|
||||
_, accused = _s.UnbondingValidators.GetByAddress(tx.Address)
|
||||
if accused == nil {
|
||||
return types.ErrTxInvalidAddress
|
||||
}
|
||||
}
|
||||
voteASignBytes := acm.SignBytes(_s.ChainID, &tx.VoteA)
|
||||
voteBSignBytes := acm.SignBytes(_s.ChainID, &tx.VoteB)
|
||||
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
|
||||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
|
||||
return types.ErrTxInvalidSignature
|
||||
}
|
||||
|
||||
// Verify equivocation
|
||||
// TODO: in the future, just require one vote from a previous height that
|
||||
// doesn't exist on this chain.
|
||||
if tx.VoteA.Height != tx.VoteB.Height {
|
||||
return errors.New("DupeoutTx heights don't match")
|
||||
}
|
||||
if tx.VoteA.Round != tx.VoteB.Round {
|
||||
return errors.New("DupeoutTx rounds don't match")
|
||||
}
|
||||
if tx.VoteA.Type != tx.VoteB.Type {
|
||||
return errors.New("DupeoutTx types don't match")
|
||||
}
|
||||
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
|
||||
return errors.New("DupeoutTx blockhashes shouldn't match")
|
||||
}
|
||||
|
||||
// Good! (Bad validator!)
|
||||
_s.destroyValidator(accused)
|
||||
if evc != nil {
|
||||
evc.FireEvent(types.EventStringDupeout(), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
return nil
|
||||
|
||||
case *types.PermissionsTx:
|
||||
var inAcc *acm.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
|
||||
}
|
||||
|
||||
permFlag := tx.PermArgs.PermFlag()
|
||||
// check permission
|
||||
if !HasPermission(blockCache, inAcc, permFlag) {
|
||||
return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag)
|
||||
}
|
||||
|
||||
// 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 := acm.SignBytes(_s.ChainID, tx)
|
||||
err := validateInput(inAcc, signBytes, tx.Input)
|
||||
if err != nil {
|
||||
log.Debug(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
|
||||
return err
|
||||
}
|
||||
|
||||
value := tx.Input.Amount
|
||||
|
||||
log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs)
|
||||
|
||||
var permAcc *acm.Account
|
||||
switch args := tx.PermArgs.(type) {
|
||||
case *ptypes.HasBaseArgs:
|
||||
// this one doesn't make sense from txs
|
||||
return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain")
|
||||
case *ptypes.SetBaseArgs:
|
||||
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
|
||||
return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
|
||||
}
|
||||
err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
|
||||
case *ptypes.UnsetBaseArgs:
|
||||
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
|
||||
return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
|
||||
}
|
||||
err = permAcc.Permissions.Base.Unset(args.Permission)
|
||||
case *ptypes.SetGlobalArgs:
|
||||
if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil {
|
||||
PanicSanity("can't find global permissions account")
|
||||
}
|
||||
err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
|
||||
case *ptypes.HasRoleArgs:
|
||||
return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain")
|
||||
case *ptypes.AddRoleArgs:
|
||||
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
|
||||
return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
|
||||
}
|
||||
if !permAcc.Permissions.AddRole(args.Role) {
|
||||
return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address)
|
||||
}
|
||||
case *ptypes.RmRoleArgs:
|
||||
if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
|
||||
return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
|
||||
}
|
||||
if !permAcc.Permissions.RmRole(args.Role) {
|
||||
return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address)
|
||||
}
|
||||
default:
|
||||
PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag)))
|
||||
}
|
||||
|
||||
// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Good!
|
||||
inAcc.Sequence += 1
|
||||
inAcc.Balance -= value
|
||||
blockCache.UpdateAccount(inAcc)
|
||||
if permAcc != nil {
|
||||
blockCache.UpdateAccount(permAcc)
|
||||
}
|
||||
|
||||
if evc != nil {
|
||||
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, nil, ""})
|
||||
evc.FireEvent(types.EventStringPermissions(ptypes.PermFlagToString(permFlag)), types.EventDataTx{tx, nil, ""})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
// binary decoding should not let this happen
|
||||
PanicSanity("Unknown Tx type")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
|
||||
// Get permission on an account or fall back to global value
|
||||
func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) bool {
|
||||
if perm > ptypes.AllPermFlags {
|
||||
PanicSanity("Checking an unknown permission in state should never happen")
|
||||
}
|
||||
|
||||
if acc == nil {
|
||||
// TODO
|
||||
// this needs to fall back to global or do some other specific things
|
||||
// eg. a bondAcc may be nil and so can only bond if global bonding is true
|
||||
}
|
||||
permString := ptypes.PermFlagToString(perm)
|
||||
|
||||
v, err := acc.Permissions.Base.Get(perm)
|
||||
if _, ok := err.(ptypes.ErrValueNotSet); ok {
|
||||
if state == nil {
|
||||
PanicSanity("All known global permissions should be set!")
|
||||
}
|
||||
log.Info("Permission for account is not set. Querying GlobalPermissionsAddress", "perm", permString)
|
||||
return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm)
|
||||
} else if v {
|
||||
log.Info("Account has permission", "address", Fmt("%X", acc.Address), "perm", permString)
|
||||
} else {
|
||||
log.Info("Account does not have permission", "address", Fmt("%X", acc.Address), "perm", permString)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TODO: for debug log the failed accounts
|
||||
func hasSendPermission(state AccountGetter, accs map[string]*acm.Account) bool {
|
||||
for _, acc := range accs {
|
||||
if !HasPermission(state, acc, ptypes.Send) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func hasNamePermission(state AccountGetter, acc *acm.Account) bool {
|
||||
return HasPermission(state, acc, ptypes.Name)
|
||||
}
|
||||
|
||||
func hasCallPermission(state AccountGetter, acc *acm.Account) bool {
|
||||
return HasPermission(state, acc, ptypes.Call)
|
||||
}
|
||||
|
||||
func hasCreateContractPermission(state AccountGetter, acc *acm.Account) bool {
|
||||
return HasPermission(state, acc, ptypes.CreateContract)
|
||||
}
|
||||
|
||||
func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account) bool {
|
||||
for _, acc := range accs {
|
||||
if !HasPermission(state, acc, ptypes.CreateAccount) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func hasBondPermission(state AccountGetter, acc *acm.Account) bool {
|
||||
return HasPermission(state, acc, ptypes.Bond)
|
||||
}
|
||||
|
||||
func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account) bool {
|
||||
for _, acc := range accs {
|
||||
if !HasPermission(state, acc, ptypes.Bond) {
|
||||
if !HasPermission(state, acc, ptypes.Send) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -1,87 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
tdb "github.com/tendermint/go-db"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
. "github.com/tendermint/tendermint/state/types"
|
||||
)
|
||||
|
||||
var chain_id = "lone_ranger"
|
||||
var addr1, _ = hex.DecodeString("964B1493BBE3312278B7DEB94C39149F7899A345")
|
||||
var send1, name1, call1 = 1, 1, 0
|
||||
var perms, setbit = 66, 70
|
||||
var accName = "me"
|
||||
var roles1 = []string{"master", "universal-ruler"}
|
||||
var amt1 int64 = 1000000
|
||||
var g1 = fmt.Sprintf(`
|
||||
{
|
||||
"chain_id":"%s",
|
||||
"accounts": [
|
||||
{
|
||||
"address": "%X",
|
||||
"amount": %d,
|
||||
"name": "%s",
|
||||
"permissions": {
|
||||
"base": {
|
||||
"perms": %d,
|
||||
"set": %d
|
||||
},
|
||||
"roles": [
|
||||
"%s",
|
||||
"%s"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
{
|
||||
"amount": 100000000,
|
||||
"pub_key": [1,"F6C79CF0CB9D66B677988BCB9B8EADD9A091CD465A60542A8AB85476256DBA92"],
|
||||
"unbond_to": [
|
||||
{
|
||||
"address": "964B1493BBE3312278B7DEB94C39149F7899A345",
|
||||
"amount": 10000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`, chain_id, addr1, amt1, accName, perms, setbit, roles1[0], roles1[1])
|
||||
|
||||
func TestGenesisReadable(t *testing.T) {
|
||||
genDoc := GenesisDocFromJSON([]byte(g1))
|
||||
if genDoc.ChainID != chain_id {
|
||||
t.Fatalf("Incorrect chain id. Got %d, expected %d\n", genDoc.ChainID, chain_id)
|
||||
}
|
||||
acc := genDoc.Accounts[0]
|
||||
if bytes.Compare(acc.Address, addr1) != 0 {
|
||||
t.Fatalf("Incorrect address for account. Got %X, expected %X\n", acc.Address, addr1)
|
||||
}
|
||||
if acc.Amount != amt1 {
|
||||
t.Fatalf("Incorrect amount for account. Got %d, expected %d\n", acc.Amount, amt1)
|
||||
}
|
||||
if acc.Name != accName {
|
||||
t.Fatalf("Incorrect name for account. Got %s, expected %s\n", acc.Name, accName)
|
||||
}
|
||||
|
||||
perm, _ := acc.Permissions.Base.Get(ptypes.Send)
|
||||
if perm != (send1 > 0) {
|
||||
t.Fatalf("Incorrect permission for send. Got %v, expected %v\n", perm, send1 > 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesisMakeState(t *testing.T) {
|
||||
genDoc := GenesisDocFromJSON([]byte(g1))
|
||||
db := tdb.NewMemDB()
|
||||
st := MakeGenesisState(db, genDoc)
|
||||
acc := st.GetAccount(addr1)
|
||||
v, _ := acc.Permissions.Base.Get(ptypes.Send)
|
||||
if v != (send1 > 0) {
|
||||
t.Fatalf("Incorrect permission for send. Got %v, expected %v\n", v, send1 > 0)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
391
state/state.go
391
state/state.go
@ -1,47 +1,34 @@
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
dbm "github.com/tendermint/go-db"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/go-merkle"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
. "github.com/tendermint/tendermint/state/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var (
|
||||
stateKey = []byte("stateKey")
|
||||
minBondAmount = int64(1) // TODO adjust
|
||||
defaultAccountsCacheCapacity = 1000 // TODO adjust
|
||||
unbondingPeriodBlocks = int(60 * 24 * 365) // TODO probably better to make it time based.
|
||||
validatorTimeoutBlocks = int(10) // TODO adjust
|
||||
stateKey = []byte("stateKey")
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// NOTE: not goroutine-safe.
|
||||
type State struct {
|
||||
DB dbm.DB
|
||||
ChainID string
|
||||
LastBlockHeight int
|
||||
LastBlockHash []byte
|
||||
LastBlockParts types.PartSetHeader
|
||||
LastBlockTime time.Time
|
||||
BondedValidators *types.ValidatorSet
|
||||
LastBondedValidators *types.ValidatorSet
|
||||
UnbondingValidators *types.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.
|
||||
DB dbm.DB
|
||||
ChainID string
|
||||
LastBlockHeight int
|
||||
LastBlockHash []byte
|
||||
LastBlockParts types.PartSetHeader
|
||||
LastBlockTime time.Time
|
||||
Validators *types.ValidatorSet
|
||||
LastValidators *types.ValidatorSet
|
||||
|
||||
evc events.Fireable // typically an events.EventCache
|
||||
}
|
||||
@ -58,18 +45,8 @@ func LoadState(db dbm.DB) *State {
|
||||
s.LastBlockHash = wire.ReadByteSlice(r, n, err)
|
||||
s.LastBlockParts = wire.ReadBinary(types.PartSetHeader{}, r, n, err).(types.PartSetHeader)
|
||||
s.LastBlockTime = wire.ReadTime(r, n, err)
|
||||
s.BondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, n, err).(*types.ValidatorSet)
|
||||
s.LastBondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, n, err).(*types.ValidatorSet)
|
||||
s.UnbondingValidators = wire.ReadBinary(&types.ValidatorSet{}, r, n, err).(*types.ValidatorSet)
|
||||
accountsHash := wire.ReadByteSlice(r, n, err)
|
||||
s.accounts = merkle.NewIAVLTree(wire.BasicCodec, acm.AccountCodec, defaultAccountsCacheCapacity, db)
|
||||
s.accounts.Load(accountsHash)
|
||||
validatorInfosHash := wire.ReadByteSlice(r, n, err)
|
||||
s.validatorInfos = merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db)
|
||||
s.validatorInfos.Load(validatorInfosHash)
|
||||
nameRegHash := wire.ReadByteSlice(r, n, err)
|
||||
s.nameReg = merkle.NewIAVLTree(wire.BasicCodec, NameRegCodec, 0, db)
|
||||
s.nameReg.Load(nameRegHash)
|
||||
s.Validators = wire.ReadBinary(&types.ValidatorSet{}, r, n, err).(*types.ValidatorSet)
|
||||
s.LastValidators = wire.ReadBinary(&types.ValidatorSet{}, r, n, err).(*types.ValidatorSet)
|
||||
if *err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
Exit(Fmt("Data has been corrupted or its spec has changed: %v\n", *err))
|
||||
@ -80,21 +57,14 @@ 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)
|
||||
wire.WriteString(s.ChainID, buf, n, err)
|
||||
wire.WriteVarint(s.LastBlockHeight, buf, n, err)
|
||||
wire.WriteByteSlice(s.LastBlockHash, buf, n, err)
|
||||
wire.WriteBinary(s.LastBlockParts, buf, n, err)
|
||||
wire.WriteTime(s.LastBlockTime, buf, n, err)
|
||||
wire.WriteBinary(s.BondedValidators, buf, n, err)
|
||||
wire.WriteBinary(s.LastBondedValidators, buf, n, err)
|
||||
wire.WriteBinary(s.UnbondingValidators, buf, n, err)
|
||||
wire.WriteByteSlice(s.accounts.Hash(), buf, n, err)
|
||||
wire.WriteByteSlice(s.validatorInfos.Hash(), buf, n, err)
|
||||
wire.WriteByteSlice(s.nameReg.Hash(), buf, n, err)
|
||||
wire.WriteBinary(s.Validators, buf, n, err)
|
||||
wire.WriteBinary(s.LastValidators, buf, n, err)
|
||||
if *err != nil {
|
||||
PanicCrisis(*err)
|
||||
}
|
||||
@ -106,30 +76,22 @@ func (s *State) Save() {
|
||||
// as if State were copied by value.
|
||||
func (s *State) Copy() *State {
|
||||
return &State{
|
||||
DB: s.DB,
|
||||
ChainID: s.ChainID,
|
||||
LastBlockHeight: s.LastBlockHeight,
|
||||
LastBlockHash: s.LastBlockHash,
|
||||
LastBlockParts: s.LastBlockParts,
|
||||
LastBlockTime: s.LastBlockTime,
|
||||
BondedValidators: s.BondedValidators.Copy(), // TODO remove need for Copy() here.
|
||||
LastBondedValidators: s.LastBondedValidators.Copy(), // That is, make updates to the validator set
|
||||
UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily.
|
||||
accounts: s.accounts.Copy(),
|
||||
validatorInfos: s.validatorInfos.Copy(),
|
||||
nameReg: s.nameReg.Copy(),
|
||||
evc: nil,
|
||||
DB: s.DB,
|
||||
ChainID: s.ChainID,
|
||||
LastBlockHeight: s.LastBlockHeight,
|
||||
LastBlockHash: s.LastBlockHash,
|
||||
LastBlockParts: s.LastBlockParts,
|
||||
LastBlockTime: s.LastBlockTime,
|
||||
Validators: s.Validators.Copy(), // TODO remove need for Copy() here.
|
||||
LastValidators: s.LastValidators.Copy(), // That is, make updates to the validator set
|
||||
evc: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a hash that represents the state data, excluding Last*
|
||||
func (s *State) Hash() []byte {
|
||||
return merkle.SimpleHashFromMap(map[string]interface{}{
|
||||
"BondedValidators": s.BondedValidators,
|
||||
"UnbondingValidators": s.UnbondingValidators,
|
||||
"Accounts": s.accounts,
|
||||
"ValidatorInfos": s.validatorInfos,
|
||||
"NameRegistry": s.nameReg,
|
||||
"Validators": s.Validators,
|
||||
})
|
||||
}
|
||||
|
||||
@ -150,213 +112,6 @@ func (s *State) SetDB(db dbm.DB) {
|
||||
s.DB = db
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// State.params
|
||||
|
||||
func (s *State) GetGasLimit() int64 {
|
||||
return 1000000 // TODO
|
||||
}
|
||||
|
||||
// State.params
|
||||
//-------------------------------------
|
||||
// State.accounts
|
||||
|
||||
// Returns nil if account does not exist with given address.
|
||||
// The returned Account is a copy, so mutating it
|
||||
// has no side effects.
|
||||
// Implements Statelike
|
||||
func (s *State) GetAccount(address []byte) *acm.Account {
|
||||
_, acc := s.accounts.Get(address)
|
||||
if acc == nil {
|
||||
return nil
|
||||
}
|
||||
return acc.(*acm.Account).Copy()
|
||||
}
|
||||
|
||||
// The account is copied before setting, so mutating it
|
||||
// afterwards has no side effects.
|
||||
// Implements Statelike
|
||||
func (s *State) UpdateAccount(account *acm.Account) bool {
|
||||
return s.accounts.Set(account.Address, account.Copy())
|
||||
}
|
||||
|
||||
// Implements Statelike
|
||||
func (s *State) RemoveAccount(address []byte) bool {
|
||||
_, removed := s.accounts.Remove(address)
|
||||
return removed
|
||||
}
|
||||
|
||||
// The returned Account is a copy, so mutating it
|
||||
// has no side effects.
|
||||
func (s *State) GetAccounts() merkle.Tree {
|
||||
return s.accounts.Copy()
|
||||
}
|
||||
|
||||
// Set the accounts tree
|
||||
func (s *State) SetAccounts(accounts merkle.Tree) {
|
||||
s.accounts = accounts
|
||||
}
|
||||
|
||||
// State.accounts
|
||||
//-------------------------------------
|
||||
// State.validators
|
||||
|
||||
// The returned ValidatorInfo is a copy, so mutating it
|
||||
// has no side effects.
|
||||
func (s *State) GetValidatorInfo(address []byte) *types.ValidatorInfo {
|
||||
_, valInfo := s.validatorInfos.Get(address)
|
||||
if valInfo == nil {
|
||||
return nil
|
||||
}
|
||||
return valInfo.(*types.ValidatorInfo).Copy()
|
||||
}
|
||||
|
||||
// Returns false if new, true if updated.
|
||||
// The valInfo is copied before setting, so mutating it
|
||||
// afterwards has no side effects.
|
||||
func (s *State) SetValidatorInfo(valInfo *types.ValidatorInfo) (updated bool) {
|
||||
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
|
||||
}
|
||||
|
||||
func (s *State) GetValidatorInfos() merkle.Tree {
|
||||
return s.validatorInfos.Copy()
|
||||
}
|
||||
|
||||
func (s *State) unbondValidator(val *types.Validator) {
|
||||
// Move validator to UnbondingValidators
|
||||
val, removed := s.BondedValidators.Remove(val.Address)
|
||||
if !removed {
|
||||
PanicCrisis("Couldn't remove validator for unbonding")
|
||||
}
|
||||
val.UnbondHeight = s.LastBlockHeight + 1
|
||||
added := s.UnbondingValidators.Add(val)
|
||||
if !added {
|
||||
PanicCrisis("Couldn't add validator for unbonding")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) rebondValidator(val *types.Validator) {
|
||||
// Move validator to BondingValidators
|
||||
val, removed := s.UnbondingValidators.Remove(val.Address)
|
||||
if !removed {
|
||||
PanicCrisis("Couldn't remove validator for rebonding")
|
||||
}
|
||||
val.BondHeight = s.LastBlockHeight + 1
|
||||
added := s.BondedValidators.Add(val)
|
||||
if !added {
|
||||
PanicCrisis("Couldn't add validator for rebonding")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) releaseValidator(val *types.Validator) {
|
||||
// Update validatorInfo
|
||||
valInfo := s.GetValidatorInfo(val.Address)
|
||||
if valInfo == nil {
|
||||
PanicSanity("Couldn't find validatorInfo for release")
|
||||
}
|
||||
valInfo.ReleasedHeight = s.LastBlockHeight + 1
|
||||
s.SetValidatorInfo(valInfo)
|
||||
|
||||
// Send coins back to UnbondTo outputs
|
||||
accounts, err := getOrMakeOutputs(s, nil, valInfo.UnbondTo)
|
||||
if err != nil {
|
||||
PanicSanity("Couldn't get or make unbondTo accounts")
|
||||
}
|
||||
adjustByOutputs(accounts, valInfo.UnbondTo)
|
||||
for _, acc := range accounts {
|
||||
s.UpdateAccount(acc)
|
||||
}
|
||||
|
||||
// Remove validator from UnbondingValidators
|
||||
_, removed := s.UnbondingValidators.Remove(val.Address)
|
||||
if !removed {
|
||||
PanicCrisis("Couldn't remove validator for release")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) destroyValidator(val *types.Validator) {
|
||||
// Update validatorInfo
|
||||
valInfo := s.GetValidatorInfo(val.Address)
|
||||
if valInfo == nil {
|
||||
PanicSanity("Couldn't find validatorInfo for release")
|
||||
}
|
||||
valInfo.DestroyedHeight = s.LastBlockHeight + 1
|
||||
valInfo.DestroyedAmount = val.VotingPower
|
||||
s.SetValidatorInfo(valInfo)
|
||||
|
||||
// Remove validator
|
||||
_, removed := s.BondedValidators.Remove(val.Address)
|
||||
if !removed {
|
||||
_, removed := s.UnbondingValidators.Remove(val.Address)
|
||||
if !removed {
|
||||
PanicCrisis("Couldn't remove validator for destruction")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set the validator infos tree
|
||||
func (s *State) SetValidatorInfos(validatorInfos merkle.Tree) {
|
||||
s.validatorInfos = validatorInfos
|
||||
}
|
||||
|
||||
// State.validators
|
||||
//-------------------------------------
|
||||
// State.storage
|
||||
|
||||
func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) {
|
||||
storage = merkle.NewIAVLTree(wire.BasicCodec, wire.BasicCodec, 1024, s.DB)
|
||||
storage.Load(hash)
|
||||
return storage
|
||||
}
|
||||
|
||||
// State.storage
|
||||
//-------------------------------------
|
||||
// State.nameReg
|
||||
|
||||
func (s *State) GetNameRegEntry(name string) *types.NameRegEntry {
|
||||
_, value := s.nameReg.Get(name)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
entry := value.(*types.NameRegEntry)
|
||||
return entry.Copy()
|
||||
}
|
||||
|
||||
func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool {
|
||||
return s.nameReg.Set(entry.Name, entry)
|
||||
}
|
||||
|
||||
func (s *State) RemoveNameRegEntry(name string) bool {
|
||||
_, removed := s.nameReg.Remove(name)
|
||||
return removed
|
||||
}
|
||||
|
||||
func (s *State) GetNames() merkle.Tree {
|
||||
return s.nameReg.Copy()
|
||||
}
|
||||
|
||||
// Set the name reg tree
|
||||
func (s *State) SetNameReg(nameReg merkle.Tree) {
|
||||
s.nameReg = nameReg
|
||||
}
|
||||
|
||||
func NameRegEncoder(o interface{}, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteBinary(o.(*types.NameRegEntry), w, n, err)
|
||||
}
|
||||
|
||||
func NameRegDecoder(r io.Reader, n *int64, err *error) interface{} {
|
||||
return wire.ReadBinary(&types.NameRegEntry{}, r, n, err)
|
||||
}
|
||||
|
||||
var NameRegCodec = wire.Codec{
|
||||
Encode: NameRegEncoder,
|
||||
Decode: NameRegDecoder,
|
||||
}
|
||||
|
||||
// State.nameReg
|
||||
//-------------------------------------
|
||||
|
||||
// Implements events.Eventable. Typically uses events.EventCache
|
||||
func (s *State) SetFireable(evc events.Fireable) {
|
||||
s.evc = evc
|
||||
@ -365,16 +120,16 @@ func (s *State) SetFireable(evc events.Fireable) {
|
||||
//-----------------------------------------------------------------------------
|
||||
// Genesis
|
||||
|
||||
func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*GenesisDoc, *State) {
|
||||
func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*types.GenesisDoc, *State) {
|
||||
jsonBlob, err := ioutil.ReadFile(genDocFile)
|
||||
if err != nil {
|
||||
Exit(Fmt("Couldn't read GenesisDoc file: %v", err))
|
||||
}
|
||||
genDoc := GenesisDocFromJSON(jsonBlob)
|
||||
genDoc := types.GenesisDocFromJSON(jsonBlob)
|
||||
return genDoc, MakeGenesisState(db, genDoc)
|
||||
}
|
||||
|
||||
func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State {
|
||||
func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State {
|
||||
if len(genDoc.Validators) == 0 {
|
||||
Exit(Fmt("The genesis file has no validators"))
|
||||
}
|
||||
@ -383,65 +138,14 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State {
|
||||
genDoc.GenesisTime = time.Now()
|
||||
}
|
||||
|
||||
// Make accounts state tree
|
||||
accounts := merkle.NewIAVLTree(wire.BasicCodec, acm.AccountCodec, defaultAccountsCacheCapacity, db)
|
||||
for _, genAcc := range genDoc.Accounts {
|
||||
perm := ptypes.ZeroAccountPermissions
|
||||
if genAcc.Permissions != nil {
|
||||
perm = *genAcc.Permissions
|
||||
}
|
||||
acc := &acm.Account{
|
||||
Address: genAcc.Address,
|
||||
PubKey: nil,
|
||||
Sequence: 0,
|
||||
Balance: genAcc.Amount,
|
||||
Permissions: perm,
|
||||
}
|
||||
accounts.Set(acc.Address, acc)
|
||||
}
|
||||
// XXX Speak to application, ensure genesis state.
|
||||
|
||||
// global permissions are saved as the 0 address
|
||||
// so they are included in the accounts tree
|
||||
globalPerms := ptypes.DefaultAccountPermissions
|
||||
if genDoc.Params != nil && genDoc.Params.GlobalPermissions != nil {
|
||||
globalPerms = *genDoc.Params.GlobalPermissions
|
||||
// XXX: make sure the set bits are all true
|
||||
// Without it the HasPermission() functions will fail
|
||||
globalPerms.Base.SetBit = ptypes.AllPermFlags
|
||||
}
|
||||
|
||||
permsAcc := &acm.Account{
|
||||
Address: ptypes.GlobalPermissionsAddress,
|
||||
PubKey: nil,
|
||||
Sequence: 0,
|
||||
Balance: 1337,
|
||||
Permissions: globalPerms,
|
||||
}
|
||||
accounts.Set(permsAcc.Address, permsAcc)
|
||||
|
||||
// Make validatorInfos state tree && validators slice
|
||||
validatorInfos := merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db)
|
||||
// Make validators slice
|
||||
validators := make([]*types.Validator, len(genDoc.Validators))
|
||||
for i, val := range genDoc.Validators {
|
||||
pubKey := val.PubKey
|
||||
address := pubKey.Address()
|
||||
|
||||
// Make ValidatorInfo
|
||||
valInfo := &types.ValidatorInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
UnbondTo: make([]*types.TxOutput, len(val.UnbondTo)),
|
||||
FirstBondHeight: 0,
|
||||
FirstBondAmount: val.Amount,
|
||||
}
|
||||
for i, unbondTo := range val.UnbondTo {
|
||||
valInfo.UnbondTo[i] = &types.TxOutput{
|
||||
Address: unbondTo.Address,
|
||||
Amount: unbondTo.Amount,
|
||||
}
|
||||
}
|
||||
validatorInfos.Set(address, valInfo)
|
||||
|
||||
// Make validator
|
||||
validators[i] = &types.Validator{
|
||||
Address: address,
|
||||
@ -450,35 +154,22 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State {
|
||||
}
|
||||
}
|
||||
|
||||
// Make namereg tree
|
||||
nameReg := merkle.NewIAVLTree(wire.BasicCodec, NameRegCodec, 0, db)
|
||||
// TODO: add names, contracts to genesis.json
|
||||
|
||||
// IAVLTrees must be persisted before copy operations.
|
||||
accounts.Save()
|
||||
validatorInfos.Save()
|
||||
nameReg.Save()
|
||||
|
||||
return &State{
|
||||
DB: db,
|
||||
ChainID: genDoc.ChainID,
|
||||
LastBlockHeight: 0,
|
||||
LastBlockHash: nil,
|
||||
LastBlockParts: types.PartSetHeader{},
|
||||
LastBlockTime: genDoc.GenesisTime,
|
||||
BondedValidators: types.NewValidatorSet(validators),
|
||||
LastBondedValidators: types.NewValidatorSet(nil),
|
||||
UnbondingValidators: types.NewValidatorSet(nil),
|
||||
accounts: accounts,
|
||||
validatorInfos: validatorInfos,
|
||||
nameReg: nameReg,
|
||||
DB: db,
|
||||
ChainID: genDoc.ChainID,
|
||||
LastBlockHeight: 0,
|
||||
LastBlockHash: nil,
|
||||
LastBlockParts: types.PartSetHeader{},
|
||||
LastBlockTime: genDoc.GenesisTime,
|
||||
Validators: types.NewValidatorSet(validators),
|
||||
LastValidators: types.NewValidatorSet(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*State, []*acm.PrivAccount, []*types.PrivValidator) {
|
||||
func RandGenesisState(numValidators int, randPower bool, minPower int64) (*State, []*types.PrivValidator) {
|
||||
db := dbm.NewMemDB()
|
||||
genDoc, privAccounts, privValidators := RandGenesisDoc(numAccounts, randBalance, minBalance, numValidators, randBonded, minBonded)
|
||||
genDoc, privValidators := types.RandGenesisDoc(numValidators, randPower, minPower)
|
||||
s0 := MakeGenesisState(db, genDoc)
|
||||
s0.Save()
|
||||
return s0, privAccounts, privValidators
|
||||
return s0, privValidators
|
||||
}
|
||||
|
@ -1,699 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/account"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func execTxWithState(state *State, tx types.Tx, runCall bool) error {
|
||||
cache := NewBlockCache(state)
|
||||
if err := ExecTx(cache, tx, runCall, nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cache.Sync()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func execTxWithStateNewBlock(state *State, tx types.Tx, runCall bool) error {
|
||||
if err := execTxWithState(state, tx, runCall); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state.LastBlockHeight += 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCopyState(t *testing.T) {
|
||||
// Generate a random state
|
||||
s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000)
|
||||
s0Hash := s0.Hash()
|
||||
if len(s0Hash) == 0 {
|
||||
t.Error("Expected state hash")
|
||||
}
|
||||
|
||||
// Check hash of copy
|
||||
s0Copy := s0.Copy()
|
||||
if !bytes.Equal(s0Hash, s0Copy.Hash()) {
|
||||
t.Error("Expected state copy hash to be the same")
|
||||
}
|
||||
|
||||
// Mutate the original; hash should change.
|
||||
acc0Address := privAccounts[0].PubKey.Address()
|
||||
acc := s0.GetAccount(acc0Address)
|
||||
acc.Balance += 1
|
||||
|
||||
// The account balance shouldn't have changed yet.
|
||||
if s0.GetAccount(acc0Address).Balance == acc.Balance {
|
||||
t.Error("Account balance changed unexpectedly")
|
||||
}
|
||||
|
||||
// Setting, however, should change the balance.
|
||||
s0.UpdateAccount(acc)
|
||||
if s0.GetAccount(acc0Address).Balance != acc.Balance {
|
||||
t.Error("Account balance wasn't set")
|
||||
}
|
||||
|
||||
// Now that the state changed, the hash should change too.
|
||||
if bytes.Equal(s0Hash, s0.Hash()) {
|
||||
t.Error("Expected state hash to have changed")
|
||||
}
|
||||
|
||||
// The s0Copy shouldn't have changed though.
|
||||
if !bytes.Equal(s0Hash, s0Copy.Hash()) {
|
||||
t.Error("Expected state copy hash to have not changed")
|
||||
}
|
||||
}
|
||||
|
||||
func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []types.Tx) *types.Block {
|
||||
if validation == nil {
|
||||
validation = &types.Validation{}
|
||||
}
|
||||
block := &types.Block{
|
||||
Header: &types.Header{
|
||||
ChainID: state.ChainID,
|
||||
Height: state.LastBlockHeight + 1,
|
||||
Time: state.LastBlockTime.Add(time.Minute),
|
||||
Fees: 0,
|
||||
NumTxs: len(txs),
|
||||
LastBlockHash: state.LastBlockHash,
|
||||
LastBlockParts: state.LastBlockParts,
|
||||
StateHash: nil,
|
||||
},
|
||||
LastValidation: validation,
|
||||
Data: &types.Data{
|
||||
Txs: txs,
|
||||
},
|
||||
}
|
||||
block.FillHeader()
|
||||
|
||||
// Fill in block StateHash
|
||||
err := state.ComputeBlockStateHash(block)
|
||||
if err != nil {
|
||||
t.Error("Error appending initial block:", err)
|
||||
}
|
||||
if len(block.Header.StateHash) == 0 {
|
||||
t.Error("Expected StateHash but got nothing.")
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func TestGenesisSaveLoad(t *testing.T) {
|
||||
|
||||
// Generate a state, save & load it.
|
||||
s0, _, _ := RandGenesisState(10, true, 1000, 5, true, 1000)
|
||||
|
||||
// Make complete block and blockParts
|
||||
block := makeBlock(t, s0, nil, nil)
|
||||
blockParts := block.MakePartSet()
|
||||
|
||||
// Now append the block to s0.
|
||||
err := ExecBlock(s0, block, blockParts.Header())
|
||||
if err != nil {
|
||||
t.Error("Error appending initial block:", err)
|
||||
}
|
||||
|
||||
// Save s0
|
||||
s0.Save()
|
||||
|
||||
// Sanity check s0
|
||||
//s0.DB.(*dbm.MemDB).Print()
|
||||
if s0.BondedValidators.TotalVotingPower() == 0 {
|
||||
t.Error("s0 BondedValidators TotalVotingPower should not be 0")
|
||||
}
|
||||
if s0.LastBlockHeight != 1 {
|
||||
t.Error("s0 LastBlockHeight should be 1, got", s0.LastBlockHeight)
|
||||
}
|
||||
|
||||
// Load s1
|
||||
s1 := LoadState(s0.DB)
|
||||
|
||||
// Compare height & blockHash
|
||||
if s0.LastBlockHeight != s1.LastBlockHeight {
|
||||
t.Error("LastBlockHeight mismatch")
|
||||
}
|
||||
if !bytes.Equal(s0.LastBlockHash, s1.LastBlockHash) {
|
||||
t.Error("LastBlockHash mismatch")
|
||||
}
|
||||
|
||||
// Compare state merkle trees
|
||||
if s0.BondedValidators.Size() != s1.BondedValidators.Size() {
|
||||
t.Error("BondedValidators Size mismatch")
|
||||
}
|
||||
if s0.BondedValidators.TotalVotingPower() != s1.BondedValidators.TotalVotingPower() {
|
||||
t.Error("BondedValidators TotalVotingPower mismatch")
|
||||
}
|
||||
if !bytes.Equal(s0.BondedValidators.Hash(), s1.BondedValidators.Hash()) {
|
||||
t.Error("BondedValidators hash mismatch")
|
||||
}
|
||||
if s0.UnbondingValidators.Size() != s1.UnbondingValidators.Size() {
|
||||
t.Error("UnbondingValidators Size mismatch")
|
||||
}
|
||||
if s0.UnbondingValidators.TotalVotingPower() != s1.UnbondingValidators.TotalVotingPower() {
|
||||
t.Error("UnbondingValidators TotalVotingPower mismatch")
|
||||
}
|
||||
if !bytes.Equal(s0.UnbondingValidators.Hash(), s1.UnbondingValidators.Hash()) {
|
||||
t.Error("UnbondingValidators hash mismatch")
|
||||
}
|
||||
if !bytes.Equal(s0.accounts.Hash(), s1.accounts.Hash()) {
|
||||
t.Error("Accounts mismatch")
|
||||
}
|
||||
if !bytes.Equal(s0.validatorInfos.Hash(), s1.validatorInfos.Hash()) {
|
||||
t.Error("Accounts mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxSequence(t *testing.T) {
|
||||
|
||||
state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000)
|
||||
acc0 := state.GetAccount(privAccounts[0].PubKey.Address())
|
||||
acc0PubKey := privAccounts[0].PubKey
|
||||
acc1 := state.GetAccount(privAccounts[1].PubKey.Address())
|
||||
|
||||
// Test a variety of sequence numbers for the tx.
|
||||
// The tx should only pass when i == 1.
|
||||
for i := -1; i < 3; i++ {
|
||||
sequence := acc0.Sequence + i
|
||||
tx := types.NewSendTx()
|
||||
tx.AddInputWithNonce(acc0PubKey, 1, sequence)
|
||||
tx.AddOutput(acc1.Address, 1)
|
||||
tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx)
|
||||
stateCopy := state.Copy()
|
||||
err := execTxWithState(stateCopy, tx, true)
|
||||
if i == 1 {
|
||||
// Sequence is good.
|
||||
if err != nil {
|
||||
t.Errorf("Expected good sequence to pass: %v", err)
|
||||
}
|
||||
// Check acc.Sequence.
|
||||
newAcc0 := stateCopy.GetAccount(acc0.Address)
|
||||
if newAcc0.Sequence != sequence {
|
||||
t.Errorf("Expected account sequence to change to %v, got %v",
|
||||
sequence, newAcc0.Sequence)
|
||||
}
|
||||
} else {
|
||||
// Sequence is bad.
|
||||
if err == nil {
|
||||
t.Errorf("Expected bad sequence to fail")
|
||||
}
|
||||
// Check acc.Sequence. (shouldn't have changed)
|
||||
newAcc0 := stateCopy.GetAccount(acc0.Address)
|
||||
if newAcc0.Sequence != acc0.Sequence {
|
||||
t.Errorf("Expected account sequence to not change from %v, got %v",
|
||||
acc0.Sequence, newAcc0.Sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameTxs(t *testing.T) {
|
||||
state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000)
|
||||
|
||||
types.MinNameRegistrationPeriod = 5
|
||||
startingBlock := state.LastBlockHeight
|
||||
|
||||
// try some bad names. these should all fail
|
||||
names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyessurprised", "no spaces please"}
|
||||
data := "something about all this just doesn't feel right."
|
||||
fee := int64(1000)
|
||||
numDesiredBlocks := 5
|
||||
for _, name := range names {
|
||||
amt := fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
|
||||
tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[0])
|
||||
|
||||
if err := execTxWithState(state, tx, true); err == nil {
|
||||
t.Fatalf("Expected invalid name error from %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
// try some bad data. these should all fail
|
||||
name := "hold_it_chum"
|
||||
datas := []string{"cold&warm", "!@#$%^&*()", "<<<>>>>", "because why would you ever need a ~ or a & or even a % in a json file? make your case and we'll talk"}
|
||||
for _, data := range datas {
|
||||
amt := fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
|
||||
tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[0])
|
||||
|
||||
if err := execTxWithState(state, tx, true); err == nil {
|
||||
t.Fatalf("Expected invalid data error from %s", data)
|
||||
}
|
||||
}
|
||||
|
||||
validateEntry := func(t *testing.T, entry *types.NameRegEntry, name, data string, addr []byte, expires int) {
|
||||
|
||||
if entry == nil {
|
||||
t.Fatalf("Could not find name %s", name)
|
||||
}
|
||||
if bytes.Compare(entry.Owner, addr) != 0 {
|
||||
t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr)
|
||||
}
|
||||
if data != entry.Data {
|
||||
t.Fatalf("Wrong data. Got %s expected %s", entry.Data, data)
|
||||
}
|
||||
if name != entry.Name {
|
||||
t.Fatalf("Wrong name. Got %s expected %s", entry.Name, name)
|
||||
}
|
||||
if expires != entry.Expires {
|
||||
t.Fatalf("Wrong expiry. Got %d, expected %d", entry.Expires, expires)
|
||||
}
|
||||
}
|
||||
|
||||
// try a good one, check data, owner, expiry
|
||||
name = "@looking_good/karaoke_bar.broadband"
|
||||
data = "on this side of neptune there are 1234567890 people: first is OMNIVORE+-3. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')"
|
||||
amt := fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
|
||||
tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[0])
|
||||
if err := execTxWithState(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry := state.GetNameRegEntry(name)
|
||||
validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks)
|
||||
|
||||
// fail to update it as non-owner, in same block
|
||||
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[1])
|
||||
if err := execTxWithState(state, tx, true); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
|
||||
// update it as owner, just to increase expiry, in same block
|
||||
// NOTE: we have to resend the data or it will clear it (is this what we want?)
|
||||
tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[0])
|
||||
if err := execTxWithStateNewBlock(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*2)
|
||||
|
||||
// update it as owner, just to increase expiry, in next block
|
||||
tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[0])
|
||||
if err := execTxWithStateNewBlock(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3)
|
||||
|
||||
// fail to update it as non-owner
|
||||
state.LastBlockHeight = entry.Expires - 1
|
||||
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[1])
|
||||
if err := execTxWithState(state, tx, true); err == nil {
|
||||
t.Fatal("Expected error")
|
||||
}
|
||||
|
||||
// once expires, non-owner succeeds
|
||||
state.LastBlockHeight = entry.Expires
|
||||
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[1])
|
||||
if err := execTxWithState(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks)
|
||||
|
||||
// update it as new owner, with new data (longer), but keep the expiry!
|
||||
data = "In the beginning there was no thing, not even the beginning. It hadn't been here, no there, nor for that matter anywhere, not especially because it had not to even exist, let alone to not. Nothing especially odd about that."
|
||||
oldCredit := amt - fee
|
||||
numDesiredBlocks = 10
|
||||
amt = fee + (int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data) - oldCredit)
|
||||
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[1])
|
||||
if err := execTxWithState(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks)
|
||||
|
||||
// test removal
|
||||
amt = fee
|
||||
data = ""
|
||||
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[1])
|
||||
if err := execTxWithStateNewBlock(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
if entry != nil {
|
||||
t.Fatal("Expected removed entry to be nil")
|
||||
}
|
||||
|
||||
// create entry by key0,
|
||||
// test removal by key1 after expiry
|
||||
name = "looking_good/karaoke_bar"
|
||||
data = "some data"
|
||||
amt = fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
|
||||
tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[0])
|
||||
if err := execTxWithState(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
validateEntry(t, entry, name, data, privAccounts[0].Address, state.LastBlockHeight+numDesiredBlocks)
|
||||
state.LastBlockHeight = entry.Expires
|
||||
|
||||
amt = fee
|
||||
data = ""
|
||||
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
|
||||
tx.Sign(state.ChainID, privAccounts[1])
|
||||
if err := execTxWithStateNewBlock(state, tx, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entry = state.GetNameRegEntry(name)
|
||||
if entry != nil {
|
||||
t.Fatal("Expected removed entry to be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test overflows.
|
||||
// TODO: test for unbonding validators.
|
||||
func TestTxs(t *testing.T) {
|
||||
|
||||
state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000)
|
||||
|
||||
//val0 := state.GetValidatorInfo(privValidators[0].Address)
|
||||
acc0 := state.GetAccount(privAccounts[0].PubKey.Address())
|
||||
acc0PubKey := privAccounts[0].PubKey
|
||||
acc1 := state.GetAccount(privAccounts[1].PubKey.Address())
|
||||
|
||||
// SendTx.
|
||||
{
|
||||
state := state.Copy()
|
||||
tx := &types.SendTx{
|
||||
Inputs: []*types.TxInput{
|
||||
&types.TxInput{
|
||||
Address: acc0.Address,
|
||||
Amount: 1,
|
||||
Sequence: acc0.Sequence + 1,
|
||||
PubKey: acc0PubKey,
|
||||
},
|
||||
},
|
||||
Outputs: []*types.TxOutput{
|
||||
&types.TxOutput{
|
||||
Address: acc1.Address,
|
||||
Amount: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx)
|
||||
err := execTxWithState(state, tx, true)
|
||||
if err != nil {
|
||||
t.Errorf("Got error in executing send 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)
|
||||
}
|
||||
}
|
||||
|
||||
// CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more
|
||||
{
|
||||
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(state.ChainID, 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 := "satoshi"
|
||||
entryData := `
|
||||
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 := int64(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(state.ChainID, 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 entry.Data != entryData {
|
||||
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(state.ChainID, tx)
|
||||
err = execTxWithState(state, tx, true)
|
||||
if _, ok := err.(types.ErrTxInvalidString); !ok {
|
||||
t.Errorf("Expected invalid string error. Got: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// BondTx.
|
||||
{
|
||||
state := state.Copy()
|
||||
tx := &types.BondTx{
|
||||
PubKey: acc0PubKey.(account.PubKeyEd25519),
|
||||
Inputs: []*types.TxInput{
|
||||
&types.TxInput{
|
||||
Address: acc0.Address,
|
||||
Amount: 1,
|
||||
Sequence: acc0.Sequence + 1,
|
||||
PubKey: acc0PubKey,
|
||||
},
|
||||
},
|
||||
UnbondTo: []*types.TxOutput{
|
||||
&types.TxOutput{
|
||||
Address: acc0.Address,
|
||||
Amount: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
tx.Signature = privAccounts[0].Sign(state.ChainID, tx).(account.SignatureEd25519)
|
||||
tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx)
|
||||
err := execTxWithState(state, tx, true)
|
||||
if err != nil {
|
||||
t.Errorf("Got error in executing bond transaction, %v", err)
|
||||
}
|
||||
newAcc0 := state.GetAccount(acc0.Address)
|
||||
if newAcc0.Balance != acc0.Balance-1 {
|
||||
t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v",
|
||||
acc0.Balance-1, newAcc0.Balance)
|
||||
}
|
||||
_, acc0Val := state.BondedValidators.GetByAddress(acc0.Address)
|
||||
if acc0Val == nil {
|
||||
t.Errorf("acc0Val not present")
|
||||
}
|
||||
if acc0Val.BondHeight != state.LastBlockHeight+1 {
|
||||
t.Errorf("Unexpected bond height. Expected %v, got %v",
|
||||
state.LastBlockHeight, acc0Val.BondHeight)
|
||||
}
|
||||
if acc0Val.VotingPower != 1 {
|
||||
t.Errorf("Unexpected voting power. Expected %v, got %v",
|
||||
acc0Val.VotingPower, acc0.Balance)
|
||||
}
|
||||
if acc0Val.Accum != 0 {
|
||||
t.Errorf("Unexpected accum. Expected 0, got %v",
|
||||
acc0Val.Accum)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO UnbondTx.
|
||||
|
||||
}
|
||||
|
||||
func TestSuicide(t *testing.T) {
|
||||
|
||||
state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000)
|
||||
|
||||
acc0 := state.GetAccount(privAccounts[0].PubKey.Address())
|
||||
acc0PubKey := privAccounts[0].PubKey
|
||||
acc1 := state.GetAccount(privAccounts[1].PubKey.Address())
|
||||
acc2 := state.GetAccount(privAccounts[2].Address)
|
||||
sendingAmount, refundedBalance, oldBalance := int64(1), acc1.Balance, acc2.Balance
|
||||
|
||||
newAcc1 := state.GetAccount(acc1.Address)
|
||||
|
||||
// store 0x1 at 0x1, push an address, then suicide :)
|
||||
contractCode := []byte{0x60, 0x01, 0x60, 0x01, 0x55, 0x73}
|
||||
contractCode = append(contractCode, acc2.Address...)
|
||||
contractCode = append(contractCode, 0xff)
|
||||
newAcc1.Code = contractCode
|
||||
state.UpdateAccount(newAcc1)
|
||||
|
||||
// send call tx with no data, cause suicide
|
||||
tx := types.NewCallTxWithNonce(acc0PubKey, acc1.Address, nil, sendingAmount, 1000, 0, acc0.Sequence+1)
|
||||
tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx)
|
||||
|
||||
// we use cache instead of execTxWithState so we can run the tx twice
|
||||
cache := NewBlockCache(state)
|
||||
if err := ExecTx(cache, tx, true, nil); err != nil {
|
||||
t.Errorf("Got error in executing call transaction, %v", err)
|
||||
}
|
||||
|
||||
// if we do it again, we won't get an error, but the suicide
|
||||
// shouldn't happen twice and the caller should lose fee
|
||||
tx.Input.Sequence += 1
|
||||
tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx)
|
||||
if err := ExecTx(cache, tx, true, nil); err != nil {
|
||||
t.Errorf("Got error in executing call transaction, %v", err)
|
||||
}
|
||||
|
||||
// commit the block
|
||||
cache.Sync()
|
||||
|
||||
// acc2 should receive the sent funds and the contracts balance
|
||||
newAcc2 := state.GetAccount(acc2.Address)
|
||||
newBalance := sendingAmount + refundedBalance + oldBalance
|
||||
if newAcc2.Balance != newBalance {
|
||||
t.Errorf("Unexpected newAcc2 balance. Expected %v, got %v",
|
||||
newAcc2.Balance, newBalance)
|
||||
}
|
||||
newAcc1 = state.GetAccount(acc1.Address)
|
||||
if newAcc1 != nil {
|
||||
t.Errorf("Expected account to be removed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddValidator(t *testing.T) {
|
||||
|
||||
// Generate a state, save & load it.
|
||||
s0, privAccounts, privValidators := RandGenesisState(10, false, 1000, 1, false, 1000)
|
||||
|
||||
// The first privAccount will become a validator
|
||||
acc0 := privAccounts[0]
|
||||
bondTx := &types.BondTx{
|
||||
PubKey: acc0.PubKey.(account.PubKeyEd25519),
|
||||
Inputs: []*types.TxInput{
|
||||
&types.TxInput{
|
||||
Address: acc0.Address,
|
||||
Amount: 1000,
|
||||
Sequence: 1,
|
||||
PubKey: acc0.PubKey,
|
||||
},
|
||||
},
|
||||
UnbondTo: []*types.TxOutput{
|
||||
&types.TxOutput{
|
||||
Address: acc0.Address,
|
||||
Amount: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
bondTx.Signature = acc0.Sign(s0.ChainID, bondTx).(account.SignatureEd25519)
|
||||
bondTx.Inputs[0].Signature = acc0.Sign(s0.ChainID, bondTx)
|
||||
|
||||
// Make complete block and blockParts
|
||||
block0 := makeBlock(t, s0, nil, []types.Tx{bondTx})
|
||||
block0Parts := block0.MakePartSet()
|
||||
|
||||
// Sanity check
|
||||
if s0.BondedValidators.Size() != 1 {
|
||||
t.Error("Expected there to be 1 validators before bondTx")
|
||||
}
|
||||
|
||||
// Now append the block to s0.
|
||||
err := ExecBlock(s0, block0, block0Parts.Header())
|
||||
if err != nil {
|
||||
t.Error("Error appending initial block:", err)
|
||||
}
|
||||
|
||||
// Must save before further modification
|
||||
s0.Save()
|
||||
|
||||
// Test new validator set
|
||||
if s0.BondedValidators.Size() != 2 {
|
||||
t.Error("Expected there to be 2 validators after bondTx")
|
||||
}
|
||||
|
||||
// The validation for the next block should only require 1 signature
|
||||
// (the new validator wasn't active for block0)
|
||||
precommit0 := &types.Vote{
|
||||
Height: 1,
|
||||
Round: 0,
|
||||
Type: types.VoteTypePrecommit,
|
||||
BlockHash: block0.Hash(),
|
||||
BlockPartsHeader: block0Parts.Header(),
|
||||
}
|
||||
privValidators[0].SignVote(s0.ChainID, precommit0)
|
||||
|
||||
block1 := makeBlock(t, s0,
|
||||
&types.Validation{
|
||||
Precommits: []*types.Vote{
|
||||
precommit0,
|
||||
},
|
||||
}, nil,
|
||||
)
|
||||
block1Parts := block1.MakePartSet()
|
||||
err = ExecBlock(s0, block1, block1Parts.Header())
|
||||
if err != nil {
|
||||
t.Error("Error appending secondary block:", err)
|
||||
}
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ...
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
type TxCache struct {
|
||||
backend *BlockCache
|
||||
accounts map[Word256]vmAccountInfo
|
||||
storages map[Tuple256]Word256
|
||||
}
|
||||
|
||||
func NewTxCache(backend *BlockCache) *TxCache {
|
||||
return &TxCache{
|
||||
backend: backend,
|
||||
accounts: make(map[Word256]vmAccountInfo),
|
||||
storages: make(map[Tuple256]Word256),
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// TxCache.account
|
||||
|
||||
func (cache *TxCache) GetAccount(addr Word256) *vm.Account {
|
||||
acc, removed := cache.accounts[addr].unpack()
|
||||
if removed {
|
||||
return nil
|
||||
} else if acc == nil {
|
||||
acc2 := cache.backend.GetAccount(addr.Postfix(20))
|
||||
if acc2 != nil {
|
||||
return toVMAccount(acc2)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func (cache *TxCache) UpdateAccount(acc *vm.Account) {
|
||||
addr := acc.Address
|
||||
_, removed := cache.accounts[addr].unpack()
|
||||
if removed {
|
||||
PanicSanity("UpdateAccount on a removed account")
|
||||
}
|
||||
cache.accounts[addr] = vmAccountInfo{acc, false}
|
||||
}
|
||||
|
||||
func (cache *TxCache) RemoveAccount(acc *vm.Account) {
|
||||
addr := acc.Address
|
||||
_, removed := cache.accounts[addr].unpack()
|
||||
if removed {
|
||||
PanicSanity("RemoveAccount on a removed account")
|
||||
}
|
||||
cache.accounts[addr] = vmAccountInfo{acc, true}
|
||||
}
|
||||
|
||||
// Creates a 20 byte address and bumps the creator's nonce.
|
||||
func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account {
|
||||
|
||||
// Generate an address
|
||||
nonce := creator.Nonce
|
||||
creator.Nonce += 1
|
||||
|
||||
addr := LeftPadWord256(NewContractAddress(creator.Address.Postfix(20), int(nonce)))
|
||||
|
||||
// Create account from address.
|
||||
account, removed := cache.accounts[addr].unpack()
|
||||
if removed || account == nil {
|
||||
account = &vm.Account{
|
||||
Address: addr,
|
||||
Balance: 0,
|
||||
Code: nil,
|
||||
Nonce: 0,
|
||||
Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress256).Permissions,
|
||||
Other: vmAccountOther{
|
||||
PubKey: nil,
|
||||
StorageRoot: nil,
|
||||
},
|
||||
}
|
||||
cache.accounts[addr] = vmAccountInfo{account, false}
|
||||
return account
|
||||
} else {
|
||||
// either we've messed up nonce handling, or sha3 is broken
|
||||
PanicSanity(Fmt("Could not create account, address already exists: %X", addr))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TxCache.account
|
||||
//-------------------------------------
|
||||
// TxCache.storage
|
||||
|
||||
func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 {
|
||||
// Check cache
|
||||
value, ok := cache.storages[Tuple256{addr, key}]
|
||||
if ok {
|
||||
return value
|
||||
}
|
||||
|
||||
// Load from backend
|
||||
return cache.backend.GetStorage(addr, key)
|
||||
}
|
||||
|
||||
// NOTE: Set value to zero to removed from the trie.
|
||||
func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) {
|
||||
_, removed := cache.accounts[addr].unpack()
|
||||
if removed {
|
||||
PanicSanity("SetStorage() on a removed account")
|
||||
}
|
||||
cache.storages[Tuple256{addr, key}] = value
|
||||
}
|
||||
|
||||
// TxCache.storage
|
||||
//-------------------------------------
|
||||
|
||||
// These updates do not have to be in deterministic order,
|
||||
// the backend is responsible for ordering updates.
|
||||
func (cache *TxCache) Sync() {
|
||||
|
||||
// Remove or update storage
|
||||
for addrKey, value := range cache.storages {
|
||||
addr, key := Tuple256Split(addrKey)
|
||||
cache.backend.SetStorage(addr, key, value)
|
||||
}
|
||||
|
||||
// Remove or update accounts
|
||||
for addr, accInfo := range cache.accounts {
|
||||
acc, removed := accInfo.unpack()
|
||||
if removed {
|
||||
cache.backend.RemoveAccount(addr.Postfix(20))
|
||||
} else {
|
||||
cache.backend.UpdateAccount(toStateAccount(acc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Convenience function to return address of new contract
|
||||
func NewContractAddress(caller []byte, nonce int) []byte {
|
||||
return types.NewContractAddress(caller, nonce)
|
||||
}
|
||||
|
||||
// Converts backend.Account to vm.Account struct.
|
||||
func toVMAccount(acc *acm.Account) *vm.Account {
|
||||
return &vm.Account{
|
||||
Address: LeftPadWord256(acc.Address),
|
||||
Balance: acc.Balance,
|
||||
Code: acc.Code, // This is crazy.
|
||||
Nonce: int64(acc.Sequence),
|
||||
Permissions: acc.Permissions, // Copy
|
||||
Other: vmAccountOther{
|
||||
PubKey: acc.PubKey,
|
||||
StorageRoot: acc.StorageRoot,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Converts vm.Account to backend.Account struct.
|
||||
func toStateAccount(acc *vm.Account) *acm.Account {
|
||||
var pubKey acm.PubKey
|
||||
var storageRoot []byte
|
||||
if acc.Other != nil {
|
||||
pubKey, storageRoot = acc.Other.(vmAccountOther).unpack()
|
||||
}
|
||||
|
||||
return &acm.Account{
|
||||
Address: acc.Address.Postfix(20),
|
||||
PubKey: pubKey,
|
||||
Balance: acc.Balance,
|
||||
Code: acc.Code,
|
||||
Sequence: int(acc.Nonce),
|
||||
StorageRoot: storageRoot,
|
||||
Permissions: acc.Permissions, // Copy
|
||||
}
|
||||
}
|
||||
|
||||
// Everything in acmAccount that doesn't belong in
|
||||
// exported vmAccount fields.
|
||||
type vmAccountOther struct {
|
||||
PubKey acm.PubKey
|
||||
StorageRoot []byte
|
||||
}
|
||||
|
||||
func (accOther vmAccountOther) unpack() (acm.PubKey, []byte) {
|
||||
return accOther.PubKey, accOther.StorageRoot
|
||||
}
|
||||
|
||||
type vmAccountInfo struct {
|
||||
account *vm.Account
|
||||
removed bool
|
||||
}
|
||||
|
||||
func (accInfo vmAccountInfo) unpack() (*vm.Account, bool) {
|
||||
return accInfo.account, accInfo.removed
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
stypes "github.com/tendermint/tendermint/state/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
func TestStateToFromVMAccount(t *testing.T) {
|
||||
acmAcc1, _ := stypes.RandAccount(true, 456)
|
||||
vmAcc := toVMAccount(acmAcc1)
|
||||
acmAcc2 := toStateAccount(vmAcc)
|
||||
|
||||
acmAcc1Bytes := wire.BinaryBytes(acmAcc1)
|
||||
acmAcc2Bytes := wire.BinaryBytes(acmAcc2)
|
||||
if !bytes.Equal(acmAcc1Bytes, acmAcc2Bytes) {
|
||||
t.Errorf("Unexpected account wire bytes\n%X vs\n%X",
|
||||
acmAcc1Bytes, acmAcc2Bytes)
|
||||
}
|
||||
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------
|
||||
// we store the gendoc in the db
|
||||
|
||||
var GenDocKey = []byte("GenDocKey")
|
||||
|
||||
//------------------------------------------------------------
|
||||
// core types for a genesis definition
|
||||
|
||||
type BasicAccount struct {
|
||||
Address []byte `json:"address"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
|
||||
type GenesisAccount struct {
|
||||
Address []byte `json:"address"`
|
||||
Amount int64 `json:"amount"`
|
||||
Name string `json:"name"`
|
||||
Permissions *ptypes.AccountPermissions `json:"permissions"`
|
||||
}
|
||||
|
||||
type GenesisValidator struct {
|
||||
PubKey acm.PubKeyEd25519 `json:"pub_key"`
|
||||
Amount int64 `json:"amount"`
|
||||
Name string `json:"name"`
|
||||
UnbondTo []BasicAccount `json:"unbond_to"`
|
||||
}
|
||||
|
||||
type GenesisParams struct {
|
||||
GlobalPermissions *ptypes.AccountPermissions `json:"global_permissions"`
|
||||
}
|
||||
|
||||
type GenesisDoc struct {
|
||||
GenesisTime time.Time `json:"genesis_time"`
|
||||
ChainID string `json:"chain_id"`
|
||||
Params *GenesisParams `json:"params"`
|
||||
Accounts []GenesisAccount `json:"accounts"`
|
||||
Validators []GenesisValidator `json:"validators"`
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Make genesis state from file
|
||||
|
||||
func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) {
|
||||
var err error
|
||||
wire.ReadJSONPtr(&genState, jsonBlob, &err)
|
||||
if err != nil {
|
||||
Exit(Fmt("Couldn't read GenesisDoc: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Make random genesis state
|
||||
|
||||
func RandAccount(randBalance bool, minBalance int64) (*acm.Account, *acm.PrivAccount) {
|
||||
privAccount := acm.GenPrivAccount()
|
||||
perms := ptypes.DefaultAccountPermissions
|
||||
acc := &acm.Account{
|
||||
Address: privAccount.PubKey.Address(),
|
||||
PubKey: privAccount.PubKey,
|
||||
Sequence: RandInt(),
|
||||
Balance: minBalance,
|
||||
Permissions: perms,
|
||||
}
|
||||
if randBalance {
|
||||
acc.Balance += int64(RandUint32())
|
||||
}
|
||||
return acc, privAccount
|
||||
}
|
||||
|
||||
func RandGenesisDoc(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*GenesisDoc, []*acm.PrivAccount, []*types.PrivValidator) {
|
||||
accounts := make([]GenesisAccount, numAccounts)
|
||||
privAccounts := make([]*acm.PrivAccount, numAccounts)
|
||||
defaultPerms := ptypes.DefaultAccountPermissions
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
account, privAccount := RandAccount(randBalance, minBalance)
|
||||
accounts[i] = GenesisAccount{
|
||||
Address: account.Address,
|
||||
Amount: account.Balance,
|
||||
Permissions: &defaultPerms, // This will get copied into each state.Account.
|
||||
}
|
||||
privAccounts[i] = privAccount
|
||||
}
|
||||
validators := make([]GenesisValidator, numValidators)
|
||||
privValidators := make([]*types.PrivValidator, numValidators)
|
||||
for i := 0; i < numValidators; i++ {
|
||||
valInfo, _, privVal := types.RandValidator(randBonded, minBonded)
|
||||
validators[i] = GenesisValidator{
|
||||
PubKey: valInfo.PubKey,
|
||||
Amount: valInfo.FirstBondAmount,
|
||||
UnbondTo: []BasicAccount{
|
||||
{
|
||||
Address: valInfo.PubKey.Address(),
|
||||
Amount: valInfo.FirstBondAmount,
|
||||
},
|
||||
},
|
||||
}
|
||||
privValidators[i] = privVal
|
||||
}
|
||||
sort.Sort(types.PrivValidatorsByAddress(privValidators))
|
||||
return &GenesisDoc{
|
||||
GenesisTime: time.Now(),
|
||||
ChainID: "tendermint_test",
|
||||
Accounts: accounts,
|
||||
Validators: validators,
|
||||
}, privAccounts, privValidators
|
||||
|
||||
}
|
@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-merkle"
|
||||
"github.com/tendermint/go-wire"
|
||||
@ -322,11 +321,11 @@ type Data struct {
|
||||
|
||||
func (data *Data) Hash() []byte {
|
||||
if data.hash == nil {
|
||||
bs := make([]interface{}, len(data.Txs))
|
||||
txs := make([]interface{}, len(data.Txs))
|
||||
for i, tx := range data.Txs {
|
||||
bs[i] = acm.SignBytes(config.GetString("chain_id"), tx)
|
||||
txs[i] = tx
|
||||
}
|
||||
data.hash = merkle.SimpleHashFromBinaries(bs) // NOTE: leaves are TxIDs.
|
||||
data.hash = merkle.SimpleHashFromBinaries(txs) // NOTE: leaves are TxIDs.
|
||||
}
|
||||
return data.hash
|
||||
}
|
||||
|
65
types/genesis.go
Normal file
65
types/genesis.go
Normal file
@ -0,0 +1,65 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------
|
||||
// we store the gendoc in the db
|
||||
|
||||
var GenDocKey = []byte("GenDocKey")
|
||||
|
||||
//------------------------------------------------------------
|
||||
// core types for a genesis definition
|
||||
|
||||
type GenesisValidator struct {
|
||||
PubKey crypto.PubKeyEd25519 `json:"pub_key"`
|
||||
Amount int64 `json:"amount"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type GenesisDoc struct {
|
||||
GenesisTime time.Time `json:"genesis_time"`
|
||||
ChainID string `json:"chain_id"`
|
||||
Validators []GenesisValidator `json:"validators"`
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Make genesis state from file
|
||||
|
||||
func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) {
|
||||
var err error
|
||||
wire.ReadJSONPtr(&genState, jsonBlob, &err)
|
||||
if err != nil {
|
||||
Exit(Fmt("Couldn't read GenesisDoc: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Make random genesis state
|
||||
|
||||
func RandGenesisDoc(numValidators int, randPower bool, minPower int64) (*GenesisDoc, []*PrivValidator) {
|
||||
validators := make([]GenesisValidator, numValidators)
|
||||
privValidators := make([]*PrivValidator, numValidators)
|
||||
for i := 0; i < numValidators; i++ {
|
||||
val, privVal := RandValidator(randPower, minPower)
|
||||
validators[i] = GenesisValidator{
|
||||
PubKey: val.PubKey,
|
||||
Amount: val.VotingPower,
|
||||
}
|
||||
privValidators[i] = privVal
|
||||
}
|
||||
sort.Sort(PrivValidatorsByAddress(privValidators))
|
||||
return &GenesisDoc{
|
||||
GenesisTime: time.Now(),
|
||||
ChainID: "tendermint_test",
|
||||
Validators: validators,
|
||||
}, privValidators
|
||||
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
MinNameRegistrationPeriod int = 5
|
||||
|
||||
// NOTE: base costs and validity checks are here so clients
|
||||
// can use them without importing state
|
||||
|
||||
// cost for storing a name for a block is
|
||||
// CostPerBlock*CostPerByte*(len(data) + 32)
|
||||
NameByteCostMultiplier int64 = 1
|
||||
NameBlockCostMultiplier int64 = 1
|
||||
|
||||
MaxNameLength = 64
|
||||
MaxDataLength = 1 << 16
|
||||
|
||||
// Name should be file system lik
|
||||
// Data should be anything permitted in JSON
|
||||
regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$")
|
||||
regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`)
|
||||
)
|
||||
|
||||
// filter strings
|
||||
func validateNameRegEntryName(name string) bool {
|
||||
return regexpAlphaNum.Match([]byte(name))
|
||||
}
|
||||
|
||||
func validateNameRegEntryData(data string) bool {
|
||||
return regexpJSON.Match([]byte(data))
|
||||
}
|
||||
|
||||
// base cost is "effective" number of bytes
|
||||
func NameBaseCost(name, data string) int64 {
|
||||
return int64(len(data) + 32)
|
||||
}
|
||||
|
||||
func NameCostPerBlock(baseCost int64) int64 {
|
||||
return NameBlockCostMultiplier * NameByteCostMultiplier * baseCost
|
||||
}
|
||||
|
||||
type NameRegEntry struct {
|
||||
Name string `json:"name"` // registered name for the entry
|
||||
Owner []byte `json:"owner"` // address that created the entry
|
||||
Data string `json:"data"` // data to store under this name
|
||||
Expires int `json:"expires"` // block at which this entry expires
|
||||
}
|
||||
|
||||
func (entry *NameRegEntry) Copy() *NameRegEntry {
|
||||
entryCopy := *entry
|
||||
return &entryCopy
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NodeInfo struct {
|
||||
PubKey acm.PubKeyEd25519 `json:"pub_key"`
|
||||
Moniker string `json:"moniker"`
|
||||
ChainID string `json:"chain_id"`
|
||||
Host string `json:"host"`
|
||||
P2PPort uint16 `json:"p2p_port"`
|
||||
RPCPort uint16 `json:"rpc_port"`
|
||||
|
||||
Version Versions `json:"versions"`
|
||||
}
|
||||
|
||||
type Versions struct {
|
||||
Revision string `json:"revision"`
|
||||
Tendermint string `json:"tendermint"`
|
||||
P2P string `json:"p2p"`
|
||||
RPC string `json:"rpc"`
|
||||
Wire string `json:"wire"`
|
||||
}
|
||||
|
||||
// CONTRACT: two nodes with the same Tendermint major and minor version and with the same ChainID are compatible
|
||||
func (ni *NodeInfo) CompatibleWith(no *NodeInfo) error {
|
||||
iM, im, _, ie := splitVersion(ni.Version.Tendermint)
|
||||
oM, om, _, oe := splitVersion(no.Version.Tendermint)
|
||||
|
||||
// if our own version number is not formatted right, we messed up
|
||||
if ie != nil {
|
||||
return ie
|
||||
}
|
||||
|
||||
// version number must be formatted correctly ("x.x.x")
|
||||
if oe != nil {
|
||||
return oe
|
||||
}
|
||||
|
||||
// major version must match
|
||||
if iM != oM {
|
||||
return fmt.Errorf("Peer is on a different major version. Got %v, expected %v", oM, iM)
|
||||
}
|
||||
|
||||
// minor version must match
|
||||
if im != om {
|
||||
return fmt.Errorf("Peer is on a different minor version. Got %v, expected %v", om, im)
|
||||
}
|
||||
|
||||
// nodes must be on the same chain_id
|
||||
if ni.ChainID != no.ChainID {
|
||||
return fmt.Errorf("Peer is on a different chain_id. Got %v, expected %v", no.ChainID, ni.ChainID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitVersion(version string) (string, string, string, error) {
|
||||
spl := strings.Split(version, ".")
|
||||
if len(spl) != 3 {
|
||||
return "", "", "", fmt.Errorf("Invalid version format %v", version)
|
||||
}
|
||||
return spl[0], spl[1], spl[2], nil
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
@ -6,12 +5,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
|
||||
"github.com/tendermint/ed25519"
|
||||
@ -37,12 +35,12 @@ func voteToStep(vote *Vote) int8 {
|
||||
}
|
||||
|
||||
type PrivValidator struct {
|
||||
Address []byte `json:"address"`
|
||||
PubKey acm.PubKeyEd25519 `json:"pub_key"`
|
||||
PrivKey acm.PrivKeyEd25519 `json:"priv_key"`
|
||||
LastHeight int `json:"last_height"`
|
||||
LastRound int `json:"last_round"`
|
||||
LastStep int8 `json:"last_step"`
|
||||
Address []byte `json:"address"`
|
||||
PubKey crypto.PubKeyEd25519 `json:"pub_key"`
|
||||
PrivKey crypto.PrivKeyEd25519 `json:"priv_key"`
|
||||
LastHeight int `json:"last_height"`
|
||||
LastRound int `json:"last_round"`
|
||||
LastStep int8 `json:"last_step"`
|
||||
|
||||
// For persistence.
|
||||
// Overloaded for testing.
|
||||
@ -55,8 +53,8 @@ func GenPrivValidator() *PrivValidator {
|
||||
privKeyBytes := new([64]byte)
|
||||
copy(privKeyBytes[:32], CRandBytes(32))
|
||||
pubKeyBytes := ed25519.MakePublicKey(privKeyBytes)
|
||||
pubKey := acm.PubKeyEd25519(*pubKeyBytes)
|
||||
privKey := acm.PrivKeyEd25519(*privKeyBytes)
|
||||
pubKey := crypto.PubKeyEd25519(*pubKeyBytes)
|
||||
privKey := crypto.PrivKeyEd25519(*privKeyBytes)
|
||||
return &PrivValidator{
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
@ -152,7 +150,7 @@ func (privVal *PrivValidator) SignVote(chainID string, vote *Vote) error {
|
||||
}
|
||||
|
||||
func (privVal *PrivValidator) SignVoteUnsafe(chainID string, vote *Vote) {
|
||||
vote.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, vote)).(acm.SignatureEd25519)
|
||||
vote.Signature = privVal.PrivKey.Sign(SignBytes(chainID, vote)).(crypto.SignatureEd25519)
|
||||
}
|
||||
|
||||
func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) error {
|
||||
@ -169,33 +167,13 @@ func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) e
|
||||
privVal.save()
|
||||
|
||||
// Sign
|
||||
proposal.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, proposal)).(acm.SignatureEd25519)
|
||||
proposal.Signature = privVal.PrivKey.Sign(SignBytes(chainID, proposal)).(crypto.SignatureEd25519)
|
||||
return nil
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("Attempt of duplicate signing of proposal: Height %v, Round %v", proposal.Height, proposal.Round))
|
||||
}
|
||||
}
|
||||
|
||||
func (privVal *PrivValidator) SignRebondTx(chainID string, rebondTx *RebondTx) error {
|
||||
privVal.mtx.Lock()
|
||||
defer privVal.mtx.Unlock()
|
||||
if privVal.LastHeight < rebondTx.Height {
|
||||
|
||||
// Persist height/round/step
|
||||
// Prevent doing anything else for this rebondTx.Height.
|
||||
privVal.LastHeight = rebondTx.Height
|
||||
privVal.LastRound = math.MaxInt32 // MaxInt64 overflows on 32bit architectures.
|
||||
privVal.LastStep = math.MaxInt8
|
||||
privVal.save()
|
||||
|
||||
// Sign
|
||||
rebondTx.Signature = privVal.PrivKey.Sign(acm.SignBytes(chainID, rebondTx)).(acm.SignatureEd25519)
|
||||
return nil
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("Attempt of duplicate signing of rebondTx: Height %v", rebondTx.Height))
|
||||
}
|
||||
}
|
||||
|
||||
func (privVal *PrivValidator) String() string {
|
||||
return fmt.Sprintf("PrivValidator{%X LH:%v, LR:%v, LS:%v}", privVal.Address, privVal.LastHeight, privVal.LastRound, privVal.LastStep)
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
@ -16,11 +16,11 @@ var (
|
||||
)
|
||||
|
||||
type Proposal struct {
|
||||
Height int `json:"height"`
|
||||
Round int `json:"round"`
|
||||
BlockPartsHeader PartSetHeader `json:"block_parts_header"`
|
||||
POLRound int `json:"pol_round"` // -1 if null.
|
||||
Signature acm.SignatureEd25519 `json:"signature"`
|
||||
Height int `json:"height"`
|
||||
Round int `json:"round"`
|
||||
BlockPartsHeader PartSetHeader `json:"block_parts_header"`
|
||||
POLRound int `json:"pol_round"` // -1 if null.
|
||||
Signature crypto.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
func NewProposal(height int, round int, blockPartsHeader PartSetHeader, polRound int) *Proposal {
|
||||
|
@ -3,7 +3,6 @@ package types
|
||||
import (
|
||||
"testing"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
)
|
||||
@ -15,7 +14,7 @@ func TestProposalSignable(t *testing.T) {
|
||||
BlockPartsHeader: PartSetHeader{111, []byte("blockparts")},
|
||||
POLRound: -1,
|
||||
}
|
||||
signBytes := acm.SignBytes(config.GetString("chain_id"), proposal)
|
||||
signBytes := SignBytes(config.GetString("chain_id"), proposal)
|
||||
signStr := string(signBytes)
|
||||
|
||||
expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_round":-1,"round":23456}}`,
|
||||
|
30
types/signable.go
Normal file
30
types/signable.go
Normal file
@ -0,0 +1,30 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-merkle"
|
||||
)
|
||||
|
||||
// Signable is an interface for all signable things.
|
||||
// It typically removes signatures before serializing.
|
||||
type Signable interface {
|
||||
WriteSignBytes(chainID string, w io.Writer, n *int64, err *error)
|
||||
}
|
||||
|
||||
// SignBytes is a convenience method for getting the bytes to sign of a Signable.
|
||||
func SignBytes(chainID string, o Signable) []byte {
|
||||
buf, n, err := new(bytes.Buffer), new(int64), new(error)
|
||||
o.WriteSignBytes(chainID, buf, n, err)
|
||||
if *err != nil {
|
||||
PanicCrisis(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// HashSignBytes is a convenience method for getting the hash of the bytes of a signable
|
||||
func HashSignBytes(chainID string, o Signable) []byte {
|
||||
return merkle.SimpleHashFromBinary(SignBytes(chainID, o))
|
||||
}
|
374
types/tx.go
374
types/tx.go
@ -1,375 +1,3 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTxInvalidAddress = errors.New("Error invalid address")
|
||||
ErrTxDuplicateAddress = errors.New("Error duplicate address")
|
||||
ErrTxInvalidAmount = errors.New("Error invalid amount")
|
||||
ErrTxInsufficientFunds = errors.New("Error insufficient funds")
|
||||
ErrTxInsufficientGasPrice = errors.New("Error insufficient gas price")
|
||||
ErrTxUnknownPubKey = errors.New("Error unknown pubkey")
|
||||
ErrTxInvalidPubKey = errors.New("Error invalid pubkey")
|
||||
ErrTxInvalidSignature = errors.New("Error invalid signature")
|
||||
ErrTxPermissionDenied = errors.New("Error permission denied")
|
||||
)
|
||||
|
||||
type ErrTxInvalidString struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e ErrTxInvalidString) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
type ErrTxInvalidSequence struct {
|
||||
Got int
|
||||
Expected int
|
||||
}
|
||||
|
||||
func (e ErrTxInvalidSequence) Error() string {
|
||||
return Fmt("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected)
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
- UnbondTx Validator leaves
|
||||
- DupeoutTx Validator dupes out (equivocates)
|
||||
|
||||
Admin Txs:
|
||||
- PermissionsTx
|
||||
*/
|
||||
|
||||
type Tx interface {
|
||||
WriteSignBytes(chainID string, w io.Writer, n *int64, err *error)
|
||||
}
|
||||
|
||||
// Types of Tx implementations
|
||||
const (
|
||||
// Account transactions
|
||||
TxTypeSend = byte(0x01)
|
||||
TxTypeCall = byte(0x02)
|
||||
TxTypeName = byte(0x03)
|
||||
|
||||
// Validation transactions
|
||||
TxTypeBond = byte(0x11)
|
||||
TxTypeUnbond = byte(0x12)
|
||||
TxTypeRebond = byte(0x13)
|
||||
TxTypeDupeout = byte(0x14)
|
||||
|
||||
// Admin transactions
|
||||
TxTypePermissions = byte(0x20)
|
||||
)
|
||||
|
||||
// for wire.readReflect
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ Tx }{},
|
||||
wire.ConcreteType{&SendTx{}, TxTypeSend},
|
||||
wire.ConcreteType{&CallTx{}, TxTypeCall},
|
||||
wire.ConcreteType{&NameTx{}, TxTypeName},
|
||||
wire.ConcreteType{&BondTx{}, TxTypeBond},
|
||||
wire.ConcreteType{&UnbondTx{}, TxTypeUnbond},
|
||||
wire.ConcreteType{&RebondTx{}, TxTypeRebond},
|
||||
wire.ConcreteType{&DupeoutTx{}, TxTypeDupeout},
|
||||
wire.ConcreteType{&PermissionsTx{}, TxTypePermissions},
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type TxInput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Amount int64 `json:"amount"` // Must not exceed account balance
|
||||
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
|
||||
Signature acm.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
|
||||
PubKey acm.PubKey `json:"pub_key"` // Must not be nil, may be nil
|
||||
}
|
||||
|
||||
func (txIn *TxInput) ValidateBasic() error {
|
||||
if len(txIn.Address) != 20 {
|
||||
return ErrTxInvalidAddress
|
||||
}
|
||||
if txIn.Amount == 0 {
|
||||
return ErrTxInvalidAmount
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"address":"%X","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err)
|
||||
}
|
||||
|
||||
func (txIn *TxInput) String() string {
|
||||
return Fmt("TxInput{%X,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PubKey)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type TxOutput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs.
|
||||
}
|
||||
|
||||
func (txOut *TxOutput) ValidateBasic() error {
|
||||
if len(txOut.Address) != 20 {
|
||||
return ErrTxInvalidAddress
|
||||
}
|
||||
if txOut.Amount == 0 {
|
||||
return ErrTxInvalidAmount
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"address":"%X","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err)
|
||||
}
|
||||
|
||||
func (txOut *TxOutput) String() string {
|
||||
return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Amount)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type SendTx struct {
|
||||
Inputs []*TxInput `json:"inputs"`
|
||||
Outputs []*TxOutput `json:"outputs"`
|
||||
}
|
||||
|
||||
func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err)
|
||||
for i, in := range tx.Inputs {
|
||||
in.WriteSignBytes(w, n, err)
|
||||
if i != len(tx.Inputs)-1 {
|
||||
wire.WriteTo([]byte(","), w, n, err)
|
||||
}
|
||||
}
|
||||
wire.WriteTo([]byte(`],"outputs":[`), w, n, err)
|
||||
for i, out := range tx.Outputs {
|
||||
out.WriteSignBytes(w, n, err)
|
||||
if i != len(tx.Outputs)-1 {
|
||||
wire.WriteTo([]byte(","), w, n, err)
|
||||
}
|
||||
}
|
||||
wire.WriteTo([]byte(`]}]}`), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *SendTx) String() string {
|
||||
return Fmt("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type CallTx struct {
|
||||
Input *TxInput `json:"input"`
|
||||
Address []byte `json:"address"`
|
||||
GasLimit int64 `json:"gas_limit"`
|
||||
Fee int64 `json:"fee"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err)
|
||||
tx.Input.WriteSignBytes(w, n, err)
|
||||
wire.WriteTo([]byte(`}]}`), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *CallTx) String() string {
|
||||
return Fmt("CallTx{%v -> %x: %x}", tx.Input, tx.Address, tx.Data)
|
||||
}
|
||||
|
||||
func NewContractAddress(caller []byte, nonce int) []byte {
|
||||
temp := make([]byte, 32+8)
|
||||
copy(temp, caller)
|
||||
PutInt64BE(temp[32:], int64(nonce))
|
||||
hasher := ripemd160.New()
|
||||
hasher.Write(temp) // does not error
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type NameTx struct {
|
||||
Input *TxInput `json:"input"`
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
Fee int64 `json:"fee"`
|
||||
}
|
||||
|
||||
func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err)
|
||||
wire.WriteTo([]byte(`,"input":`), w, n, err)
|
||||
tx.Input.WriteSignBytes(w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"name":%s`, jsonEscape(tx.Name))), w, n, err)
|
||||
wire.WriteTo([]byte(`}]}`), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *NameTx) ValidateStrings() error {
|
||||
if len(tx.Name) == 0 {
|
||||
return ErrTxInvalidString{"Name must not be empty"}
|
||||
}
|
||||
if len(tx.Name) > MaxNameLength {
|
||||
return ErrTxInvalidString{Fmt("Name is too long. Max %d bytes", MaxNameLength)}
|
||||
}
|
||||
if len(tx.Data) > MaxDataLength {
|
||||
return ErrTxInvalidString{Fmt("Data is too long. Max %d bytes", MaxDataLength)}
|
||||
}
|
||||
|
||||
if !validateNameRegEntryName(tx.Name) {
|
||||
return ErrTxInvalidString{Fmt("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)}
|
||||
}
|
||||
|
||||
if !validateNameRegEntryData(tx.Data) {
|
||||
return ErrTxInvalidString{Fmt("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *NameTx) String() string {
|
||||
return Fmt("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type BondTx struct {
|
||||
PubKey acm.PubKeyEd25519 `json:"pub_key"`
|
||||
Signature acm.SignatureEd25519 `json:"signature"`
|
||||
Inputs []*TxInput `json:"inputs"`
|
||||
UnbondTo []*TxOutput `json:"unbond_to"`
|
||||
}
|
||||
|
||||
func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err)
|
||||
for i, in := range tx.Inputs {
|
||||
in.WriteSignBytes(w, n, err)
|
||||
if i != len(tx.Inputs)-1 {
|
||||
wire.WriteTo([]byte(","), w, n, err)
|
||||
}
|
||||
}
|
||||
wire.WriteTo([]byte(Fmt(`],"pub_key":`)), w, n, err)
|
||||
wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err)
|
||||
wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err)
|
||||
for i, out := range tx.UnbondTo {
|
||||
out.WriteSignBytes(w, n, err)
|
||||
if i != len(tx.UnbondTo)-1 {
|
||||
wire.WriteTo([]byte(","), w, n, err)
|
||||
}
|
||||
}
|
||||
wire.WriteTo([]byte(`]}]}`), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *BondTx) String() string {
|
||||
return Fmt("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type UnbondTx struct {
|
||||
Address []byte `json:"address"`
|
||||
Height int `json:"height"`
|
||||
Signature acm.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *UnbondTx) String() string {
|
||||
return Fmt("UnbondTx{%X,%v,%v}", tx.Address, tx.Height, tx.Signature)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type RebondTx struct {
|
||||
Address []byte `json:"address"`
|
||||
Height int `json:"height"`
|
||||
Signature acm.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *RebondTx) String() string {
|
||||
return Fmt("RebondTx{%X,%v,%v}", tx.Address, tx.Height, tx.Signature)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type DupeoutTx struct {
|
||||
Address []byte `json:"address"`
|
||||
VoteA Vote `json:"vote_a"`
|
||||
VoteB Vote `json:"vote_b"`
|
||||
}
|
||||
|
||||
func (tx *DupeoutTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
PanicSanity("DupeoutTx has no sign bytes")
|
||||
}
|
||||
|
||||
func (tx *DupeoutTx) String() string {
|
||||
return Fmt("DupeoutTx{%X,%v,%v}", tx.Address, tx.VoteA, tx.VoteB)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type PermissionsTx struct {
|
||||
Input *TxInput `json:"input"`
|
||||
PermArgs ptypes.PermArgs `json:"args"`
|
||||
}
|
||||
|
||||
func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
|
||||
wire.WriteTo([]byte(Fmt(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err)
|
||||
wire.WriteJSON(tx.PermArgs, w, n, err)
|
||||
wire.WriteTo([]byte(`","input":`), w, n, err)
|
||||
tx.Input.WriteSignBytes(w, n, err)
|
||||
wire.WriteTo([]byte(`}]}`), w, n, err)
|
||||
}
|
||||
|
||||
func (tx *PermissionsTx) String() string {
|
||||
return Fmt("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// This should match the leaf hashes of Block.Data.Hash()'s SimpleMerkleTree.
|
||||
func TxID(chainID string, tx Tx) []byte {
|
||||
signBytes := acm.SignBytes(chainID, tx)
|
||||
return wire.BinaryRipemd160(signBytes)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// Contract: This function is deterministic and completely reversible.
|
||||
func jsonEscape(str string) string {
|
||||
escapedBytes, err := json.Marshal(str)
|
||||
if err != nil {
|
||||
PanicSanity(Fmt("Error json-escaping a string", str))
|
||||
}
|
||||
return string(escapedBytes)
|
||||
}
|
||||
type Tx []byte
|
||||
|
178
types/tx_test.go
178
types/tx_test.go
@ -1,178 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
)
|
||||
|
||||
var chainID string
|
||||
|
||||
func init() {
|
||||
chainID = config.GetString("chain_id")
|
||||
}
|
||||
|
||||
func TestSendTxSignable(t *testing.T) {
|
||||
sendTx := &SendTx{
|
||||
Inputs: []*TxInput{
|
||||
&TxInput{
|
||||
Address: []byte("input1"),
|
||||
Amount: 12345,
|
||||
Sequence: 67890,
|
||||
},
|
||||
&TxInput{
|
||||
Address: []byte("input2"),
|
||||
Amount: 111,
|
||||
Sequence: 222,
|
||||
},
|
||||
},
|
||||
Outputs: []*TxOutput{
|
||||
&TxOutput{
|
||||
Address: []byte("output1"),
|
||||
Amount: 333,
|
||||
},
|
||||
&TxOutput{
|
||||
Address: []byte("output2"),
|
||||
Amount: 444,
|
||||
},
|
||||
},
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, sendTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"outputs":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallTxSignable(t *testing.T) {
|
||||
callTx := &CallTx{
|
||||
Input: &TxInput{
|
||||
Address: []byte("input1"),
|
||||
Amount: 12345,
|
||||
Sequence: 67890,
|
||||
},
|
||||
Address: []byte("contract1"),
|
||||
GasLimit: 111,
|
||||
Fee: 222,
|
||||
Data: []byte("data1"),
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, callTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[2,{"address":"636F6E747261637431","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"696E70757431","amount":12345,"sequence":67890}}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameTxSignable(t *testing.T) {
|
||||
nameTx := &NameTx{
|
||||
Input: &TxInput{
|
||||
Address: []byte("input1"),
|
||||
Amount: 12345,
|
||||
Sequence: 250,
|
||||
},
|
||||
Name: "google.com",
|
||||
Data: "secretly.not.google.com",
|
||||
Fee: 1000,
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, nameTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[3,{"data":"secretly.not.google.com","fee":1000,"input":{"address":"696E70757431","amount":12345,"sequence":250},"name":"google.com"}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBondTxSignable(t *testing.T) {
|
||||
privKeyBytes := make([]byte, 64)
|
||||
privAccount := acm.GenPrivAccountFromPrivKeyBytes(privKeyBytes)
|
||||
bondTx := &BondTx{
|
||||
PubKey: privAccount.PubKey.(acm.PubKeyEd25519),
|
||||
Inputs: []*TxInput{
|
||||
&TxInput{
|
||||
Address: []byte("input1"),
|
||||
Amount: 12345,
|
||||
Sequence: 67890,
|
||||
},
|
||||
&TxInput{
|
||||
Address: []byte("input2"),
|
||||
Amount: 111,
|
||||
Sequence: 222,
|
||||
},
|
||||
},
|
||||
UnbondTo: []*TxOutput{
|
||||
&TxOutput{
|
||||
Address: []byte("output1"),
|
||||
Amount: 333,
|
||||
},
|
||||
&TxOutput{
|
||||
Address: []byte("output2"),
|
||||
Amount: 444,
|
||||
},
|
||||
},
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, bondTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[17,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"pub_key":[1,"3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29"],"unbond_to":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Unexpected sign string for BondTx. \nGot %s\nExpected %s", signStr, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnbondTxSignable(t *testing.T) {
|
||||
unbondTx := &UnbondTx{
|
||||
Address: []byte("address1"),
|
||||
Height: 111,
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, unbondTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[18,{"address":"6164647265737331","height":111}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Got unexpected sign string for UnbondTx")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebondTxSignable(t *testing.T) {
|
||||
rebondTx := &RebondTx{
|
||||
Address: []byte("address1"),
|
||||
Height: 111,
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, rebondTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[19,{"address":"6164647265737331","height":111}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Got unexpected sign string for RebondTx")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionsTxSignable(t *testing.T) {
|
||||
permsTx := &PermissionsTx{
|
||||
Input: &TxInput{
|
||||
Address: []byte("input1"),
|
||||
Amount: 12345,
|
||||
Sequence: 250,
|
||||
},
|
||||
PermArgs: &ptypes.SetBaseArgs{
|
||||
Address: []byte("address1"),
|
||||
Permission: 1,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
signBytes := acm.SignBytes(chainID, permsTx)
|
||||
signStr := string(signBytes)
|
||||
expected := Fmt(`{"chain_id":"%s","tx":[32,{"args":"[2,{"address":"6164647265737331","permission":1,"value":true}]","input":{"address":"696E70757431","amount":12345,"sequence":250}}]}`,
|
||||
config.GetString("chain_id"))
|
||||
if signStr != expected {
|
||||
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)
|
||||
}
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
)
|
||||
|
||||
type AccountGetter interface {
|
||||
GetAccount(addr []byte) *acm.Account
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// SendTx interface for adding inputs/outputs and adding signatures
|
||||
|
||||
func NewSendTx() *SendTx {
|
||||
return &SendTx{
|
||||
Inputs: []*TxInput{},
|
||||
Outputs: []*TxOutput{},
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *SendTx) AddInput(st AccountGetter, pubkey acm.PubKey, amt int64) error {
|
||||
addr := pubkey.Address()
|
||||
acc := st.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return fmt.Errorf("Invalid address %X from pubkey %X", addr, pubkey)
|
||||
}
|
||||
return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1)
|
||||
}
|
||||
|
||||
func (tx *SendTx) AddInputWithNonce(pubkey acm.PubKey, amt int64, nonce int) error {
|
||||
addr := pubkey.Address()
|
||||
tx.Inputs = append(tx.Inputs, &TxInput{
|
||||
Address: addr,
|
||||
Amount: amt,
|
||||
Sequence: nonce,
|
||||
Signature: acm.SignatureEd25519{},
|
||||
PubKey: pubkey,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *SendTx) AddOutput(addr []byte, amt int64) error {
|
||||
tx.Outputs = append(tx.Outputs, &TxOutput{
|
||||
Address: addr,
|
||||
Amount: amt,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *SendTx) SignInput(chainID string, i int, privAccount *acm.PrivAccount) error {
|
||||
if i >= len(tx.Inputs) {
|
||||
return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs))
|
||||
}
|
||||
tx.Inputs[i].PubKey = privAccount.PubKey
|
||||
tx.Inputs[i].Signature = privAccount.Sign(chainID, tx)
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// CallTx interface for creating tx
|
||||
|
||||
func NewCallTx(st AccountGetter, from acm.PubKey, to, data []byte, amt, gasLimit, fee int64) (*CallTx, error) {
|
||||
addr := from.Address()
|
||||
acc := st.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from)
|
||||
}
|
||||
|
||||
nonce := acc.Sequence + 1
|
||||
return NewCallTxWithNonce(from, to, data, amt, gasLimit, fee, nonce), nil
|
||||
}
|
||||
|
||||
func NewCallTxWithNonce(from acm.PubKey, to, data []byte, amt, gasLimit, fee int64, nonce int) *CallTx {
|
||||
addr := from.Address()
|
||||
input := &TxInput{
|
||||
Address: addr,
|
||||
Amount: amt,
|
||||
Sequence: nonce,
|
||||
Signature: acm.SignatureEd25519{},
|
||||
PubKey: from,
|
||||
}
|
||||
|
||||
return &CallTx{
|
||||
Input: input,
|
||||
Address: to,
|
||||
GasLimit: gasLimit,
|
||||
Fee: fee,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *CallTx) Sign(chainID string, privAccount *acm.PrivAccount) {
|
||||
tx.Input.PubKey = privAccount.PubKey
|
||||
tx.Input.Signature = privAccount.Sign(chainID, tx)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// NameTx interface for creating tx
|
||||
|
||||
func NewNameTx(st AccountGetter, from acm.PubKey, name, data string, amt, fee int64) (*NameTx, error) {
|
||||
addr := from.Address()
|
||||
acc := st.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from)
|
||||
}
|
||||
|
||||
nonce := acc.Sequence + 1
|
||||
return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil
|
||||
}
|
||||
|
||||
func NewNameTxWithNonce(from acm.PubKey, name, data string, amt, fee int64, nonce int) *NameTx {
|
||||
addr := from.Address()
|
||||
input := &TxInput{
|
||||
Address: addr,
|
||||
Amount: amt,
|
||||
Sequence: nonce,
|
||||
Signature: acm.SignatureEd25519{},
|
||||
PubKey: from,
|
||||
}
|
||||
|
||||
return &NameTx{
|
||||
Input: input,
|
||||
Name: name,
|
||||
Data: data,
|
||||
Fee: fee,
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *NameTx) Sign(chainID string, privAccount *acm.PrivAccount) {
|
||||
tx.Input.PubKey = privAccount.PubKey
|
||||
tx.Input.Signature = privAccount.Sign(chainID, tx)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BondTx interface for adding inputs/outputs and adding signatures
|
||||
|
||||
func NewBondTx(pubkey acm.PubKey) (*BondTx, error) {
|
||||
pubkeyEd, ok := pubkey.(acm.PubKeyEd25519)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Pubkey must be ed25519")
|
||||
}
|
||||
return &BondTx{
|
||||
PubKey: pubkeyEd,
|
||||
Inputs: []*TxInput{},
|
||||
UnbondTo: []*TxOutput{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tx *BondTx) AddInput(st AccountGetter, pubkey acm.PubKey, amt int64) error {
|
||||
addr := pubkey.Address()
|
||||
acc := st.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return fmt.Errorf("Invalid address %X from pubkey %X", addr, pubkey)
|
||||
}
|
||||
return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1)
|
||||
}
|
||||
|
||||
func (tx *BondTx) AddInputWithNonce(pubkey acm.PubKey, amt int64, nonce int) error {
|
||||
addr := pubkey.Address()
|
||||
tx.Inputs = append(tx.Inputs, &TxInput{
|
||||
Address: addr,
|
||||
Amount: amt,
|
||||
Sequence: nonce,
|
||||
Signature: acm.SignatureEd25519{},
|
||||
PubKey: pubkey,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *BondTx) AddOutput(addr []byte, amt int64) error {
|
||||
tx.UnbondTo = append(tx.UnbondTo, &TxOutput{
|
||||
Address: addr,
|
||||
Amount: amt,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *BondTx) SignBond(chainID string, privAccount *acm.PrivAccount) error {
|
||||
sig := privAccount.Sign(chainID, tx)
|
||||
sigEd, ok := sig.(acm.SignatureEd25519)
|
||||
if !ok {
|
||||
return fmt.Errorf("Bond signer must be ED25519")
|
||||
}
|
||||
tx.Signature = sigEd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *BondTx) SignInput(chainID string, i int, privAccount *acm.PrivAccount) error {
|
||||
if i >= len(tx.Inputs) {
|
||||
return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs))
|
||||
}
|
||||
tx.Inputs[i].PubKey = privAccount.PubKey
|
||||
tx.Inputs[i].Signature = privAccount.Sign(chainID, tx)
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// UnbondTx interface for creating tx
|
||||
|
||||
func NewUnbondTx(addr []byte, height int) *UnbondTx {
|
||||
return &UnbondTx{
|
||||
Address: addr,
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *UnbondTx) Sign(chainID string, privAccount *acm.PrivAccount) {
|
||||
tx.Signature = privAccount.Sign(chainID, tx).(acm.SignatureEd25519)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// RebondTx interface for creating tx
|
||||
|
||||
func NewRebondTx(addr []byte, height int) *RebondTx {
|
||||
return &RebondTx{
|
||||
Address: addr,
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *RebondTx) Sign(chainID string, privAccount *acm.PrivAccount) {
|
||||
tx.Signature = privAccount.Sign(chainID, tx).(acm.SignatureEd25519)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// PermissionsTx interface for creating tx
|
||||
|
||||
func NewPermissionsTx(st AccountGetter, from acm.PubKey, args ptypes.PermArgs) (*PermissionsTx, error) {
|
||||
addr := from.Address()
|
||||
acc := st.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from)
|
||||
}
|
||||
|
||||
nonce := acc.Sequence + 1
|
||||
return NewPermissionsTxWithNonce(from, args, nonce), nil
|
||||
}
|
||||
|
||||
func NewPermissionsTxWithNonce(from acm.PubKey, args ptypes.PermArgs, nonce int) *PermissionsTx {
|
||||
addr := from.Address()
|
||||
input := &TxInput{
|
||||
Address: addr,
|
||||
Amount: 1, // NOTE: amounts can't be 0 ...
|
||||
Sequence: nonce,
|
||||
Signature: acm.SignatureEd25519{},
|
||||
PubKey: from,
|
||||
}
|
||||
|
||||
return &PermissionsTx{
|
||||
Input: input,
|
||||
PermArgs: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *PermissionsTx) Sign(chainID string, privAccount *acm.PrivAccount) {
|
||||
tx.Input.PubKey = privAccount.PubKey
|
||||
tx.Input.Signature = privAccount.Sign(chainID, tx)
|
||||
}
|
@ -5,54 +5,20 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
// Persistent (mostly) static data for each Validator
|
||||
type ValidatorInfo struct {
|
||||
Address []byte `json:"address"`
|
||||
PubKey acm.PubKeyEd25519 `json:"pub_key"`
|
||||
UnbondTo []*TxOutput `json:"unbond_to"`
|
||||
FirstBondHeight int `json:"first_bond_height"`
|
||||
FirstBondAmount int64 `json:"first_bond_amount"`
|
||||
DestroyedHeight int `json:"destroyed_height"` // If destroyed
|
||||
DestroyedAmount int64 `json:"destroyed_amount"` // If destroyed
|
||||
ReleasedHeight int `json:"released_height"` // If released
|
||||
}
|
||||
|
||||
func (valInfo *ValidatorInfo) Copy() *ValidatorInfo {
|
||||
valInfoCopy := *valInfo
|
||||
return &valInfoCopy
|
||||
}
|
||||
|
||||
func ValidatorInfoEncoder(o interface{}, w io.Writer, n *int64, err *error) {
|
||||
wire.WriteBinary(o.(*ValidatorInfo), w, n, err)
|
||||
}
|
||||
|
||||
func ValidatorInfoDecoder(r io.Reader, n *int64, err *error) interface{} {
|
||||
return wire.ReadBinary(&ValidatorInfo{}, r, n, err)
|
||||
}
|
||||
|
||||
var ValidatorInfoCodec = wire.Codec{
|
||||
Encode: ValidatorInfoEncoder,
|
||||
Decode: ValidatorInfoDecoder,
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Volatile state for each Validator
|
||||
// Also persisted with the state, but fields change
|
||||
// every height|round so they don't go in merkle.Tree
|
||||
type Validator struct {
|
||||
Address []byte `json:"address"`
|
||||
PubKey acm.PubKeyEd25519 `json:"pub_key"`
|
||||
BondHeight int `json:"bond_height"`
|
||||
UnbondHeight int `json:"unbond_height"`
|
||||
LastCommitHeight int `json:"last_commit_height"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
Accum int64 `json:"accum"`
|
||||
Address []byte `json:"address"`
|
||||
PubKey crypto.PubKeyEd25519 `json:"pub_key"`
|
||||
LastCommitHeight int `json:"last_commit_height"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
Accum int64 `json:"accum"`
|
||||
}
|
||||
|
||||
// Creates a new copy of the validator so we can mutate accum.
|
||||
@ -90,9 +56,7 @@ func (v *Validator) String() string {
|
||||
return fmt.Sprintf("Validator{%X %v %v-%v-%v VP:%v A:%v}",
|
||||
v.Address,
|
||||
v.PubKey,
|
||||
v.BondHeight,
|
||||
v.LastCommitHeight,
|
||||
v.UnbondHeight,
|
||||
v.VotingPower,
|
||||
v.Accum)
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-merkle"
|
||||
)
|
||||
@ -230,7 +229,7 @@ func (valSet *ValidatorSet) VerifyValidation(chainID string,
|
||||
}
|
||||
_, val := valSet.GetByIndex(idx)
|
||||
// Validate signature
|
||||
precommitSignBytes := account.SignBytes(chainID, precommit)
|
||||
precommitSignBytes := SignBytes(chainID, precommit)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return fmt.Errorf("Invalid validation -- invalid signature: %v", precommit)
|
||||
}
|
||||
|
@ -1,25 +1,24 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-crypto"
|
||||
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func randPubKey() account.PubKeyEd25519 {
|
||||
func randPubKey() crypto.PubKeyEd25519 {
|
||||
var pubKey [32]byte
|
||||
copy(pubKey[:], RandBytes(32))
|
||||
return account.PubKeyEd25519(pubKey)
|
||||
return crypto.PubKeyEd25519(pubKey)
|
||||
}
|
||||
|
||||
func randValidator_() *Validator {
|
||||
return &Validator{
|
||||
Address: RandBytes(20),
|
||||
PubKey: randPubKey(),
|
||||
BondHeight: RandInt(),
|
||||
VotingPower: RandInt64(),
|
||||
Accum: RandInt64(),
|
||||
}
|
||||
@ -53,21 +52,18 @@ func TestProposerSelection(t *testing.T) {
|
||||
&Validator{
|
||||
Address: []byte("foo"),
|
||||
PubKey: randPubKey(),
|
||||
BondHeight: RandInt(),
|
||||
VotingPower: 1000,
|
||||
Accum: 0,
|
||||
},
|
||||
&Validator{
|
||||
Address: []byte("bar"),
|
||||
PubKey: randPubKey(),
|
||||
BondHeight: RandInt(),
|
||||
VotingPower: 300,
|
||||
Accum: 0,
|
||||
},
|
||||
&Validator{
|
||||
Address: []byte("baz"),
|
||||
PubKey: randPubKey(),
|
||||
BondHeight: RandInt(),
|
||||
VotingPower: 330,
|
||||
Accum: 0,
|
||||
},
|
||||
@ -88,10 +84,11 @@ func BenchmarkValidatorSetCopy(b *testing.B) {
|
||||
b.StopTimer()
|
||||
vset := NewValidatorSet([]*Validator{})
|
||||
for i := 0; i < 1000; i++ {
|
||||
privAccount := account.GenPrivAccount()
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey().(crypto.PubKeyEd25519)
|
||||
val := &Validator{
|
||||
Address: privAccount.Address,
|
||||
PubKey: privAccount.PubKey.(account.PubKeyEd25519),
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
}
|
||||
if !vset.Add(val) {
|
||||
panic("Failed to add validator")
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/go-crypto"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
@ -33,7 +33,7 @@ type Vote struct {
|
||||
Type byte `json:"type"`
|
||||
BlockHash []byte `json:"block_hash"` // empty if vote is nil.
|
||||
BlockPartsHeader PartSetHeader `json:"block_parts_header"` // zero if vote is nil.
|
||||
Signature acm.SignatureEd25519 `json:"signature"`
|
||||
Signature crypto.SignatureEd25519 `json:"signature"`
|
||||
}
|
||||
|
||||
// Types of votes
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
)
|
||||
@ -130,7 +129,7 @@ func (voteSet *VoteSet) addVote(val *Validator, valIndex int, vote *Vote) (bool,
|
||||
}
|
||||
|
||||
// Check signature.
|
||||
if !val.PubKey.VerifyBytes(acm.SignBytes(config.GetString("chain_id"), vote), vote.Signature) {
|
||||
if !val.PubKey.VerifyBytes(SignBytes(config.GetString("chain_id"), vote), vote.Signature) {
|
||||
// Bad signature.
|
||||
return false, 0, ErrVoteInvalidSignature
|
||||
}
|
||||
@ -305,32 +304,20 @@ func (voteSet *VoteSet) MakeValidation() *Validation {
|
||||
//--------------------------------------------------------------------------------
|
||||
// For testing...
|
||||
|
||||
func RandValidator(randBonded bool, minBonded int64) (*ValidatorInfo, *Validator, *PrivValidator) {
|
||||
func RandValidator(randPower bool, minPower int64) (*Validator, *PrivValidator) {
|
||||
privVal := GenPrivValidator()
|
||||
_, tempFilePath := Tempfile("priv_validator_")
|
||||
privVal.SetFile(tempFilePath)
|
||||
bonded := minBonded
|
||||
if randBonded {
|
||||
bonded += int64(RandUint32())
|
||||
}
|
||||
valInfo := &ValidatorInfo{
|
||||
Address: privVal.Address,
|
||||
PubKey: privVal.PubKey,
|
||||
UnbondTo: []*TxOutput{&TxOutput{
|
||||
Amount: bonded,
|
||||
Address: privVal.Address,
|
||||
}},
|
||||
FirstBondHeight: 0,
|
||||
FirstBondAmount: bonded,
|
||||
votePower := minPower
|
||||
if randPower {
|
||||
votePower += int64(RandUint32())
|
||||
}
|
||||
val := &Validator{
|
||||
Address: valInfo.Address,
|
||||
PubKey: valInfo.PubKey,
|
||||
BondHeight: 0,
|
||||
UnbondHeight: 0,
|
||||
Address: privVal.Address,
|
||||
PubKey: privVal.PubKey,
|
||||
LastCommitHeight: 0,
|
||||
VotingPower: valInfo.FirstBondAmount,
|
||||
VotingPower: votePower,
|
||||
Accum: 0,
|
||||
}
|
||||
return valInfo, val, privVal
|
||||
return val, privVal
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ func randVoteSet(height int, round int, type_ byte, numValidators int, votingPow
|
||||
vals := make([]*Validator, numValidators)
|
||||
privValidators := make([]*PrivValidator, numValidators)
|
||||
for i := 0; i < numValidators; i++ {
|
||||
_, val, privValidator := RandValidator(false, votingPower)
|
||||
val, privValidator := RandValidator(false, votingPower)
|
||||
vals[i] = val
|
||||
privValidators[i] = privValidator
|
||||
}
|
||||
|
26
vm/common.go
26
vm/common.go
@ -1,26 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// To256
|
||||
//
|
||||
// "cast" the big int to a 256 big int (i.e., limit to)
|
||||
var tt256 = new(big.Int).Lsh(big.NewInt(1), 256)
|
||||
var tt256m1 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
|
||||
var tt255 = new(big.Int).Lsh(big.NewInt(1), 255)
|
||||
|
||||
func U256(x *big.Int) *big.Int {
|
||||
x.And(x, tt256m1)
|
||||
return x
|
||||
}
|
||||
|
||||
func S256(x *big.Int) *big.Int {
|
||||
if x.Cmp(tt255) < 0 {
|
||||
return x
|
||||
} else {
|
||||
// We don't want to modify x, ever
|
||||
return new(big.Int).Sub(x, tt256)
|
||||
}
|
||||
}
|
18
vm/gas.go
18
vm/gas.go
@ -1,18 +0,0 @@
|
||||
package vm
|
||||
|
||||
const (
|
||||
GasSha3 int64 = 1
|
||||
GasGetAccount int64 = 1
|
||||
GasStorageUpdate int64 = 1
|
||||
|
||||
GasBaseOp int64 = 0 // TODO: make this 1
|
||||
GasStackOp int64 = 1
|
||||
|
||||
GasEcRecover int64 = 1
|
||||
GasSha256Word int64 = 1
|
||||
GasSha256Base int64 = 1
|
||||
GasRipemd160Word int64 = 1
|
||||
GasRipemd160Base int64 = 1
|
||||
GasIdentityWord int64 = 1
|
||||
GasIdentityBase int64 = 1
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-logger"
|
||||
)
|
||||
|
||||
var log = logger.New("module", "vm")
|
105
vm/native.go
105
vm/native.go
@ -1,105 +0,0 @@
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"code.google.com/p/go.crypto/ripemd160"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
var registeredNativeContracts = make(map[Word256]NativeContract)
|
||||
|
||||
func RegisteredNativeContract(addr Word256) bool {
|
||||
_, ok := registeredNativeContracts[addr]
|
||||
return ok
|
||||
}
|
||||
|
||||
func RegisterNativeContract(addr Word256, fn NativeContract) bool {
|
||||
_, exists := registeredNativeContracts[addr]
|
||||
if exists {
|
||||
return false
|
||||
}
|
||||
registeredNativeContracts[addr] = fn
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerNativeContracts()
|
||||
registerSNativeContracts()
|
||||
}
|
||||
|
||||
func registerNativeContracts() {
|
||||
// registeredNativeContracts[Int64ToWord256(1)] = ecrecoverFunc
|
||||
registeredNativeContracts[Int64ToWord256(2)] = sha256Func
|
||||
registeredNativeContracts[Int64ToWord256(3)] = ripemd160Func
|
||||
registeredNativeContracts[Int64ToWord256(4)] = identityFunc
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error)
|
||||
|
||||
/* Removed due to C dependency
|
||||
func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
|
||||
// Deduct gas
|
||||
gasRequired := GasEcRecover
|
||||
if *gas < gasRequired {
|
||||
return nil, ErrInsufficientGas
|
||||
} else {
|
||||
*gas -= gasRequired
|
||||
}
|
||||
// Recover
|
||||
hash := input[:32]
|
||||
v := byte(input[32] - 27) // ignore input[33:64], v is small.
|
||||
sig := append(input[64:], v)
|
||||
|
||||
recovered, err := secp256k1.RecoverPubkey(hash, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashed := sha3.Sha3(recovered[1:])
|
||||
return LeftPadBytes(hashed, 32), nil
|
||||
}
|
||||
*/
|
||||
|
||||
func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
|
||||
// Deduct gas
|
||||
gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base
|
||||
if *gas < gasRequired {
|
||||
return nil, ErrInsufficientGas
|
||||
} else {
|
||||
*gas -= gasRequired
|
||||
}
|
||||
// Hash
|
||||
hasher := sha256.New()
|
||||
// CONTRACT: this does not err
|
||||
hasher.Write(input)
|
||||
return hasher.Sum(nil), nil
|
||||
}
|
||||
|
||||
func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
|
||||
// Deduct gas
|
||||
gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base
|
||||
if *gas < gasRequired {
|
||||
return nil, ErrInsufficientGas
|
||||
} else {
|
||||
*gas -= gasRequired
|
||||
}
|
||||
// Hash
|
||||
hasher := ripemd160.New()
|
||||
// CONTRACT: this does not err
|
||||
hasher.Write(input)
|
||||
return LeftPadBytes(hasher.Sum(nil), 32), nil
|
||||
}
|
||||
|
||||
func identityFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
|
||||
// Deduct gas
|
||||
gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase
|
||||
if *gas < gasRequired {
|
||||
return nil, ErrInsufficientGas
|
||||
} else {
|
||||
*gas -= gasRequired
|
||||
}
|
||||
// Return identity
|
||||
return input, nil
|
||||
}
|
354
vm/opcodes.go
354
vm/opcodes.go
@ -1,354 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
type OpCode byte
|
||||
|
||||
const (
|
||||
// Op codes
|
||||
// 0x0 range - arithmetic ops
|
||||
STOP OpCode = iota
|
||||
ADD
|
||||
MUL
|
||||
SUB
|
||||
DIV
|
||||
SDIV
|
||||
MOD
|
||||
SMOD
|
||||
ADDMOD
|
||||
MULMOD
|
||||
EXP
|
||||
SIGNEXTEND
|
||||
)
|
||||
|
||||
const (
|
||||
LT OpCode = iota + 0x10
|
||||
GT
|
||||
SLT
|
||||
SGT
|
||||
EQ
|
||||
ISZERO
|
||||
AND
|
||||
OR
|
||||
XOR
|
||||
NOT
|
||||
BYTE
|
||||
|
||||
SHA3 = 0x20
|
||||
)
|
||||
|
||||
const (
|
||||
// 0x30 range - closure state
|
||||
ADDRESS OpCode = 0x30 + iota
|
||||
BALANCE
|
||||
ORIGIN
|
||||
CALLER
|
||||
CALLVALUE
|
||||
CALLDATALOAD
|
||||
CALLDATASIZE
|
||||
CALLDATACOPY
|
||||
CODESIZE
|
||||
CODECOPY
|
||||
GASPRICE_DEPRECATED
|
||||
EXTCODESIZE
|
||||
EXTCODECOPY
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// 0x40 range - block operations
|
||||
BLOCKHASH OpCode = 0x40 + iota
|
||||
COINBASE
|
||||
TIMESTAMP
|
||||
BLOCKHEIGHT
|
||||
DIFFICULTY_DEPRECATED
|
||||
GASLIMIT
|
||||
)
|
||||
|
||||
const (
|
||||
// 0x50 range - 'storage' and execution
|
||||
POP OpCode = 0x50 + iota
|
||||
MLOAD
|
||||
MSTORE
|
||||
MSTORE8
|
||||
SLOAD
|
||||
SSTORE
|
||||
JUMP
|
||||
JUMPI
|
||||
PC
|
||||
MSIZE
|
||||
GAS
|
||||
JUMPDEST
|
||||
)
|
||||
|
||||
const (
|
||||
// 0x60 range
|
||||
PUSH1 OpCode = 0x60 + iota
|
||||
PUSH2
|
||||
PUSH3
|
||||
PUSH4
|
||||
PUSH5
|
||||
PUSH6
|
||||
PUSH7
|
||||
PUSH8
|
||||
PUSH9
|
||||
PUSH10
|
||||
PUSH11
|
||||
PUSH12
|
||||
PUSH13
|
||||
PUSH14
|
||||
PUSH15
|
||||
PUSH16
|
||||
PUSH17
|
||||
PUSH18
|
||||
PUSH19
|
||||
PUSH20
|
||||
PUSH21
|
||||
PUSH22
|
||||
PUSH23
|
||||
PUSH24
|
||||
PUSH25
|
||||
PUSH26
|
||||
PUSH27
|
||||
PUSH28
|
||||
PUSH29
|
||||
PUSH30
|
||||
PUSH31
|
||||
PUSH32
|
||||
DUP1
|
||||
DUP2
|
||||
DUP3
|
||||
DUP4
|
||||
DUP5
|
||||
DUP6
|
||||
DUP7
|
||||
DUP8
|
||||
DUP9
|
||||
DUP10
|
||||
DUP11
|
||||
DUP12
|
||||
DUP13
|
||||
DUP14
|
||||
DUP15
|
||||
DUP16
|
||||
SWAP1
|
||||
SWAP2
|
||||
SWAP3
|
||||
SWAP4
|
||||
SWAP5
|
||||
SWAP6
|
||||
SWAP7
|
||||
SWAP8
|
||||
SWAP9
|
||||
SWAP10
|
||||
SWAP11
|
||||
SWAP12
|
||||
SWAP13
|
||||
SWAP14
|
||||
SWAP15
|
||||
SWAP16
|
||||
)
|
||||
|
||||
const (
|
||||
LOG0 OpCode = 0xa0 + iota
|
||||
LOG1
|
||||
LOG2
|
||||
LOG3
|
||||
LOG4
|
||||
)
|
||||
|
||||
const (
|
||||
// 0xf0 range - closures
|
||||
CREATE OpCode = 0xf0 + iota
|
||||
CALL
|
||||
CALLCODE
|
||||
RETURN
|
||||
|
||||
// 0x70 range - other
|
||||
SUICIDE = 0xff
|
||||
)
|
||||
|
||||
// Since the opcodes aren't all in order we can't use a regular slice
|
||||
var opCodeToString = map[OpCode]string{
|
||||
// 0x0 range - arithmetic ops
|
||||
STOP: "STOP",
|
||||
ADD: "ADD",
|
||||
MUL: "MUL",
|
||||
SUB: "SUB",
|
||||
DIV: "DIV",
|
||||
SDIV: "SDIV",
|
||||
MOD: "MOD",
|
||||
SMOD: "SMOD",
|
||||
EXP: "EXP",
|
||||
NOT: "NOT",
|
||||
LT: "LT",
|
||||
GT: "GT",
|
||||
SLT: "SLT",
|
||||
SGT: "SGT",
|
||||
EQ: "EQ",
|
||||
ISZERO: "ISZERO",
|
||||
SIGNEXTEND: "SIGNEXTEND",
|
||||
|
||||
// 0x10 range - bit ops
|
||||
AND: "AND",
|
||||
OR: "OR",
|
||||
XOR: "XOR",
|
||||
BYTE: "BYTE",
|
||||
ADDMOD: "ADDMOD",
|
||||
MULMOD: "MULMOD",
|
||||
|
||||
// 0x20 range - crypto
|
||||
SHA3: "SHA3",
|
||||
|
||||
// 0x30 range - closure state
|
||||
ADDRESS: "ADDRESS",
|
||||
BALANCE: "BALANCE",
|
||||
ORIGIN: "ORIGIN",
|
||||
CALLER: "CALLER",
|
||||
CALLVALUE: "CALLVALUE",
|
||||
CALLDATALOAD: "CALLDATALOAD",
|
||||
CALLDATASIZE: "CALLDATASIZE",
|
||||
CALLDATACOPY: "CALLDATACOPY",
|
||||
CODESIZE: "CODESIZE",
|
||||
CODECOPY: "CODECOPY",
|
||||
GASPRICE_DEPRECATED: "TXGASPRICE_DEPRECATED",
|
||||
|
||||
// 0x40 range - block operations
|
||||
BLOCKHASH: "BLOCKHASH",
|
||||
COINBASE: "COINBASE",
|
||||
TIMESTAMP: "TIMESTAMP",
|
||||
BLOCKHEIGHT: "BLOCKHEIGHT",
|
||||
DIFFICULTY_DEPRECATED: "DIFFICULTY_DEPRECATED",
|
||||
GASLIMIT: "GASLIMIT",
|
||||
EXTCODESIZE: "EXTCODESIZE",
|
||||
EXTCODECOPY: "EXTCODECOPY",
|
||||
|
||||
// 0x50 range - 'storage' and execution
|
||||
POP: "POP",
|
||||
//DUP: "DUP",
|
||||
//SWAP: "SWAP",
|
||||
MLOAD: "MLOAD",
|
||||
MSTORE: "MSTORE",
|
||||
MSTORE8: "MSTORE8",
|
||||
SLOAD: "SLOAD",
|
||||
SSTORE: "SSTORE",
|
||||
JUMP: "JUMP",
|
||||
JUMPI: "JUMPI",
|
||||
PC: "PC",
|
||||
MSIZE: "MSIZE",
|
||||
GAS: "GAS",
|
||||
JUMPDEST: "JUMPDEST",
|
||||
|
||||
// 0x60 range - push
|
||||
PUSH1: "PUSH1",
|
||||
PUSH2: "PUSH2",
|
||||
PUSH3: "PUSH3",
|
||||
PUSH4: "PUSH4",
|
||||
PUSH5: "PUSH5",
|
||||
PUSH6: "PUSH6",
|
||||
PUSH7: "PUSH7",
|
||||
PUSH8: "PUSH8",
|
||||
PUSH9: "PUSH9",
|
||||
PUSH10: "PUSH10",
|
||||
PUSH11: "PUSH11",
|
||||
PUSH12: "PUSH12",
|
||||
PUSH13: "PUSH13",
|
||||
PUSH14: "PUSH14",
|
||||
PUSH15: "PUSH15",
|
||||
PUSH16: "PUSH16",
|
||||
PUSH17: "PUSH17",
|
||||
PUSH18: "PUSH18",
|
||||
PUSH19: "PUSH19",
|
||||
PUSH20: "PUSH20",
|
||||
PUSH21: "PUSH21",
|
||||
PUSH22: "PUSH22",
|
||||
PUSH23: "PUSH23",
|
||||
PUSH24: "PUSH24",
|
||||
PUSH25: "PUSH25",
|
||||
PUSH26: "PUSH26",
|
||||
PUSH27: "PUSH27",
|
||||
PUSH28: "PUSH28",
|
||||
PUSH29: "PUSH29",
|
||||
PUSH30: "PUSH30",
|
||||
PUSH31: "PUSH31",
|
||||
PUSH32: "PUSH32",
|
||||
|
||||
DUP1: "DUP1",
|
||||
DUP2: "DUP2",
|
||||
DUP3: "DUP3",
|
||||
DUP4: "DUP4",
|
||||
DUP5: "DUP5",
|
||||
DUP6: "DUP6",
|
||||
DUP7: "DUP7",
|
||||
DUP8: "DUP8",
|
||||
DUP9: "DUP9",
|
||||
DUP10: "DUP10",
|
||||
DUP11: "DUP11",
|
||||
DUP12: "DUP12",
|
||||
DUP13: "DUP13",
|
||||
DUP14: "DUP14",
|
||||
DUP15: "DUP15",
|
||||
DUP16: "DUP16",
|
||||
|
||||
SWAP1: "SWAP1",
|
||||
SWAP2: "SWAP2",
|
||||
SWAP3: "SWAP3",
|
||||
SWAP4: "SWAP4",
|
||||
SWAP5: "SWAP5",
|
||||
SWAP6: "SWAP6",
|
||||
SWAP7: "SWAP7",
|
||||
SWAP8: "SWAP8",
|
||||
SWAP9: "SWAP9",
|
||||
SWAP10: "SWAP10",
|
||||
SWAP11: "SWAP11",
|
||||
SWAP12: "SWAP12",
|
||||
SWAP13: "SWAP13",
|
||||
SWAP14: "SWAP14",
|
||||
SWAP15: "SWAP15",
|
||||
SWAP16: "SWAP16",
|
||||
LOG0: "LOG0",
|
||||
LOG1: "LOG1",
|
||||
LOG2: "LOG2",
|
||||
LOG3: "LOG3",
|
||||
LOG4: "LOG4",
|
||||
|
||||
// 0xf0 range
|
||||
CREATE: "CREATE",
|
||||
CALL: "CALL",
|
||||
RETURN: "RETURN",
|
||||
CALLCODE: "CALLCODE",
|
||||
|
||||
// 0x70 range - other
|
||||
SUICIDE: "SUICIDE",
|
||||
}
|
||||
|
||||
func (o OpCode) String() string {
|
||||
str := opCodeToString[o]
|
||||
if len(str) == 0 {
|
||||
return fmt.Sprintf("Missing opcode 0x%x", int(o))
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func AnalyzeJumpDests(code []byte) (dests *set.Set) {
|
||||
dests = set.New()
|
||||
|
||||
for pc := uint64(0); pc < uint64(len(code)); pc++ {
|
||||
var op OpCode = OpCode(code[pc])
|
||||
switch op {
|
||||
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
|
||||
a := uint64(op) - uint64(PUSH1) + 1
|
||||
|
||||
pc += a
|
||||
case JUMPDEST:
|
||||
dests.Add(pc)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package randentropy
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"github.com/tendermint/tendermint/vm/sha3"
|
||||
"io"
|
||||
)
|
||||
|
||||
var Reader io.Reader = &randEntropy{}
|
||||
|
||||
type randEntropy struct {
|
||||
}
|
||||
|
||||
func (*randEntropy) Read(bytes []byte) (n int, err error) {
|
||||
readBytes := GetEntropyCSPRNG(len(bytes))
|
||||
copy(bytes, readBytes)
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
// TODO: copied from crypto.go , move to sha3 package?
|
||||
func Sha3(data []byte) []byte {
|
||||
d := sha3.NewKeccak256()
|
||||
d.Write(data)
|
||||
|
||||
return d.Sum(nil)
|
||||
}
|
||||
|
||||
func GetEntropyCSPRNG(n int) []byte {
|
||||
mainBuff := make([]byte, n)
|
||||
_, err := io.ReadFull(crand.Reader, mainBuff)
|
||||
if err != nil {
|
||||
panic("reading from crypto/rand failed: " + err.Error())
|
||||
}
|
||||
return mainBuff
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sha3
|
||||
|
||||
// This file implements the core Keccak permutation function necessary for computing SHA3.
|
||||
// This is implemented in a separate file to allow for replacement by an optimized implementation.
|
||||
// Nothing in this package is exported.
|
||||
// For the detailed specification, refer to the Keccak web site (http://keccak.noekeon.org/).
|
||||
|
||||
// rc stores the round constants for use in the ι step.
|
||||
var rc = [...]uint64{
|
||||
0x0000000000000001,
|
||||
0x0000000000008082,
|
||||
0x800000000000808A,
|
||||
0x8000000080008000,
|
||||
0x000000000000808B,
|
||||
0x0000000080000001,
|
||||
0x8000000080008081,
|
||||
0x8000000000008009,
|
||||
0x000000000000008A,
|
||||
0x0000000000000088,
|
||||
0x0000000080008009,
|
||||
0x000000008000000A,
|
||||
0x000000008000808B,
|
||||
0x800000000000008B,
|
||||
0x8000000000008089,
|
||||
0x8000000000008003,
|
||||
0x8000000000008002,
|
||||
0x8000000000000080,
|
||||
0x000000000000800A,
|
||||
0x800000008000000A,
|
||||
0x8000000080008081,
|
||||
0x8000000000008080,
|
||||
0x0000000080000001,
|
||||
0x8000000080008008,
|
||||
}
|
||||
|
||||
// ro_xx represent the rotation offsets for use in the χ step.
|
||||
// Defining them as const instead of in an array allows the compiler to insert constant shifts.
|
||||
const (
|
||||
ro_00 = 0
|
||||
ro_01 = 36
|
||||
ro_02 = 3
|
||||
ro_03 = 41
|
||||
ro_04 = 18
|
||||
ro_05 = 1
|
||||
ro_06 = 44
|
||||
ro_07 = 10
|
||||
ro_08 = 45
|
||||
ro_09 = 2
|
||||
ro_10 = 62
|
||||
ro_11 = 6
|
||||
ro_12 = 43
|
||||
ro_13 = 15
|
||||
ro_14 = 61
|
||||
ro_15 = 28
|
||||
ro_16 = 55
|
||||
ro_17 = 25
|
||||
ro_18 = 21
|
||||
ro_19 = 56
|
||||
ro_20 = 27
|
||||
ro_21 = 20
|
||||
ro_22 = 39
|
||||
ro_23 = 8
|
||||
ro_24 = 14
|
||||
)
|
||||
|
||||
// keccakF computes the complete Keccak-f function consisting of 24 rounds with a different
|
||||
// constant (rc) in each round. This implementation fully unrolls the round function to avoid
|
||||
// inner loops, as well as pre-calculating shift offsets.
|
||||
func (d *digest) keccakF() {
|
||||
for _, roundConstant := range rc {
|
||||
// θ step
|
||||
d.c[0] = d.a[0] ^ d.a[5] ^ d.a[10] ^ d.a[15] ^ d.a[20]
|
||||
d.c[1] = d.a[1] ^ d.a[6] ^ d.a[11] ^ d.a[16] ^ d.a[21]
|
||||
d.c[2] = d.a[2] ^ d.a[7] ^ d.a[12] ^ d.a[17] ^ d.a[22]
|
||||
d.c[3] = d.a[3] ^ d.a[8] ^ d.a[13] ^ d.a[18] ^ d.a[23]
|
||||
d.c[4] = d.a[4] ^ d.a[9] ^ d.a[14] ^ d.a[19] ^ d.a[24]
|
||||
|
||||
d.d[0] = d.c[4] ^ (d.c[1]<<1 ^ d.c[1]>>63)
|
||||
d.d[1] = d.c[0] ^ (d.c[2]<<1 ^ d.c[2]>>63)
|
||||
d.d[2] = d.c[1] ^ (d.c[3]<<1 ^ d.c[3]>>63)
|
||||
d.d[3] = d.c[2] ^ (d.c[4]<<1 ^ d.c[4]>>63)
|
||||
d.d[4] = d.c[3] ^ (d.c[0]<<1 ^ d.c[0]>>63)
|
||||
|
||||
d.a[0] ^= d.d[0]
|
||||
d.a[1] ^= d.d[1]
|
||||
d.a[2] ^= d.d[2]
|
||||
d.a[3] ^= d.d[3]
|
||||
d.a[4] ^= d.d[4]
|
||||
d.a[5] ^= d.d[0]
|
||||
d.a[6] ^= d.d[1]
|
||||
d.a[7] ^= d.d[2]
|
||||
d.a[8] ^= d.d[3]
|
||||
d.a[9] ^= d.d[4]
|
||||
d.a[10] ^= d.d[0]
|
||||
d.a[11] ^= d.d[1]
|
||||
d.a[12] ^= d.d[2]
|
||||
d.a[13] ^= d.d[3]
|
||||
d.a[14] ^= d.d[4]
|
||||
d.a[15] ^= d.d[0]
|
||||
d.a[16] ^= d.d[1]
|
||||
d.a[17] ^= d.d[2]
|
||||
d.a[18] ^= d.d[3]
|
||||
d.a[19] ^= d.d[4]
|
||||
d.a[20] ^= d.d[0]
|
||||
d.a[21] ^= d.d[1]
|
||||
d.a[22] ^= d.d[2]
|
||||
d.a[23] ^= d.d[3]
|
||||
d.a[24] ^= d.d[4]
|
||||
|
||||
// ρ and π steps
|
||||
d.b[0] = d.a[0]
|
||||
d.b[1] = d.a[6]<<ro_06 ^ d.a[6]>>(64-ro_06)
|
||||
d.b[2] = d.a[12]<<ro_12 ^ d.a[12]>>(64-ro_12)
|
||||
d.b[3] = d.a[18]<<ro_18 ^ d.a[18]>>(64-ro_18)
|
||||
d.b[4] = d.a[24]<<ro_24 ^ d.a[24]>>(64-ro_24)
|
||||
d.b[5] = d.a[3]<<ro_15 ^ d.a[3]>>(64-ro_15)
|
||||
d.b[6] = d.a[9]<<ro_21 ^ d.a[9]>>(64-ro_21)
|
||||
d.b[7] = d.a[10]<<ro_02 ^ d.a[10]>>(64-ro_02)
|
||||
d.b[8] = d.a[16]<<ro_08 ^ d.a[16]>>(64-ro_08)
|
||||
d.b[9] = d.a[22]<<ro_14 ^ d.a[22]>>(64-ro_14)
|
||||
d.b[10] = d.a[1]<<ro_05 ^ d.a[1]>>(64-ro_05)
|
||||
d.b[11] = d.a[7]<<ro_11 ^ d.a[7]>>(64-ro_11)
|
||||
d.b[12] = d.a[13]<<ro_17 ^ d.a[13]>>(64-ro_17)
|
||||
d.b[13] = d.a[19]<<ro_23 ^ d.a[19]>>(64-ro_23)
|
||||
d.b[14] = d.a[20]<<ro_04 ^ d.a[20]>>(64-ro_04)
|
||||
d.b[15] = d.a[4]<<ro_20 ^ d.a[4]>>(64-ro_20)
|
||||
d.b[16] = d.a[5]<<ro_01 ^ d.a[5]>>(64-ro_01)
|
||||
d.b[17] = d.a[11]<<ro_07 ^ d.a[11]>>(64-ro_07)
|
||||
d.b[18] = d.a[17]<<ro_13 ^ d.a[17]>>(64-ro_13)
|
||||
d.b[19] = d.a[23]<<ro_19 ^ d.a[23]>>(64-ro_19)
|
||||
d.b[20] = d.a[2]<<ro_10 ^ d.a[2]>>(64-ro_10)
|
||||
d.b[21] = d.a[8]<<ro_16 ^ d.a[8]>>(64-ro_16)
|
||||
d.b[22] = d.a[14]<<ro_22 ^ d.a[14]>>(64-ro_22)
|
||||
d.b[23] = d.a[15]<<ro_03 ^ d.a[15]>>(64-ro_03)
|
||||
d.b[24] = d.a[21]<<ro_09 ^ d.a[21]>>(64-ro_09)
|
||||
|
||||
// χ step
|
||||
d.a[0] = d.b[0] ^ (^d.b[1] & d.b[2])
|
||||
d.a[1] = d.b[1] ^ (^d.b[2] & d.b[3])
|
||||
d.a[2] = d.b[2] ^ (^d.b[3] & d.b[4])
|
||||
d.a[3] = d.b[3] ^ (^d.b[4] & d.b[0])
|
||||
d.a[4] = d.b[4] ^ (^d.b[0] & d.b[1])
|
||||
d.a[5] = d.b[5] ^ (^d.b[6] & d.b[7])
|
||||
d.a[6] = d.b[6] ^ (^d.b[7] & d.b[8])
|
||||
d.a[7] = d.b[7] ^ (^d.b[8] & d.b[9])
|
||||
d.a[8] = d.b[8] ^ (^d.b[9] & d.b[5])
|
||||
d.a[9] = d.b[9] ^ (^d.b[5] & d.b[6])
|
||||
d.a[10] = d.b[10] ^ (^d.b[11] & d.b[12])
|
||||
d.a[11] = d.b[11] ^ (^d.b[12] & d.b[13])
|
||||
d.a[12] = d.b[12] ^ (^d.b[13] & d.b[14])
|
||||
d.a[13] = d.b[13] ^ (^d.b[14] & d.b[10])
|
||||
d.a[14] = d.b[14] ^ (^d.b[10] & d.b[11])
|
||||
d.a[15] = d.b[15] ^ (^d.b[16] & d.b[17])
|
||||
d.a[16] = d.b[16] ^ (^d.b[17] & d.b[18])
|
||||
d.a[17] = d.b[17] ^ (^d.b[18] & d.b[19])
|
||||
d.a[18] = d.b[18] ^ (^d.b[19] & d.b[15])
|
||||
d.a[19] = d.b[19] ^ (^d.b[15] & d.b[16])
|
||||
d.a[20] = d.b[20] ^ (^d.b[21] & d.b[22])
|
||||
d.a[21] = d.b[21] ^ (^d.b[22] & d.b[23])
|
||||
d.a[22] = d.b[22] ^ (^d.b[23] & d.b[24])
|
||||
d.a[23] = d.b[23] ^ (^d.b[24] & d.b[20])
|
||||
d.a[24] = d.b[24] ^ (^d.b[20] & d.b[21])
|
||||
|
||||
// ι step
|
||||
d.a[0] ^= roundConstant
|
||||
}
|
||||
}
|
224
vm/sha3/sha3.go
224
vm/sha3/sha3.go
@ -1,224 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sha3 implements the SHA3 hash algorithm (formerly called Keccak) chosen by NIST in 2012.
|
||||
// This file provides a SHA3 implementation which implements the standard hash.Hash interface.
|
||||
// Writing input data, including padding, and reading output data are computed in this file.
|
||||
// Note that the current implementation can compute the hash of an integral number of bytes only.
|
||||
// This is a consequence of the hash interface in which a buffer of bytes is passed in.
|
||||
// The internals of the Keccak-f function are computed in keccakf.go.
|
||||
// For the detailed specification, refer to the Keccak web site (http://keccak.noekeon.org/).
|
||||
package sha3
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// laneSize is the size in bytes of each "lane" of the internal state of SHA3 (5 * 5 * 8).
|
||||
// Note that changing this size would requires using a type other than uint64 to store each lane.
|
||||
const laneSize = 8
|
||||
|
||||
// sliceSize represents the dimensions of the internal state, a square matrix of
|
||||
// sliceSize ** 2 lanes. This is the size of both the "rows" and "columns" dimensions in the
|
||||
// terminology of the SHA3 specification.
|
||||
const sliceSize = 5
|
||||
|
||||
// numLanes represents the total number of lanes in the state.
|
||||
const numLanes = sliceSize * sliceSize
|
||||
|
||||
// stateSize is the size in bytes of the internal state of SHA3 (5 * 5 * WSize).
|
||||
const stateSize = laneSize * numLanes
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
// Note that capacity, and not outputSize, is the critical security parameter, as SHA3 can output
|
||||
// an arbitrary number of bytes for any given capacity. The Keccak proposal recommends that
|
||||
// capacity = 2*outputSize to ensure that finding a collision of size outputSize requires
|
||||
// O(2^{outputSize/2}) computations (the birthday lower bound). Future standards may modify the
|
||||
// capacity/outputSize ratio to allow for more output with lower cryptographic security.
|
||||
type digest struct {
|
||||
a [numLanes]uint64 // main state of the hash
|
||||
b [numLanes]uint64 // intermediate states
|
||||
c [sliceSize]uint64 // intermediate states
|
||||
d [sliceSize]uint64 // intermediate states
|
||||
outputSize int // desired output size in bytes
|
||||
capacity int // number of bytes to leave untouched during squeeze/absorb
|
||||
absorbed int // number of bytes absorbed thus far
|
||||
}
|
||||
|
||||
// minInt returns the lesser of two integer arguments, to simplify the absorption routine.
|
||||
func minInt(v1, v2 int) int {
|
||||
if v1 <= v2 {
|
||||
return v1
|
||||
}
|
||||
return v2
|
||||
}
|
||||
|
||||
// rate returns the number of bytes of the internal state which can be absorbed or squeezed
|
||||
// in between calls to the permutation function.
|
||||
func (d *digest) rate() int {
|
||||
return stateSize - d.capacity
|
||||
}
|
||||
|
||||
// Reset clears the internal state by zeroing bytes in the state buffer.
|
||||
// This can be skipped for a newly-created hash state; the default zero-allocated state is correct.
|
||||
func (d *digest) Reset() {
|
||||
d.absorbed = 0
|
||||
for i := range d.a {
|
||||
d.a[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// BlockSize, required by the hash.Hash interface, does not have a standard intepretation
|
||||
// for a sponge-based construction like SHA3. We return the data rate: the number of bytes which
|
||||
// can be absorbed per invocation of the permutation function. For Merkle-Damgård based hashes
|
||||
// (ie SHA1, SHA2, MD5) the output size of the internal compression function is returned.
|
||||
// We consider this to be roughly equivalent because it represents the number of bytes of output
|
||||
// produced per cryptographic operation.
|
||||
func (d *digest) BlockSize() int { return d.rate() }
|
||||
|
||||
// Size returns the output size of the hash function in bytes.
|
||||
func (d *digest) Size() int {
|
||||
return d.outputSize
|
||||
}
|
||||
|
||||
// unalignedAbsorb is a helper function for Write, which absorbs data that isn't aligned with an
|
||||
// 8-byte lane. This requires shifting the individual bytes into position in a uint64.
|
||||
func (d *digest) unalignedAbsorb(p []byte) {
|
||||
var t uint64
|
||||
for i := len(p) - 1; i >= 0; i-- {
|
||||
t <<= 8
|
||||
t |= uint64(p[i])
|
||||
}
|
||||
offset := (d.absorbed) % d.rate()
|
||||
t <<= 8 * uint(offset%laneSize)
|
||||
d.a[offset/laneSize] ^= t
|
||||
d.absorbed += len(p)
|
||||
}
|
||||
|
||||
// Write "absorbs" bytes into the state of the SHA3 hash, updating as needed when the sponge
|
||||
// "fills up" with rate() bytes. Since lanes are stored internally as type uint64, this requires
|
||||
// converting the incoming bytes into uint64s using a little endian interpretation. This
|
||||
// implementation is optimized for large, aligned writes of multiples of 8 bytes (laneSize).
|
||||
// Non-aligned or uneven numbers of bytes require shifting and are slower.
|
||||
func (d *digest) Write(p []byte) (int, error) {
|
||||
// An initial offset is needed if the we aren't absorbing to the first lane initially.
|
||||
offset := d.absorbed % d.rate()
|
||||
toWrite := len(p)
|
||||
|
||||
// The first lane may need to absorb unaligned and/or incomplete data.
|
||||
if (offset%laneSize != 0 || len(p) < 8) && len(p) > 0 {
|
||||
toAbsorb := minInt(laneSize-(offset%laneSize), len(p))
|
||||
d.unalignedAbsorb(p[:toAbsorb])
|
||||
p = p[toAbsorb:]
|
||||
offset = (d.absorbed) % d.rate()
|
||||
|
||||
// For every rate() bytes absorbed, the state must be permuted via the F Function.
|
||||
if (d.absorbed)%d.rate() == 0 {
|
||||
d.keccakF()
|
||||
}
|
||||
}
|
||||
|
||||
// This loop should absorb the bulk of the data into full, aligned lanes.
|
||||
// It will call the update function as necessary.
|
||||
for len(p) > 7 {
|
||||
firstLane := offset / laneSize
|
||||
lastLane := minInt(d.rate()/laneSize, firstLane+len(p)/laneSize)
|
||||
|
||||
// This inner loop absorbs input bytes into the state in groups of 8, converted to uint64s.
|
||||
for lane := firstLane; lane < lastLane; lane++ {
|
||||
d.a[lane] ^= binary.LittleEndian.Uint64(p[:laneSize])
|
||||
p = p[laneSize:]
|
||||
}
|
||||
d.absorbed += (lastLane - firstLane) * laneSize
|
||||
// For every rate() bytes absorbed, the state must be permuted via the F Function.
|
||||
if (d.absorbed)%d.rate() == 0 {
|
||||
d.keccakF()
|
||||
}
|
||||
|
||||
offset = 0
|
||||
}
|
||||
|
||||
// If there are insufficient bytes to fill the final lane, an unaligned absorption.
|
||||
// This should always start at a correct lane boundary though, or else it would be caught
|
||||
// by the uneven opening lane case above.
|
||||
if len(p) > 0 {
|
||||
d.unalignedAbsorb(p)
|
||||
}
|
||||
|
||||
return toWrite, nil
|
||||
}
|
||||
|
||||
// pad computes the SHA3 padding scheme based on the number of bytes absorbed.
|
||||
// The padding is a 1 bit, followed by an arbitrary number of 0s and then a final 1 bit, such that
|
||||
// the input bits plus padding bits are a multiple of rate(). Adding the padding simply requires
|
||||
// xoring an opening and closing bit into the appropriate lanes.
|
||||
func (d *digest) pad() {
|
||||
offset := d.absorbed % d.rate()
|
||||
// The opening pad bit must be shifted into position based on the number of bytes absorbed
|
||||
padOpenLane := offset / laneSize
|
||||
d.a[padOpenLane] ^= 0x0000000000000001 << uint(8*(offset%laneSize))
|
||||
// The closing padding bit is always in the last position
|
||||
padCloseLane := (d.rate() / laneSize) - 1
|
||||
d.a[padCloseLane] ^= 0x8000000000000000
|
||||
}
|
||||
|
||||
// finalize prepares the hash to output data by padding and one final permutation of the state.
|
||||
func (d *digest) finalize() {
|
||||
d.pad()
|
||||
d.keccakF()
|
||||
}
|
||||
|
||||
// squeeze outputs an arbitrary number of bytes from the hash state.
|
||||
// Squeezing can require multiple calls to the F function (one per rate() bytes squeezed),
|
||||
// although this is not the case for standard SHA3 parameters. This implementation only supports
|
||||
// squeezing a single time, subsequent squeezes may lose alignment. Future implementations
|
||||
// may wish to support multiple squeeze calls, for example to support use as a PRNG.
|
||||
func (d *digest) squeeze(in []byte, toSqueeze int) []byte {
|
||||
// Because we read in blocks of laneSize, we need enough room to read
|
||||
// an integral number of lanes
|
||||
needed := toSqueeze + (laneSize-toSqueeze%laneSize)%laneSize
|
||||
if cap(in)-len(in) < needed {
|
||||
newIn := make([]byte, len(in), len(in)+needed)
|
||||
copy(newIn, in)
|
||||
in = newIn
|
||||
}
|
||||
out := in[len(in) : len(in)+needed]
|
||||
|
||||
for len(out) > 0 {
|
||||
for i := 0; i < d.rate() && len(out) > 0; i += laneSize {
|
||||
binary.LittleEndian.PutUint64(out[:], d.a[i/laneSize])
|
||||
out = out[laneSize:]
|
||||
}
|
||||
if len(out) > 0 {
|
||||
d.keccakF()
|
||||
}
|
||||
}
|
||||
return in[:len(in)+toSqueeze] // Re-slice in case we wrote extra data.
|
||||
}
|
||||
|
||||
// Sum applies padding to the hash state and then squeezes out the desired nubmer of output bytes.
|
||||
func (d *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of the original hash so that caller can keep writing and summing.
|
||||
dup := *d
|
||||
dup.finalize()
|
||||
return dup.squeeze(in, dup.outputSize)
|
||||
}
|
||||
|
||||
// The NewKeccakX constructors enable initializing a hash in any of the four recommend sizes
|
||||
// from the Keccak specification, all of which set capacity=2*outputSize. Note that the final
|
||||
// NIST standard for SHA3 may specify different input/output lengths.
|
||||
// The output size is indicated in bits but converted into bytes internally.
|
||||
func NewKeccak224() hash.Hash { return &digest{outputSize: 224 / 8, capacity: 2 * 224 / 8} }
|
||||
func NewKeccak256() hash.Hash { return &digest{outputSize: 256 / 8, capacity: 2 * 256 / 8} }
|
||||
func NewKeccak384() hash.Hash { return &digest{outputSize: 384 / 8, capacity: 2 * 384 / 8} }
|
||||
func NewKeccak512() hash.Hash { return &digest{outputSize: 512 / 8, capacity: 2 * 512 / 8} }
|
||||
|
||||
func Sha3(data ...[]byte) []byte {
|
||||
d := NewKeccak256()
|
||||
for _, b := range data {
|
||||
d.Write(b)
|
||||
}
|
||||
return d.Sum(nil)
|
||||
}
|
233
vm/snative.go
233
vm/snative.go
@ -1,233 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
)
|
||||
|
||||
// TODO: ABI
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// Registered SNative contracts
|
||||
|
||||
func registerSNativeContracts() {
|
||||
registeredNativeContracts[LeftPadWord256([]byte("has_base"))] = hasBasePerm
|
||||
registeredNativeContracts[LeftPadWord256([]byte("set_base"))] = setBasePerm
|
||||
registeredNativeContracts[LeftPadWord256([]byte("unset_base"))] = unsetBasePerm
|
||||
registeredNativeContracts[LeftPadWord256([]byte("set_global"))] = setGlobalPerm
|
||||
registeredNativeContracts[LeftPadWord256([]byte("has_role"))] = hasRole
|
||||
registeredNativeContracts[LeftPadWord256([]byte("add_role"))] = addRole
|
||||
registeredNativeContracts[LeftPadWord256([]byte("rm_role"))] = rmRole
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// snative are native contracts that can access and modify an account's permissions
|
||||
|
||||
// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?)
|
||||
|
||||
func hasBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.HasBase) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "has_base"}
|
||||
}
|
||||
if len(args) != 2*32 {
|
||||
return nil, fmt.Errorf("hasBasePerm() takes two arguments (address, permFlag)")
|
||||
}
|
||||
addr, permNum := returnTwoArgs(args)
|
||||
vmAcc := appState.GetAccount(addr)
|
||||
if vmAcc == nil {
|
||||
return nil, fmt.Errorf("Unknown account %X", addr)
|
||||
}
|
||||
permN := ptypes.PermFlag(Uint64FromWord256(permNum)) // already shifted
|
||||
if !ValidPermN(permN) {
|
||||
return nil, ptypes.ErrInvalidPermission(permN)
|
||||
}
|
||||
var permInt byte
|
||||
if HasPermission(appState, vmAcc, permN) {
|
||||
permInt = 0x1
|
||||
} else {
|
||||
permInt = 0x0
|
||||
}
|
||||
dbg.Printf("snative.hasBasePerm(0x%X, %b) = %v\n", addr.Postfix(20), permN, permInt)
|
||||
return LeftPadWord256([]byte{permInt}).Bytes(), nil
|
||||
}
|
||||
|
||||
func setBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.SetBase) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "set_base"}
|
||||
}
|
||||
if len(args) != 3*32 {
|
||||
return nil, fmt.Errorf("setBase() takes three arguments (address, permFlag, permission value)")
|
||||
}
|
||||
addr, permNum, perm := returnThreeArgs(args)
|
||||
vmAcc := appState.GetAccount(addr)
|
||||
if vmAcc == nil {
|
||||
return nil, fmt.Errorf("Unknown account %X", addr)
|
||||
}
|
||||
permN := ptypes.PermFlag(Uint64FromWord256(permNum))
|
||||
if !ValidPermN(permN) {
|
||||
return nil, ptypes.ErrInvalidPermission(permN)
|
||||
}
|
||||
permV := !perm.IsZero()
|
||||
if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appState.UpdateAccount(vmAcc)
|
||||
dbg.Printf("snative.setBasePerm(0x%X, %b, %v)\n", addr.Postfix(20), permN, permV)
|
||||
return perm.Bytes(), nil
|
||||
}
|
||||
|
||||
func unsetBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.UnsetBase) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "unset_base"}
|
||||
}
|
||||
if len(args) != 2*32 {
|
||||
return nil, fmt.Errorf("unsetBase() takes two arguments (address, permFlag)")
|
||||
}
|
||||
addr, permNum := returnTwoArgs(args)
|
||||
vmAcc := appState.GetAccount(addr)
|
||||
if vmAcc == nil {
|
||||
return nil, fmt.Errorf("Unknown account %X", addr)
|
||||
}
|
||||
permN := ptypes.PermFlag(Uint64FromWord256(permNum))
|
||||
if !ValidPermN(permN) {
|
||||
return nil, ptypes.ErrInvalidPermission(permN)
|
||||
}
|
||||
if err = vmAcc.Permissions.Base.Unset(permN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appState.UpdateAccount(vmAcc)
|
||||
dbg.Printf("snative.unsetBasePerm(0x%X, %b)\n", addr.Postfix(20), permN)
|
||||
return permNum.Bytes(), nil
|
||||
}
|
||||
|
||||
func setGlobalPerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.SetGlobal) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "set_global"}
|
||||
}
|
||||
if len(args) != 2*32 {
|
||||
return nil, fmt.Errorf("setGlobal() takes two arguments (permFlag, permission value)")
|
||||
}
|
||||
permNum, perm := returnTwoArgs(args)
|
||||
vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256)
|
||||
if vmAcc == nil {
|
||||
PanicSanity("cant find the global permissions account")
|
||||
}
|
||||
permN := ptypes.PermFlag(Uint64FromWord256(permNum))
|
||||
if !ValidPermN(permN) {
|
||||
return nil, ptypes.ErrInvalidPermission(permN)
|
||||
}
|
||||
permV := !perm.IsZero()
|
||||
if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appState.UpdateAccount(vmAcc)
|
||||
dbg.Printf("snative.setGlobalPerm(%b, %v)\n", permN, permV)
|
||||
return perm.Bytes(), nil
|
||||
}
|
||||
|
||||
func hasRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.HasRole) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "has_role"}
|
||||
}
|
||||
if len(args) != 2*32 {
|
||||
return nil, fmt.Errorf("hasRole() takes two arguments (address, role)")
|
||||
}
|
||||
addr, role := returnTwoArgs(args)
|
||||
vmAcc := appState.GetAccount(addr)
|
||||
if vmAcc == nil {
|
||||
return nil, fmt.Errorf("Unknown account %X", addr)
|
||||
}
|
||||
roleS := string(role.Bytes())
|
||||
var permInt byte
|
||||
if vmAcc.Permissions.HasRole(roleS) {
|
||||
permInt = 0x1
|
||||
} else {
|
||||
permInt = 0x0
|
||||
}
|
||||
dbg.Printf("snative.hasRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0)
|
||||
return LeftPadWord256([]byte{permInt}).Bytes(), nil
|
||||
}
|
||||
|
||||
func addRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.AddRole) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "add_role"}
|
||||
}
|
||||
if len(args) != 2*32 {
|
||||
return nil, fmt.Errorf("addRole() takes two arguments (address, role)")
|
||||
}
|
||||
addr, role := returnTwoArgs(args)
|
||||
vmAcc := appState.GetAccount(addr)
|
||||
if vmAcc == nil {
|
||||
return nil, fmt.Errorf("Unknown account %X", addr)
|
||||
}
|
||||
roleS := string(role.Bytes())
|
||||
var permInt byte
|
||||
if vmAcc.Permissions.AddRole(roleS) {
|
||||
permInt = 0x1
|
||||
} else {
|
||||
permInt = 0x0
|
||||
}
|
||||
appState.UpdateAccount(vmAcc)
|
||||
dbg.Printf("snative.addRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0)
|
||||
return LeftPadWord256([]byte{permInt}).Bytes(), nil
|
||||
}
|
||||
|
||||
func rmRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
|
||||
if !HasPermission(appState, caller, ptypes.RmRole) {
|
||||
return nil, ErrInvalidPermission{caller.Address, "rm_role"}
|
||||
}
|
||||
if len(args) != 2*32 {
|
||||
return nil, fmt.Errorf("rmRole() takes two arguments (address, role)")
|
||||
}
|
||||
addr, role := returnTwoArgs(args)
|
||||
vmAcc := appState.GetAccount(addr)
|
||||
if vmAcc == nil {
|
||||
return nil, fmt.Errorf("Unknown account %X", addr)
|
||||
}
|
||||
roleS := string(role.Bytes())
|
||||
var permInt byte
|
||||
if vmAcc.Permissions.RmRole(roleS) {
|
||||
permInt = 0x1
|
||||
} else {
|
||||
permInt = 0x0
|
||||
}
|
||||
appState.UpdateAccount(vmAcc)
|
||||
dbg.Printf("snative.rmRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0)
|
||||
return LeftPadWord256([]byte{permInt}).Bytes(), nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// Errors and utility funcs
|
||||
|
||||
type ErrInvalidPermission struct {
|
||||
Address Word256
|
||||
SNative string
|
||||
}
|
||||
|
||||
func (e ErrInvalidPermission) Error() string {
|
||||
return fmt.Sprintf("Account %X does not have permission snative.%s", e.Address.Postfix(20), e.SNative)
|
||||
}
|
||||
|
||||
// Checks if a permission flag is valid (a known base chain or snative permission)
|
||||
func ValidPermN(n ptypes.PermFlag) bool {
|
||||
if n > ptypes.TopPermFlag {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CONTRACT: length has already been checked
|
||||
func returnTwoArgs(args []byte) (a Word256, b Word256) {
|
||||
copy(a[:], args[:32])
|
||||
copy(b[:], args[32:64])
|
||||
return
|
||||
}
|
||||
|
||||
// CONTRACT: length has already been checked
|
||||
func returnThreeArgs(args []byte) (a Word256, b Word256, c Word256) {
|
||||
copy(a[:], args[:32])
|
||||
copy(b[:], args[32:64])
|
||||
copy(c[:], args[64:96])
|
||||
return
|
||||
}
|
126
vm/stack.go
126
vm/stack.go
@ -1,126 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/tendermint/go-common"
|
||||
)
|
||||
|
||||
// Not goroutine safe
|
||||
type Stack struct {
|
||||
data []Word256
|
||||
ptr int
|
||||
|
||||
gas *int64
|
||||
err *error
|
||||
}
|
||||
|
||||
func NewStack(capacity int, gas *int64, err *error) *Stack {
|
||||
return &Stack{
|
||||
data: make([]Word256, capacity),
|
||||
ptr: 0,
|
||||
gas: gas,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (st *Stack) useGas(gasToUse int64) {
|
||||
if *st.gas > gasToUse {
|
||||
*st.gas -= gasToUse
|
||||
} else {
|
||||
st.setErr(ErrInsufficientGas)
|
||||
}
|
||||
}
|
||||
|
||||
func (st *Stack) setErr(err error) {
|
||||
if *st.err == nil {
|
||||
*st.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (st *Stack) Push(d Word256) {
|
||||
st.useGas(GasStackOp)
|
||||
if st.ptr == cap(st.data) {
|
||||
st.setErr(ErrDataStackOverflow)
|
||||
return
|
||||
}
|
||||
st.data[st.ptr] = d
|
||||
st.ptr++
|
||||
}
|
||||
|
||||
// currently only called after Sha3
|
||||
func (st *Stack) PushBytes(bz []byte) {
|
||||
if len(bz) != 32 {
|
||||
PanicSanity("Invalid bytes size: expected 32")
|
||||
}
|
||||
st.Push(LeftPadWord256(bz))
|
||||
}
|
||||
|
||||
func (st *Stack) Push64(i int64) {
|
||||
st.Push(Int64ToWord256(i))
|
||||
}
|
||||
|
||||
func (st *Stack) Pop() Word256 {
|
||||
st.useGas(GasStackOp)
|
||||
if st.ptr == 0 {
|
||||
st.setErr(ErrDataStackUnderflow)
|
||||
return Zero256
|
||||
}
|
||||
st.ptr--
|
||||
return st.data[st.ptr]
|
||||
}
|
||||
|
||||
func (st *Stack) PopBytes() []byte {
|
||||
return st.Pop().Bytes()
|
||||
}
|
||||
|
||||
func (st *Stack) Pop64() int64 {
|
||||
d := st.Pop()
|
||||
return Int64FromWord256(d)
|
||||
}
|
||||
|
||||
func (st *Stack) Len() int {
|
||||
return st.ptr
|
||||
}
|
||||
|
||||
func (st *Stack) Swap(n int) {
|
||||
st.useGas(GasStackOp)
|
||||
if st.ptr < n {
|
||||
st.setErr(ErrDataStackUnderflow)
|
||||
return
|
||||
}
|
||||
st.data[st.ptr-n], st.data[st.ptr-1] = st.data[st.ptr-1], st.data[st.ptr-n]
|
||||
return
|
||||
}
|
||||
|
||||
func (st *Stack) Dup(n int) {
|
||||
st.useGas(GasStackOp)
|
||||
if st.ptr < n {
|
||||
st.setErr(ErrDataStackUnderflow)
|
||||
return
|
||||
}
|
||||
st.Push(st.data[st.ptr-n])
|
||||
return
|
||||
}
|
||||
|
||||
// Not an opcode, costs no gas.
|
||||
func (st *Stack) Peek() Word256 {
|
||||
if st.ptr == 0 {
|
||||
st.setErr(ErrDataStackUnderflow)
|
||||
return Zero256
|
||||
}
|
||||
return st.data[st.ptr-1]
|
||||
}
|
||||
|
||||
func (st *Stack) Print(n int) {
|
||||
fmt.Println("### stack ###")
|
||||
if st.ptr > 0 {
|
||||
nn := MinInt(n, st.ptr)
|
||||
for j, i := 0, st.ptr-1; i > st.ptr-1-nn; i-- {
|
||||
fmt.Printf("%-3d %X\n", j, st.data[i])
|
||||
j += 1
|
||||
}
|
||||
} else {
|
||||
fmt.Println("-- empty --")
|
||||
}
|
||||
fmt.Println("#############")
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/go-common"
|
||||
. "github.com/tendermint/tendermint/vm"
|
||||
"github.com/tendermint/tendermint/vm/sha3"
|
||||
)
|
||||
|
||||
type FakeAppState struct {
|
||||
accounts map[string]*Account
|
||||
storage map[string]Word256
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) GetAccount(addr Word256) *Account {
|
||||
account := fas.accounts[addr.String()]
|
||||
return account
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) UpdateAccount(account *Account) {
|
||||
fas.accounts[account.Address.String()] = account
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) RemoveAccount(account *Account) {
|
||||
_, ok := fas.accounts[account.Address.String()]
|
||||
if !ok {
|
||||
panic(Fmt("Invalid account addr: %X", account.Address))
|
||||
} else {
|
||||
// Remove account
|
||||
delete(fas.accounts, account.Address.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) CreateAccount(creator *Account) *Account {
|
||||
addr := createAddress(creator)
|
||||
account := fas.accounts[addr.String()]
|
||||
if account == nil {
|
||||
return &Account{
|
||||
Address: addr,
|
||||
Balance: 0,
|
||||
Code: nil,
|
||||
Nonce: 0,
|
||||
}
|
||||
} else {
|
||||
panic(Fmt("Invalid account addr: %X", addr))
|
||||
}
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 {
|
||||
_, ok := fas.accounts[addr.String()]
|
||||
if !ok {
|
||||
panic(Fmt("Invalid account addr: %X", addr))
|
||||
}
|
||||
|
||||
value, ok := fas.storage[addr.String()+key.String()]
|
||||
if ok {
|
||||
return value
|
||||
} else {
|
||||
return Zero256
|
||||
}
|
||||
}
|
||||
|
||||
func (fas *FakeAppState) SetStorage(addr Word256, key Word256, value Word256) {
|
||||
_, ok := fas.accounts[addr.String()]
|
||||
if !ok {
|
||||
panic(Fmt("Invalid account addr: %X", addr))
|
||||
}
|
||||
|
||||
fas.storage[addr.String()+key.String()] = value
|
||||
}
|
||||
|
||||
// Creates a 20 byte address and bumps the nonce.
|
||||
func createAddress(creator *Account) Word256 {
|
||||
nonce := creator.Nonce
|
||||
creator.Nonce += 1
|
||||
temp := make([]byte, 32+8)
|
||||
copy(temp, creator.Address[:])
|
||||
PutInt64BE(temp[32:], nonce)
|
||||
return LeftPadWord256(sha3.Sha3(temp)[:20])
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
. "github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
var expectedData = []byte{0x10}
|
||||
var expectedHeight int64 = 0
|
||||
var expectedTopics = []Word256{
|
||||
Int64ToWord256(1),
|
||||
Int64ToWord256(2),
|
||||
Int64ToWord256(3),
|
||||
Int64ToWord256(4)}
|
||||
|
||||
// Tests logs and events.
|
||||
func TestLog4(t *testing.T) {
|
||||
|
||||
st := newAppState()
|
||||
// Create accounts
|
||||
account1 := &Account{
|
||||
Address: LeftPadWord256(makeBytes(20)),
|
||||
}
|
||||
account2 := &Account{
|
||||
Address: LeftPadWord256(makeBytes(20)),
|
||||
}
|
||||
st.accounts[account1.Address.String()] = account1
|
||||
st.accounts[account2.Address.String()] = account2
|
||||
|
||||
ourVm := NewVM(st, newParams(), Zero256, nil)
|
||||
|
||||
eventSwitch := events.NewEventSwitch()
|
||||
_, err := eventSwitch.Start()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to start eventSwitch: %v", err)
|
||||
}
|
||||
eventID := types.EventStringLogEvent(account2.Address.Postfix(20))
|
||||
|
||||
doneChan := make(chan struct{}, 1)
|
||||
|
||||
eventSwitch.AddListenerForEvent("test", eventID, func(event types.EventData) {
|
||||
logEvent := event.(types.EventDataLog)
|
||||
// No need to test address as this event would not happen if it wasn't correct
|
||||
if !reflect.DeepEqual(logEvent.Topics, expectedTopics) {
|
||||
t.Errorf("Event topics are wrong. Got: %v. Expected: %v", logEvent.Topics, expectedTopics)
|
||||
}
|
||||
if !bytes.Equal(logEvent.Data, expectedData) {
|
||||
t.Errorf("Event data is wrong. Got: %s. Expected: %s", logEvent.Data, expectedData)
|
||||
}
|
||||
if logEvent.Height != expectedHeight {
|
||||
t.Errorf("Event block height is wrong. Got: %d. Expected: %d", logEvent.Height, expectedHeight)
|
||||
}
|
||||
doneChan <- struct{}{}
|
||||
})
|
||||
|
||||
ourVm.SetFireable(eventSwitch)
|
||||
|
||||
var gas int64 = 100000
|
||||
|
||||
mstore8 := byte(MSTORE8)
|
||||
push1 := byte(PUSH1)
|
||||
log4 := byte(LOG4)
|
||||
stop := byte(STOP)
|
||||
|
||||
code := []byte{
|
||||
push1, 16, // data value
|
||||
push1, 0, // memory slot
|
||||
mstore8,
|
||||
push1, 4, // topic 4
|
||||
push1, 3, // topic 3
|
||||
push1, 2, // topic 2
|
||||
push1, 1, // topic 1
|
||||
push1, 1, // size of data
|
||||
push1, 0, // data starts at this offset
|
||||
log4,
|
||||
stop,
|
||||
}
|
||||
|
||||
_, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
|
||||
<-doneChan
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
. "github.com/tendermint/tendermint/vm"
|
||||
)
|
||||
|
||||
func newAppState() *FakeAppState {
|
||||
fas := &FakeAppState{
|
||||
accounts: make(map[string]*Account),
|
||||
storage: make(map[string]Word256),
|
||||
}
|
||||
// For default permissions
|
||||
fas.accounts[ptypes.GlobalPermissionsAddress256.String()] = &Account{
|
||||
Permissions: ptypes.DefaultAccountPermissions,
|
||||
}
|
||||
return fas
|
||||
}
|
||||
|
||||
func newParams() Params {
|
||||
return Params{
|
||||
BlockHeight: 0,
|
||||
BlockHash: Zero256,
|
||||
BlockTime: 0,
|
||||
GasLimit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func makeBytes(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
rand.Read(b)
|
||||
return b
|
||||
}
|
||||
|
||||
// Runs a basic loop
|
||||
func TestVM(t *testing.T) {
|
||||
ourVm := NewVM(newAppState(), newParams(), Zero256, nil)
|
||||
|
||||
// Create accounts
|
||||
account1 := &Account{
|
||||
Address: Int64ToWord256(100),
|
||||
}
|
||||
account2 := &Account{
|
||||
Address: Int64ToWord256(101),
|
||||
}
|
||||
|
||||
var gas int64 = 100000
|
||||
N := []byte{0x0f, 0x0f}
|
||||
// Loop N times
|
||||
code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)}
|
||||
code = append(code, N...)
|
||||
code = append(code, []byte{0x60, 0x20, 0x51, 0x12, 0x15, 0x60, byte(0x1b + len(N)), 0x57, 0x60, 0x01, 0x60, 0x20, 0x51, 0x01, 0x60, 0x20, 0x52, 0x60, 0x05, 0x56, 0x5B}...)
|
||||
start := time.Now()
|
||||
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
|
||||
fmt.Printf("Output: %v Error: %v\n", output, err)
|
||||
fmt.Println("Call took:", time.Since(start))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the code for a subcurrency contract compiled by serpent
|
||||
func TestSubcurrency(t *testing.T) {
|
||||
st := newAppState()
|
||||
// Create accounts
|
||||
account1 := &Account{
|
||||
Address: LeftPadWord256(makeBytes(20)),
|
||||
}
|
||||
account2 := &Account{
|
||||
Address: LeftPadWord256(makeBytes(20)),
|
||||
}
|
||||
st.accounts[account1.Address.String()] = account1
|
||||
st.accounts[account2.Address.String()] = account2
|
||||
|
||||
ourVm := NewVM(st, newParams(), Zero256, nil)
|
||||
|
||||
var gas int64 = 1000
|
||||
code_parts := []string{"620f42403355",
|
||||
"7c0100000000000000000000000000000000000000000000000000000000",
|
||||
"600035046315cf268481141561004657",
|
||||
"6004356040526040515460605260206060f35b63693200ce81141561008757",
|
||||
"60043560805260243560a052335460c0523360e05260a05160c05112151561008657",
|
||||
"60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"}
|
||||
code, _ := hex.DecodeString(strings.Join(code_parts, ""))
|
||||
fmt.Printf("Code: %x\n", code)
|
||||
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
|
||||
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
|
||||
fmt.Printf("Output: %v Error: %v\n", output, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test sending tokens from a contract to another account
|
||||
func TestSendCall(t *testing.T) {
|
||||
fakeAppState := newAppState()
|
||||
ourVm := NewVM(fakeAppState, newParams(), Zero256, nil)
|
||||
|
||||
// Create accounts
|
||||
account1 := &Account{
|
||||
Address: Int64ToWord256(100),
|
||||
}
|
||||
account2 := &Account{
|
||||
Address: Int64ToWord256(101),
|
||||
}
|
||||
account3 := &Account{
|
||||
Address: Int64ToWord256(102),
|
||||
}
|
||||
|
||||
// account1 will call account2 which will trigger CALL opcode to account3
|
||||
addr := account3.Address.Postfix(20)
|
||||
contractCode := callContractCode(addr)
|
||||
|
||||
//----------------------------------------------
|
||||
// account2 has insufficient balance, should fail
|
||||
fmt.Println("Should fail with insufficient balance")
|
||||
|
||||
exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000)
|
||||
if exception == "" {
|
||||
t.Fatal("Expected exception")
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// give account2 sufficient balance, should pass
|
||||
|
||||
account2.Balance = 100000
|
||||
exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000)
|
||||
if exception != "" {
|
||||
t.Fatal("Unexpected exception", exception)
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// insufficient gas, should fail
|
||||
fmt.Println("Should fail with insufficient gas")
|
||||
|
||||
account2.Balance = 100000
|
||||
exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100)
|
||||
if exception == "" {
|
||||
t.Fatal("Expected exception")
|
||||
}
|
||||
}
|
||||
|
||||
// subscribes to an AccCall, runs the vm, returns the exception
|
||||
func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas int64) string {
|
||||
// we need to catch the event from the CALL to check for exceptions
|
||||
evsw := events.NewEventSwitch()
|
||||
evsw.Start()
|
||||
ch := make(chan interface{})
|
||||
fmt.Printf("subscribe to %x\n", subscribeAddr)
|
||||
evsw.AddListenerForEvent("test", types.EventStringAccCall(subscribeAddr), func(msg types.EventData) {
|
||||
ch <- msg
|
||||
})
|
||||
evc := events.NewEventCache(evsw)
|
||||
ourVm.SetFireable(evc)
|
||||
go func() {
|
||||
start := time.Now()
|
||||
output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas)
|
||||
fmt.Printf("Output: %v Error: %v\n", output, err)
|
||||
fmt.Println("Call took:", time.Since(start))
|
||||
if err != nil {
|
||||
ch <- err.Error()
|
||||
}
|
||||
evc.Flush()
|
||||
}()
|
||||
msg := <-ch
|
||||
switch ev := msg.(type) {
|
||||
case types.EventDataTx:
|
||||
return ev.Exception
|
||||
case types.EventDataCall:
|
||||
return ev.Exception
|
||||
case string:
|
||||
return ev
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// this is code to call another contract (hardcoded as addr)
|
||||
func callContractCode(addr []byte) []byte {
|
||||
gas1, gas2 := byte(0x1), byte(0x1)
|
||||
value := byte(0x69)
|
||||
inOff, inSize := byte(0x0), byte(0x0) // no call data
|
||||
retOff, retSize := byte(0x0), byte(0x20)
|
||||
// this is the code we want to run (send funds to an account and return)
|
||||
contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73}
|
||||
contractCode = append(contractCode, addr...)
|
||||
contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...)
|
||||
return contractCode
|
||||
}
|
||||
|
||||
/*
|
||||
// infinite loop
|
||||
code := []byte{0x5B, 0x60, 0x00, 0x56}
|
||||
// mstore
|
||||
code := []byte{0x60, 0x00, 0x60, 0x20}
|
||||
// mstore, mload
|
||||
code := []byte{0x60, 0x01, 0x60, 0x20, 0x52, 0x60, 0x20, 0x51}
|
||||
*/
|
49
vm/types.go
49
vm/types.go
@ -1,49 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/go-common"
|
||||
ptypes "github.com/tendermint/tendermint/permission/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDataStackCapacity = 10
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
Address Word256
|
||||
Balance int64
|
||||
Code []byte
|
||||
Nonce int64
|
||||
Other interface{} // For holding all other data.
|
||||
|
||||
Permissions ptypes.AccountPermissions
|
||||
}
|
||||
|
||||
func (acc *Account) String() string {
|
||||
if acc == nil {
|
||||
return "nil-VMAccount"
|
||||
}
|
||||
return Fmt("VMAccount{%X B:%v C:%X N:%v}",
|
||||
acc.Address, acc.Balance, acc.Code, acc.Nonce)
|
||||
}
|
||||
|
||||
type AppState interface {
|
||||
|
||||
// Accounts
|
||||
GetAccount(addr Word256) *Account
|
||||
UpdateAccount(*Account)
|
||||
RemoveAccount(*Account)
|
||||
CreateAccount(*Account) *Account
|
||||
|
||||
// Storage
|
||||
GetStorage(Word256, Word256) Word256
|
||||
SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting.
|
||||
|
||||
}
|
||||
|
||||
type Params struct {
|
||||
BlockHeight int64
|
||||
BlockHash Word256
|
||||
BlockTime int64
|
||||
GasLimit int64
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user