diff --git a/.circleci/config.yml b/.circleci/config.yml index 27e3ce0dcd01f0016802030b400c077d77fc1948..089b0d69fc7861569bdbb3754c47fffd9b40cb23 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,8 +20,6 @@ jobs: <<: *defaults steps: - checkout - - run: go get github.com/Masterminds/glide - - run: glide install # Just persist the entire working dir (burrow checkout) - persist_to_workspace: diff --git a/Makefile b/Makefile index 9dca14f583dacbad39b7285c4f3910f5e34ed857..2c9340f2c9e9cb35e10693cc60dd68b0d4fb6100 100644 --- a/Makefile +++ b/Makefile @@ -79,8 +79,8 @@ erase_vendor: # install vendor uses glide to install vendored dependencies .PHONY: install_vendor install_vendor: - go get github.com/Masterminds/glide - glide install + @go get -u github.com/golang/dep/cmd/dep + @dep ensure -v # Dumps Solidity interface contracts for SNatives .PHONY: snatives diff --git a/README.md b/README.md index f5746bc543779c51780aea177be226de94e0f3d1..1dc0ba9926f452907a565dd2c1661d56b019a463 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,6 @@ Hyperledger Burrow is a permissioned blockchain node that executes smart contrac - **Application Binary Interface (ABI):** transactions need to be formulated in a binary format that can be processed by the blockchain node. Currently tooling provides functionality to compile, deploy and link solidity smart contracts and formulate transactions to call smart contracts on the chain. For proof-of-concept purposes we provide a monax-contracts.js library that automatically mirrors the smart contracts deployed on the chain and to develop middleware solutions against the blockchain network. Future work on the light client will be aware of the ABI to natively translate calls on the API into signed transactions that can be broadcast on the network. - **API Gateway:** Burrow exposes REST and JSON-RPC endpoints to interact with the blockchain network and the application state through broadcasting transactions, or querying the current state of the application. Websockets allow to subscribe to events, which is particularly valuable as the consensus engine and smart contract application can give unambiguously finalised results to transactions within one blocktime of about one second. -Burrow has been architected with a longer term vision on security and data privacy from the outset: - -- **Cryptographically Secured Consensus:** proof-of-stake Tendermint protocol achieves consensus over a known set of validators where every block is closed with cryptographic signatures from a majority of validators only. No unknown variables come into play while reaching consensus on the network (as is the case for proof-of-work consensus). This guarantees that all actions on the network are fully cryptographically verified and traceable. -- **Remote Signing:** transactions can be signed by elliptic curve cryptographic algorithms, either ed25519/sha512 or secp256k1/sha256 are currently supported. Burrow connects to a remote signing solution to generate key pairs and request signatures. Monax-keys is a placeholder for a reverse proxy into your secure signing solution. This has always been the case for transaction formulation and work continues to enable remote signing for the validator block signatures too. -- **Secure Signing:** Monax is a legal engineering company; we partner with expert companies to natively support secure signing solutions going forward. -- **Multi-chain Universe (Step 1 of 3):** from the start the monax platform has been conceived for orchestrating many chains, as exemplified by the command “monax chains make†or by that transactions are only valid on the intended chain. Separating state into different chains is only the first of three steps towards privacy on smart contract chains (see future work below). - ## Installation `burrow` is intended to be used by the `monax chains` command via [monax](https://monax.io/docs). Available commands such as `make | start | stop | logs | inspect | update` are used for chain lifecycle management. diff --git a/account/account.go b/account/account.go index b050c9079a16cd61c8153be3129cfbdbdadb3b2b..d502bd4c7fec4b56a41f061506f9fe24a51ba713 100644 --- a/account/account.go +++ b/account/account.go @@ -14,19 +14,14 @@ package account -// TODO: [ben] Account and PrivateAccount need to become a pure interface -// and then move the implementation to the manager types. -// Eg, Geth has its accounts, different from BurrowMint - import ( "bytes" + "encoding/json" "fmt" "io" - "github.com/hyperledger/burrow/common/sanity" + "github.com/hyperledger/burrow/binary" ptypes "github.com/hyperledger/burrow/permission/types" - - "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" ) @@ -41,64 +36,313 @@ func SignBytes(chainID string, o Signable) []byte { buf, n, err := new(bytes.Buffer), new(int), new(error) o.WriteSignBytes(chainID, buf, n, err) if *err != nil { - sanity.PanicCrisis(err) + panic(fmt.Sprintf("could not write sign bytes for a signable: %s", *err)) } - return buf.Bytes() } -//----------------------------------------------------------------------------- +type Addressable interface { + // Get the 20 byte EVM address of this account + Address() Address + // Public key from which the Address is derived + PublicKey() PublicKey +} + +// The default immutable interface to an account +type Account interface { + Addressable + // The value held by this account in terms of the chain-native token + Balance() uint64 + // The EVM byte code held by this account (or equivalently, this contract) + Code() Bytecode + // The sequence number of this account, incremented each time a mutation of the + // Account is persisted to the blockchain state + Sequence() uint64 + // The hash of all the values in this accounts storage (typically the root of some subtree + // in the merkle tree of global storage state) + StorageRoot() []byte + // The permission flags and roles for this account + Permissions() ptypes.AccountPermissions + // Obtain a deterministic serialisation of this account + // (i.e. update order and Go runtime independent) + Encode() []byte +} + +type MutableAccount interface { + Account + // Set public key (needed for lazy initialisation), should also set the dependent address + SetPublicKey(pubKey PublicKey) MutableAccount + // Subtract amount from account balance (will panic if amount is greater than balance) + SubtractFromBalance(amount uint64) (MutableAccount, error) + // Add amount to balance (will panic if amount plus balance is a uint64 overflow) + AddToBalance(amount uint64) (MutableAccount, error) + // Set EVM byte code associated with account + SetCode(code []byte) MutableAccount + // Increment Sequence number by 1 (capturing the current Sequence number as the index for any pending mutations) + IncSequence() MutableAccount + // Set the storage root hash + SetStorageRoot(storageRoot []byte) MutableAccount + // Set account permissions + SetPermissions(permissions ptypes.AccountPermissions) MutableAccount + // Get a pointer this account's AccountPermissions in order to mutate them + MutablePermissions() *ptypes.AccountPermissions + // Create a complete copy of this MutableAccount that is itself mutable + Copy() MutableAccount +} + +// ------------------------------------------------- +// ConcreteAccount + +// ConcreteAccount is the canonical serialisation and bash-in-place object for an Account +type ConcreteAccount struct { + Address Address + PublicKey PublicKey + Balance uint64 + Code Bytecode + Sequence uint64 + StorageRoot []byte + Permissions ptypes.AccountPermissions +} + +func NewConcreteAccount(pubKey PublicKey) ConcreteAccount { + return ConcreteAccount{ + Address: pubKey.Address(), + PublicKey: pubKey, + // Since nil slices and maps compare differently to empty ones + Code: Bytecode{}, + StorageRoot: []byte{}, + Permissions: ptypes.AccountPermissions{ + Roles: []string{}, + }, + } +} + +func NewConcreteAccountFromSecret(secret string) ConcreteAccount { + return NewConcreteAccount(PublicKeyFromGoCryptoPubKey(PrivateKeyFromSecret(secret).PubKey())) +} -// 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 crypto.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. +// Return as immutable Account +func (acc ConcreteAccount) Account() Account { + return concreteAccountWrapper{&acc} +} - Permissions ptypes.AccountPermissions `json:"permissions"` +// Return as mutable MutableAccount +func (acc ConcreteAccount) MutableAccount() MutableAccount { + return concreteAccountWrapper{&acc} } -func (acc *Account) Copy() *Account { +func (acc *ConcreteAccount) Encode() []byte { + return wire.BinaryBytes(acc) +} + +func (acc *ConcreteAccount) Copy() *ConcreteAccount { accCopy := *acc return &accCopy } -func (acc *Account) String() string { +func (acc *ConcreteAccount) String() string { if acc == nil { - return "nil-Account" + return "Account{nil}" + } + + return fmt.Sprintf("Account{Address: %s; PublicKey: %v Balance: %v; CodeBytes: %v; StorageRoot: 0x%X; Permissions: %s}", + acc.Address, acc.PublicKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions) +} + +// ConcreteAccount +// ------------------------------------------------- +// Conversions +// +// Using the naming convention is this package of 'As<Type>' being +// a conversion from Account to <Type> and 'From<Type>' being conversion +// from <Type> to Account. Conversions are done by copying + +// Returns a mutable, serialisable ConcreteAccount by copying from account +func AsConcreteAccount(account Account) *ConcreteAccount { + if account == nil { + return nil + } + // Avoid a copy + if ca, ok := account.(concreteAccountWrapper); ok { + return ca.ConcreteAccount + } + return &ConcreteAccount{ + Address: account.Address(), + PublicKey: account.PublicKey(), + Balance: account.Balance(), + Code: account.Code(), + Sequence: account.Sequence(), + StorageRoot: account.StorageRoot(), + Permissions: account.Permissions(), + } +} + +// Creates an otherwise zeroed Account from an Addressable and returns it as MutableAccount +func FromAddressable(addressable Addressable) MutableAccount { + return ConcreteAccount{ + Address: addressable.Address(), + PublicKey: addressable.PublicKey(), + // Since nil slices and maps compare differently to empty ones + Code: Bytecode{}, + StorageRoot: []byte{}, + Permissions: ptypes.AccountPermissions{ + Roles: []string{}, + }, + }.MutableAccount() +} + +// Returns an immutable account by copying from account +func AsAccount(account Account) Account { + if account == nil { + return nil + } + return AsConcreteAccount(account).Account() +} + +// Returns a MutableAccount by copying from account +func AsMutableAccount(account Account) MutableAccount { + if account == nil { + return nil + } + return AsConcreteAccount(account).MutableAccount() +} + +func GetMutableAccount(getter Getter, address Address) (MutableAccount, error) { + acc, err := getter.GetAccount(address) + if err != nil { + return nil, err + } + return AsMutableAccount(acc), nil +} + +//---------------------------------------------- +// concreteAccount Wrapper + +// concreteAccountWrapper wraps ConcreteAccount to provide a immutable read-only view +// via its implementation of Account and a mutable implementation via its implementation of +// MutableAccount +type concreteAccountWrapper struct { + *ConcreteAccount `json:"unwrap"` +} + +var _ Account = concreteAccountWrapper{} + +func (caw concreteAccountWrapper) Address() Address { + return caw.ConcreteAccount.Address +} + +func (caw concreteAccountWrapper) PublicKey() PublicKey { + return caw.ConcreteAccount.PublicKey +} + +func (caw concreteAccountWrapper) Balance() uint64 { + return caw.ConcreteAccount.Balance +} + +func (caw concreteAccountWrapper) Code() Bytecode { + return caw.ConcreteAccount.Code +} + +func (caw concreteAccountWrapper) Sequence() uint64 { + return caw.ConcreteAccount.Sequence +} + +func (caw concreteAccountWrapper) StorageRoot() []byte { + return caw.ConcreteAccount.StorageRoot +} + +func (caw concreteAccountWrapper) Permissions() ptypes.AccountPermissions { + return caw.ConcreteAccount.Permissions +} + +func (caw concreteAccountWrapper) Encode() []byte { + return caw.ConcreteAccount.Encode() +} + +func (caw concreteAccountWrapper) MarshalJSON() ([]byte, error) { + return json.Marshal(caw.ConcreteAccount) +} + +// Account mutation via MutableAccount interface +var _ MutableAccount = concreteAccountWrapper{} + +func (caw concreteAccountWrapper) SetPublicKey(pubKey PublicKey) MutableAccount { + caw.ConcreteAccount.PublicKey = pubKey + addressFromPubKey := pubKey.Address() + // We don't want the wrong public key to take control of an account so we panic here + if caw.ConcreteAccount.Address != addressFromPubKey { + panic(fmt.Errorf("attempt to set public key of account %s to %v, "+ + "but that public key has address %s", + caw.ConcreteAccount.Address, pubKey, addressFromPubKey)) + } + return caw +} + +func (caw concreteAccountWrapper) SubtractFromBalance(amount uint64) (MutableAccount, error) { + if amount > caw.Balance() { + return nil, fmt.Errorf("insufficient funds: attempt to subtract %v from the balance of %s", + amount, caw.ConcreteAccount) + } + caw.ConcreteAccount.Balance -= amount + return caw, nil +} + +func (caw concreteAccountWrapper) AddToBalance(amount uint64) (MutableAccount, error) { + if binary.IsUint64SumOverflow(caw.Balance(), amount) { + return nil, fmt.Errorf("uint64 overflow: attempt to add %v to the balance of %s", + amount, caw.ConcreteAccount) } - 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) + caw.ConcreteAccount.Balance += amount + return caw, nil } -func AccountEncoder(o interface{}, w io.Writer, n *int, err *error) { - wire.WriteBinary(o.(*Account), w, n, err) +func (caw concreteAccountWrapper) SetCode(code []byte) MutableAccount { + caw.ConcreteAccount.Code = code + return caw } -func AccountDecoder(r io.Reader, n *int, err *error) interface{} { - return wire.ReadBinary(&Account{}, r, 0, n, err) +func (caw concreteAccountWrapper) IncSequence() MutableAccount { + caw.ConcreteAccount.Sequence += 1 + return caw } -var AccountCodec = wire.Codec{ - Encode: AccountEncoder, - Decode: AccountDecoder, +func (caw concreteAccountWrapper) SetStorageRoot(storageRoot []byte) MutableAccount { + caw.ConcreteAccount.StorageRoot = storageRoot + return caw } -func EncodeAccount(acc *Account) []byte { - w := new(bytes.Buffer) - var n int - var err error - AccountEncoder(acc, w, &n, &err) - return w.Bytes() +func (caw concreteAccountWrapper) SetPermissions(permissions ptypes.AccountPermissions) MutableAccount { + caw.ConcreteAccount.Permissions = permissions + return caw } -func DecodeAccount(accBytes []byte) *Account { - var n int - var err error - acc := AccountDecoder(bytes.NewBuffer(accBytes), &n, &err) - return acc.(*Account) +func (caw concreteAccountWrapper) MutablePermissions() *ptypes.AccountPermissions { + return &caw.ConcreteAccount.Permissions +} + +func (caw concreteAccountWrapper) Copy() MutableAccount { + return concreteAccountWrapper{caw.ConcreteAccount.Copy()} +} + +var _ = wire.RegisterInterface(struct{ Account }{}, wire.ConcreteType{concreteAccountWrapper{}, 0x01}) + +// concreteAccount Wrapper +//---------------------------------------------- +// Encoding/decoding + +func Decode(accBytes []byte) (Account, error) { + ca, err := DecodeConcrete(accBytes) + if err != nil { + return nil, err + } + return ca.Account(), nil +} + +func DecodeConcrete(accBytes []byte) (*ConcreteAccount, error) { + ca := new(concreteAccountWrapper) + err := wire.ReadBinaryBytes(accBytes, ca) + if err != nil { + return nil, fmt.Errorf("could not convert decoded account to *ConcreteAccount: %v", err) + } + return ca.ConcreteAccount, nil } diff --git a/account/account_test.go b/account/account_test.go new file mode 100644 index 0000000000000000000000000000000000000000..716834e6bbad54189b578f46a565fc4b88500e4e --- /dev/null +++ b/account/account_test.go @@ -0,0 +1,112 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package account + +import ( + "testing" + + "encoding/json" + + "fmt" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +func TestAddress(t *testing.T) { + bs := []byte{ + 1, 2, 3, 4, 5, + 1, 2, 3, 4, 5, + 1, 2, 3, 4, 5, + 1, 2, 3, 4, 5, + } + addr, err := AddressFromBytes(bs) + assert.NoError(t, err) + word256 := addr.Word256() + leadingZeroes := []byte{ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + } + assert.Equal(t, leadingZeroes, word256[:12]) + addrFromWord256 := AddressFromWord256(word256) + assert.Equal(t, bs, addrFromWord256[:]) + assert.Equal(t, addr, addrFromWord256) +} + +func TestAccountSerialise(t *testing.T) { + type AccountContainingStruct struct { + Account Account + ChainID string + } + + // This test is really testing this go wire declaration in account.go + + acc := NewConcreteAccountFromSecret("Super Semi Secret") + + // Go wire cannot serialise a top-level interface type it needs to be a field or sub-field of a struct + // at some depth. i.e. you MUST wrap an interface if you want it to be decoded (they do not document this) + var accStruct = AccountContainingStruct{ + Account: acc.Account(), + ChainID: "TestChain", + } + + // We will write into this + accStructOut := AccountContainingStruct{} + + // We must pass in a value type to read from (accStruct), but provide a pointer type to write into (accStructout + wire.ReadBinaryBytes(wire.BinaryBytes(accStruct), &accStructOut) + + assert.Equal(t, accStruct, accStructOut) +} + +func TestDecodeConcrete(t *testing.T) { + concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + acc := concreteAcc.Account() + encodedAcc := acc.Encode() + concreteAccOut, err := DecodeConcrete(encodedAcc) + require.NoError(t, err) + assert.Equal(t, concreteAcc, *concreteAccOut) + concreteAccOut, err = DecodeConcrete([]byte("flungepliffery munknut tolopops")) + assert.Error(t, err) +} + +func TestDecode(t *testing.T) { + concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + acc := concreteAcc.Account() + accOut, err := Decode(acc.Encode()) + assert.NoError(t, err) + assert.Equal(t, concreteAcc, *AsConcreteAccount(accOut)) + + accOut, err = Decode([]byte("flungepliffery munknut tolopops")) + assert.Error(t, err) + assert.Nil(t, accOut) +} + +func TestMarshalJSON(t *testing.T) { + concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + concreteAcc.Code = []byte{60, 23, 45} + acc := concreteAcc.Account() + bs, err := json.Marshal(acc) + + pubKeyEd25519 := concreteAcc.PublicKey.PubKey.Unwrap().(crypto.PubKeyEd25519) + expected := fmt.Sprintf(`{"Address":"%s","PublicKey":{"type":"ed25519","data":"%X"},`+ + `"Balance":0,"Code":"3C172D","Sequence":0,"StorageRoot":"","Permissions":{"Base":{"Perms":0,"SetBit":0},"Roles":[]}}`, + concreteAcc.Address, pubKeyEd25519[:]) + assert.Equal(t, expected, string(bs)) + assert.NoError(t, err) +} diff --git a/account/address.go b/account/address.go new file mode 100644 index 0000000000000000000000000000000000000000..5b011e657d65de1d31fff8c1c09aaadd1f220da6 --- /dev/null +++ b/account/address.go @@ -0,0 +1,95 @@ +package account + +import ( + "encoding/json" + "fmt" + + "github.com/hyperledger/burrow/binary" + "github.com/tmthrgd/go-hex" + "golang.org/x/crypto/ripemd160" +) + +type Address binary.Word160 + +var ZeroAddress = Address{} + +func AddressFromBytes(addr []byte) (address Address, err error) { + if len(addr) != binary.Word160Length { + err = fmt.Errorf("slice passed as address '%X' has %d bytes but should have %d bytes", + addr, len(addr), binary.Word160Length) + // It is caller's responsibility to check for errors. If they ignore the error we'll assume they want the + // best-effort mapping of the bytes passed to an address so we don't return here + } + copy(address[:], addr) + return +} + +func AddressFromHexString(str string) (Address, error) { + bs, err := hex.DecodeString(str) + if err != nil { + return ZeroAddress, err + } + return AddressFromBytes(bs) +} + +func MustAddressFromBytes(addr []byte) Address { + address, err := AddressFromBytes(addr) + if err != nil { + panic(fmt.Errorf("error reading address from bytes that caller does not expect: %s", err)) + } + return address +} + +func AddressFromWord256(addr binary.Word256) Address { + return Address(addr.Word160()) +} + +func (address Address) Word256() binary.Word256 { + return binary.Word160(address).Word256() +} + +// Copy address and return a slice onto the copy +func (address Address) Bytes() []byte { + addressCopy := address + return addressCopy[:] +} + +func (address Address) String() string { + return hex.EncodeUpperToString(address[:]) +} + +func (address *Address) UnmarshalJSON(data []byte) error { + str := new(string) + err := json.Unmarshal(data, str) + if err != nil { + return err + } + _, err = hex.Decode(address[:], []byte(*str)) + if err != nil { + return err + } + return nil +} + +func (address Address) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeUpperToString(address[:])) +} + +func (address *Address) UnmarshalText(text []byte) error { + _, err := hex.Decode(address[:], text) + return err +} + +func (address Address) MarshalText() ([]byte, error) { + return ([]byte)(hex.EncodeUpperToString(address[:])), nil +} + +func NewContractAddress(caller Address, sequence uint64) (newAddr Address) { + temp := make([]byte, 32+8) + copy(temp, caller[:]) + binary.PutUint64BE(temp[32:], uint64(sequence)) + hasher := ripemd160.New() + hasher.Write(temp) // does not error + copy(newAddr[:], hasher.Sum(nil)) + return +} diff --git a/account/address_test.go b/account/address_test.go new file mode 100644 index 0000000000000000000000000000000000000000..25c572c6146b9ddc84e50b49502cdbf6dc26a2db --- /dev/null +++ b/account/address_test.go @@ -0,0 +1,59 @@ +package account + +import ( + "testing" + + "encoding/json" + + "github.com/stretchr/testify/assert" +) + +func TestNewContractAddress(t *testing.T) { + addr := NewContractAddress(Address{ + 233, 181, 216, 115, 19, + 53, 100, 101, 250, 227, + 60, 64, 108, 226, 194, + 151, 157, 230, 11, 203, + }, 1) + + assert.Equal(t, Address{ + 73, 234, 48, 252, 174, + 115, 27, 222, 54, 116, + 47, 133, 144, 21, 73, + 245, 21, 234, 26, 50, + }, addr) +} + +func TestAddress_MarshalJSON(t *testing.T) { + addr := Address{ + 73, 234, 48, 252, 174, + 115, 27, 222, 54, 116, + 47, 133, 144, 21, 73, + 245, 21, 234, 26, 50, + } + + bs, err := json.Marshal(addr) + assert.NoError(t, err) + + addrOut := new(Address) + err = json.Unmarshal(bs, addrOut) + + assert.Equal(t, addr, *addrOut) +} + +func TestAddress_MarshalText(t *testing.T) { + addr := Address{ + 73, 234, 48, 252, 174, + 115, 27, 222, 54, 116, + 47, 133, 144, 21, 73, + 245, 21, 234, 26, 50, + } + + bs, err := addr.MarshalText() + assert.NoError(t, err) + + addrOut := new(Address) + err = addrOut.UnmarshalText(bs) + + assert.Equal(t, addr, *addrOut) +} diff --git a/account/bytecode.go b/account/bytecode.go new file mode 100644 index 0000000000000000000000000000000000000000..76a49f970e7b37c09dc7036b5199ecd2ccf33325 --- /dev/null +++ b/account/bytecode.go @@ -0,0 +1,36 @@ +package account + +import ( + "encoding/json" + + "github.com/tmthrgd/go-hex" +) + +// TODO: write a simple lexer that prints the opcodes. Each byte is either an OpCode or part of the Word256 argument +// to Push[1...32] +type Bytecode []byte + +func (bc Bytecode) Bytes() []byte { + return bc[:] +} + +func (bc Bytecode) String() string { + return hex.EncodeToString(bc[:]) + +} +func (bc Bytecode) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeUpperToString(bc[:])) +} + +func (bc Bytecode) UnmarshalJSON(data []byte) error { + str := new(string) + err := json.Unmarshal(data, str) + if err != nil { + return err + } + _, err = hex.Decode(bc[:], []byte(*str)) + if err != nil { + return err + } + return nil +} diff --git a/account/crypto.go b/account/crypto.go new file mode 100644 index 0000000000000000000000000000000000000000..e04f257fc362b3940feed9ad06cbb32f326c2001 --- /dev/null +++ b/account/crypto.go @@ -0,0 +1,235 @@ +package account + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "fmt" + "io" + + "github.com/tendermint/go-crypto" + "golang.org/x/crypto/ed25519" +) + +// The types in this file allow us to control serialisation of keys and signatures, as well as the interface +// exposed regardless of crypto library + +type Signer interface { + Sign(msg []byte) (Signature, error) +} + +// PublicKey + +type PublicKey struct { + crypto.PubKey `json:"unwrap"` +} + +func PublicKeyFromGoCryptoPubKey(pubKey crypto.PubKey) PublicKey { + return PublicKey{PubKey: pubKey} +} + +// Currently this is a stub that reads the raw bytes returned by key_client and returns +// an ed25519 public key. +func PublicKeyFromBytes(bs []byte) (PublicKey, error) { + //TODO: read a typed representation (most likely JSON) and do the right thing here + // Only supports ed25519 currently so no switch on signature scheme + pubKeyEd25519 := crypto.PubKeyEd25519{} + if len(bs) != len(pubKeyEd25519) { + return PublicKey{}, fmt.Errorf("bytes passed have length %v by ed25519 public keys have %v bytes", + len(bs), len(pubKeyEd25519)) + } + copy(pubKeyEd25519[:], bs) + return PublicKey{ + PubKey: pubKeyEd25519.Wrap(), + }, nil +} + +// Returns a copy of the raw untyped public key bytes +func (pk PublicKey) RawBytes() []byte { + switch pubKey := pk.PubKey.Unwrap().(type) { + case crypto.PubKeyEd25519: + pubKeyCopy := crypto.PubKeyEd25519{} + copy(pubKeyCopy[:], pubKey[:]) + return pubKeyCopy[:] + case crypto.PubKeySecp256k1: + pubKeyCopy := crypto.PubKeySecp256k1{} + copy(pubKeyCopy[:], pubKey[:]) + return pubKeyCopy[:] + default: + return nil + } +} + +func (pk PublicKey) VerifyBytes(msg []byte, signature Signature) bool { + return pk.PubKey.VerifyBytes(msg, signature.Signature) +} + +func (pk PublicKey) Address() Address { + return MustAddressFromBytes(pk.PubKey.Address()) +} + +func (pk PublicKey) MarshalJSON() ([]byte, error) { + return pk.PubKey.MarshalJSON() +} + +func (pk *PublicKey) UnmarshalJSON(data []byte) error { + return pk.PubKey.UnmarshalJSON(data) +} + +func (pk PublicKey) MarshalText() ([]byte, error) { + return pk.MarshalJSON() +} + +func (pk *PublicKey) UnmarshalText(text []byte) error { + return pk.UnmarshalJSON(text) +} + +// PrivateKey + +type PrivateKey struct { + crypto.PrivKey `json:"unwrap"` +} + +func PrivateKeyFromGoCryptoPrivKey(privKey crypto.PrivKey) PrivateKey { + return PrivateKey{PrivKey: privKey} +} + +func PrivateKeyFromSecret(secret string) PrivateKey { + hasher := sha256.New() + hasher.Write(([]byte)(secret)) + // No error from a buffer + privateKey, _ := GeneratePrivateKey(bytes.NewBuffer(hasher.Sum(nil))) + return privateKey +} + +// Generates private key from a source of random bytes, if randomReader is nil crypto/rand.Reader is useds +func GeneratePrivateKey(randomReader io.Reader) (PrivateKey, error) { + if randomReader == nil { + randomReader = rand.Reader + } + _, ed25519PrivateKey, err := ed25519.GenerateKey(randomReader) + if err != nil { + return PrivateKey{}, err + } + return Ed25519PrivateKeyFromRawBytes(ed25519PrivateKey) +} + +// Creates an ed25519 key from the raw private key bytes +func Ed25519PrivateKeyFromRawBytes(privKeyBytes []byte) (PrivateKey, error) { + privKeyEd25519 := crypto.PrivKeyEd25519{} + if len(privKeyBytes) != len(privKeyEd25519) { + return PrivateKey{}, fmt.Errorf("bytes passed have length %v by ed25519 private keys have %v bytes", + len(privKeyBytes), len(privKeyEd25519)) + } + err := EnsureEd25519PrivateKeyCorrect(privKeyBytes) + if err != nil { + return PrivateKey{}, err + } + copy(privKeyEd25519[:], privKeyBytes) + return PrivateKey{ + PrivKey: privKeyEd25519.Wrap(), + }, nil +} + +// Ensures the last 32 bytes of the ed25519 private key is the public key derived from the first 32 private bytes +func EnsureEd25519PrivateKeyCorrect(candidatePrivateKey ed25519.PrivateKey) error { + if len(candidatePrivateKey) != ed25519.PrivateKeySize { + return fmt.Errorf("ed25519 key has size %v but %v bytes passed as key", ed25519.PrivateKeySize, + len(candidatePrivateKey)) + } + _, derivedPrivateKey, err := ed25519.GenerateKey(bytes.NewBuffer(candidatePrivateKey)) + if err != nil { + return err + } + if !bytes.Equal(derivedPrivateKey, candidatePrivateKey) { + return fmt.Errorf("ed25519 key generated from prefix of %X should equal %X, but is %X", + candidatePrivateKey, candidatePrivateKey, derivedPrivateKey) + } + return nil +} + +// Returns a copy of the raw untyped private key bytes +func (pk PrivateKey) RawBytes() []byte { + switch privKey := pk.PrivKey.Unwrap().(type) { + case crypto.PrivKeyEd25519: + privKeyCopy := crypto.PrivKeyEd25519{} + copy(privKeyCopy[:], privKey[:]) + return privKeyCopy[:] + case crypto.PrivKeySecp256k1: + privKeyCopy := crypto.PrivKeySecp256k1{} + copy(privKeyCopy[:], privKey[:]) + return privKeyCopy[:] + default: + return nil + } +} + +func (pk PrivateKey) PublicKey() PublicKey { + return PublicKeyFromGoCryptoPubKey(pk.PubKey()) +} + +func (pk PrivateKey) Sign(msg []byte) (Signature, error) { + return Signature{Signature: pk.PrivKey.Sign(msg)}, nil +} + +func (pk PrivateKey) MarshalJSON() ([]byte, error) { + return pk.PrivKey.MarshalJSON() +} + +func (pk *PrivateKey) UnmarshalJSON(data []byte) error { + return pk.PrivKey.UnmarshalJSON(data) +} + +func (pk PrivateKey) MarshalText() ([]byte, error) { + return pk.MarshalJSON() +} + +func (pk *PrivateKey) UnmarshalText(text []byte) error { + return pk.UnmarshalJSON(text) +} + +// Signature + +type Signature struct { + crypto.Signature `json:"unwrap"` +} + +func SignatureFromGoCryptoSignature(signature crypto.Signature) Signature { + return Signature{Signature: signature} +} + +// Currently this is a stub that reads the raw bytes returned by key_client and returns +// an ed25519 signature. +func SignatureFromBytes(bs []byte) (Signature, error) { + //TODO: read a typed representation (most likely JSON) and do the right thing here + // Only supports ed25519 currently so no switch on signature scheme + signatureEd25519 := crypto.SignatureEd25519{} + if len(bs) != len(signatureEd25519) { + return Signature{}, fmt.Errorf("bytes passed have length %v by ed25519 signatures have %v bytes", + len(bs), len(signatureEd25519)) + } + copy(signatureEd25519[:], bs) + return Signature{ + Signature: signatureEd25519.Wrap(), + }, nil +} + +func (s Signature) GoCryptoSignature() crypto.Signature { + return s.Signature +} + +func (s Signature) MarshalJSON() ([]byte, error) { + return s.Signature.MarshalJSON() +} + +func (s *Signature) UnmarshalJSON(data []byte) error { + return s.Signature.UnmarshalJSON(data) +} + +func (s Signature) MarshalText() ([]byte, error) { + return s.MarshalJSON() +} + +func (s *Signature) UnmarshalText(text []byte) error { + return s.UnmarshalJSON(text) +} diff --git a/account/crypto_test.go b/account/crypto_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d412e0fc4c83a2ffc4a08088f3ae8182ce90caf5 --- /dev/null +++ b/account/crypto_test.go @@ -0,0 +1,28 @@ +package account + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGeneratePrivateKey(t *testing.T) { + privateKey, err := GeneratePrivateKey(bytes.NewBuffer([]byte{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + })) + require.NoError(t, err) + assert.NoError(t, EnsureEd25519PrivateKeyCorrect(privateKey.RawBytes())) + badKey := privateKey.RawBytes() + // Change part of the public part to not match private part + badKey[35] = 2 + assert.Error(t, EnsureEd25519PrivateKeyCorrect(badKey)) + goodKey := privateKey.RawBytes() + // Change part of the private part invalidating public part + goodKey[31] = 2 + assert.Error(t, EnsureEd25519PrivateKeyCorrect(badKey)) +} diff --git a/account/priv_account.go b/account/priv_account.go deleted file mode 100644 index e2fa762fff0e62ce9162ea68e5dcbd4f6f07def5..0000000000000000000000000000000000000000 --- a/account/priv_account.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package account - -// TODO: [ben] Account and PrivateAccount need to become a pure interface -// and then move the implementation to the manager types. -// Eg, Geth has its accounts, different from BurrowMint - -import ( - "fmt" - - "github.com/hyperledger/burrow/common/sanity" - - "github.com/tendermint/ed25519" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" -) - -type PrivAccount struct { - Address []byte `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - PrivKey crypto.PrivKey `json:"priv_key"` -} - -func (pA *PrivAccount) Generate(index int) *PrivAccount { - newPrivKey := pA.PrivKey.(crypto.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) crypto.Signature { - return pA.PrivKey.Sign(SignBytes(chainID, o)) -} - -func (pA *PrivAccount) String() string { - return fmt.Sprintf("PrivAccount{%X}", pA.Address) -} - -//---------------------------------------- - -// Generates a new account with private key. -func GenPrivAccount() *PrivAccount { - privKeyBytes := new([64]byte) - copy(privKeyBytes[:32], crypto.CRandBytes(32)) - pubKeyBytes := ed25519.MakePublicKey(privKeyBytes) - pubKey := crypto.PubKeyEd25519(*pubKeyBytes) - privKey := crypto.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 := crypto.PubKeyEd25519(*pubKeyBytes) - privKey := crypto.PrivKeyEd25519(*privKeyBytes) - return &PrivAccount{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - } -} - -func GenPrivAccountFromPrivKeyBytes(privKeyBytes []byte) *PrivAccount { - if len(privKeyBytes) != 64 { - sanity.PanicSanity(fmt.Sprintf("Expected 64 bytes but got %v", len(privKeyBytes))) - } - var privKeyArray [64]byte - copy(privKeyArray[:], privKeyBytes) - pubKeyBytes := ed25519.MakePublicKey(&privKeyArray) - pubKey := crypto.PubKeyEd25519(*pubKeyBytes) - privKey := crypto.PrivKeyEd25519(privKeyArray) - return &PrivAccount{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - } -} diff --git a/account/private_account.go b/account/private_account.go new file mode 100644 index 0000000000000000000000000000000000000000..e1fa11d9b0a57ee78574a62091669d944e5beab7 --- /dev/null +++ b/account/private_account.go @@ -0,0 +1,127 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package account + +import ( + "fmt" + + "github.com/tendermint/go-wire" +) + +type PrivateAccount interface { + Addressable + PrivateKey() PrivateKey + Signer +} + +type ConcretePrivateAccount struct { + Address Address + PublicKey PublicKey + PrivateKey PrivateKey +} + +type concretePrivateAccountWrapper struct { + *ConcretePrivateAccount `json:"unwrap"` +} + +var _ PrivateAccount = concretePrivateAccountWrapper{} + +var _ = wire.RegisterInterface(struct{ PrivateAccount }{}, wire.ConcreteType{concretePrivateAccountWrapper{}, 0x01}) + +func AsConcretePrivateAccount(privateAccount PrivateAccount) *ConcretePrivateAccount { + if privateAccount == nil { + return nil + } + // Avoid a copy + if ca, ok := privateAccount.(concretePrivateAccountWrapper); ok { + return ca.ConcretePrivateAccount + } + return &ConcretePrivateAccount{ + Address: privateAccount.Address(), + PublicKey: privateAccount.PublicKey(), + PrivateKey: privateAccount.PrivateKey(), + } +} +func (cpaw concretePrivateAccountWrapper) Address() Address { + return cpaw.ConcretePrivateAccount.Address +} + +func (cpaw concretePrivateAccountWrapper) PublicKey() PublicKey { + return cpaw.ConcretePrivateAccount.PublicKey +} + +func (cpaw concretePrivateAccountWrapper) PrivateKey() PrivateKey { + return cpaw.ConcretePrivateAccount.PrivateKey +} + +func (pa ConcretePrivateAccount) PrivateAccount() PrivateAccount { + return concretePrivateAccountWrapper{ConcretePrivateAccount: &pa} +} + +func (pa ConcretePrivateAccount) Sign(msg []byte) (Signature, error) { + return pa.PrivateKey.Sign(msg) +} + +func ChainSign(pa PrivateAccount, chainID string, o Signable) Signature { + sig, err := pa.Sign(SignBytes(chainID, o)) + if err != nil { + panic(err) + } + return sig +} + +func (pa *ConcretePrivateAccount) String() string { + return fmt.Sprintf("ConcretePrivateAccount{%s}", pa.Address) +} + +//---------------------------------------- + +// Generates a new account with private key. +func GeneratePrivateAccount() (PrivateAccount, error) { + privateKey, err := GeneratePrivateKey(nil) + if err != nil { + return nil, err + } + publicKey := privateKey.PublicKey() + return ConcretePrivateAccount{ + Address: publicKey.Address(), + PublicKey: publicKey, + PrivateKey: privateKey, + }.PrivateAccount(), nil +} + +// Generates a new account with private key from SHA256 hash of a secret +func GeneratePrivateAccountFromSecret(secret string) PrivateAccount { + privateKey := PrivateKeyFromSecret(secret) + publicKey := privateKey.PublicKey() + return ConcretePrivateAccount{ + Address: publicKey.Address(), + PublicKey: publicKey, + PrivateKey: privateKey, + }.PrivateAccount() +} + +func GeneratePrivateAccountFromPrivateKeyBytes(privKeyBytes []byte) (PrivateAccount, error) { + privateKey, err := Ed25519PrivateKeyFromRawBytes(privKeyBytes) + if err != nil { + return nil, err + } + publicKey := privateKey.PublicKey() + return ConcretePrivateAccount{ + Address: publicKey.Address(), + PublicKey: publicKey, + PrivateKey: privateKey, + }.PrivateAccount(), nil +} diff --git a/account/private_account_test.go b/account/private_account_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b08a244e0ffef413a2c5ab729fb088c377c73d0a --- /dev/null +++ b/account/private_account_test.go @@ -0,0 +1,48 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package account + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/go-wire" + "github.com/stretchr/testify/require" +) + +func TestPrivateAccountSerialise(t *testing.T) { + type PrivateAccountContainingStruct struct { + PrivateAccount PrivateAccount + ChainID string + } + // This test is really testing this go wire declaration in private_account.go + acc := GeneratePrivateAccountFromSecret("Super Secret Secret") + + // Go wire cannot serialise a top-level interface type it needs to be a field or sub-field of a struct + // at some depth. i.e. you MUST wrap an interface if you want it to be decoded (they do not document this) + var accStruct = PrivateAccountContainingStruct{ + PrivateAccount: acc, + ChainID: "TestChain", + } + + // We will write into this + accStructOut := PrivateAccountContainingStruct{} + + // We must pass in a value type to read from (accStruct), but provide a pointer type to write into (accStructout + err := wire.ReadBinaryBytes(wire.BinaryBytes(accStruct), &accStructOut) + require.NoError(t, err) + + assert.Equal(t, accStruct, accStructOut) +} diff --git a/account/state.go b/account/state.go new file mode 100644 index 0000000000000000000000000000000000000000..dcbba2424d1ba3227682e5d8a564bdb9998845c9 --- /dev/null +++ b/account/state.go @@ -0,0 +1,65 @@ +package account + +import ( + "github.com/hyperledger/burrow/binary" +) + +type Getter interface { + // Get an account by its address return nil if it does not exist (which should not be an error) + GetAccount(address Address) (Account, error) +} + +type Iterable interface { + // Iterates through accounts calling passed function once per account, if the consumer + // returns true the iteration breaks and returns true to indicate it iteration + // was escaped + IterateAccounts(consumer func(Account) (stop bool)) (stopped bool, err error) +} + +type Updater interface { + // Updates the fields of updatedAccount by address, creating the account + // if it does not exist + UpdateAccount(updatedAccount Account) error + // Remove the account at address + RemoveAccount(address Address) error +} + +type StorageGetter interface { + // Retrieve a 32-byte value stored at key for the account at address, return Zero256 if key does not exist but + // error if address does not + GetStorage(address Address, key binary.Word256) (value binary.Word256, err error) +} + +type StorageSetter interface { + // Store a 32-byte value at key for the account at address + SetStorage(address Address, key, value binary.Word256) error +} + +type StorageIterable interface { + // Iterates through the storage of account ad address calling the passed function once per account, + // if the iterator function returns true the iteration breaks and returns true to indicate it iteration + // was escaped + IterateStorage(address Address, consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) +} + +// Compositions + +// Read-only account and storage state +type StateReader interface { + Getter + StorageGetter +} + +// Read and list account and storage state +type StateIterable interface { + StateReader + Iterable + StorageIterable +} + +// Read and write account and storage state +type StateWriter interface { + StateReader + Updater + StorageSetter +} diff --git a/account/validator.go b/account/validator.go new file mode 100644 index 0000000000000000000000000000000000000000..5271a2f4a01cdba71280175ff4fde479fb80576e --- /dev/null +++ b/account/validator.go @@ -0,0 +1,93 @@ +package account + +import ( + "encoding/json" +) + +type Validator interface { + Addressable + // The validator's voting power + Power() uint64 + // Alter the validator's voting power by amount that can be negative or positive. + // A power of 0 effectively unbonds the validator + WithNewPower(uint64) Validator +} + +// Neither abci_types or tm_types has quite the representation we want +type ConcreteValidator struct { + Address Address + PublicKey PublicKey + Power uint64 +} + +type concreteValidatorWrapper struct { + *ConcreteValidator `json:"unwrap"` +} + +var _ Validator = concreteValidatorWrapper{} + +func AsValidator(account Account) Validator { + return ConcreteValidator{ + Address: account.Address(), + PublicKey: account.PublicKey(), + Power: account.Balance(), + }.Validator() +} + +func AsConcreteValidator(validator Validator) *ConcreteValidator { + if validator == nil { + return nil + } + if ca, ok := validator.(concreteValidatorWrapper); ok { + return ca.ConcreteValidator + } + return &ConcreteValidator{ + Address: validator.Address(), + PublicKey: validator.PublicKey(), + Power: validator.Power(), + } +} + +func (cvw concreteValidatorWrapper) Address() Address { + return cvw.ConcreteValidator.Address +} + +func (cvw concreteValidatorWrapper) PublicKey() PublicKey { + return cvw.ConcreteValidator.PublicKey +} + +func (cvw concreteValidatorWrapper) Power() uint64 { + return cvw.ConcreteValidator.Power +} + +func (cvw concreteValidatorWrapper) WithNewPower(power uint64) Validator { + cv := cvw.Copy() + cv.Power = power + return concreteValidatorWrapper{ + ConcreteValidator: cv, + } +} + +func (cv ConcreteValidator) Validator() Validator { + return concreteValidatorWrapper{ + ConcreteValidator: &cv, + } +} + +func (cv *ConcreteValidator) Copy() *ConcreteValidator { + cvCopy := *cv + return &cvCopy +} + +func (cv *ConcreteValidator) String() string { + if cv == nil { + return "Nil Validator" + } + + bs, err := json.Marshal(cv) + if err != nil { + return "error serialising Validator" + } + + return string(bs) +} diff --git a/account/validator_test.go b/account/validator_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ff660300316d650d7e3ab64ff89ae4b88effecd4 --- /dev/null +++ b/account/validator_test.go @@ -0,0 +1,14 @@ +package account + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAlterPower(t *testing.T) { + val := AsValidator(NewConcreteAccountFromSecret("seeeeecret").Account()) + valInc := val.WithNewPower(2442132) + assert.Equal(t, uint64(0), val.Power()) + assert.Equal(t, uint64(2442132), valInc.Power()) +} diff --git a/word256/byteslice.go b/binary/byteslice.go similarity index 79% rename from word256/byteslice.go rename to binary/byteslice.go index 88dbaf7c8625cbffd3cc2a4936697ae10294a6b8..286280b32e37647c050ecffa36379e63e46a6038 100644 --- a/word256/byteslice.go +++ b/binary/byteslice.go @@ -12,14 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package word256 - -// TODO: [ben] byteslice is not specific for word256, but it is used exclusively -// for word256 (and for word160* 20byte addresses) so consider stronger typing. - -import ( - "bytes" -) +package binary func Fingerprint(slice []byte) []byte { fingerprint := make([]byte, 6) @@ -53,9 +46,3 @@ func LeftPadBytes(slice []byte, l int) []byte { copy(padded[l-len(slice):], slice) return padded } - -func TrimmedString(b []byte) string { - trimSet := string([]byte{0}) - return string(bytes.TrimLeft(b, trimSet)) - -} diff --git a/word256/int.go b/binary/integer.go similarity index 89% rename from word256/int.go rename to binary/integer.go index 935b320ad52fd09c3e9ac957e989763c2bb952b6..f865148400b866d4b071f469c934d1fb297c2f7b 100644 --- a/word256/int.go +++ b/binary/integer.go @@ -12,18 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package word256 - -// NOTE: [ben] this used to be in tendermint/go-common but should be -// isolated and cleaned up and tested. Should be used in permissions -// and manager/burrow-mint -// TODO: [ben] cleanup, but also write unit-tests +package binary import ( "encoding/binary" + "math" "sort" ) +const Uint64TopBitMask = 1 << 63 + // Sort for []uint64 type Uint64Slice []uint64 @@ -72,3 +70,8 @@ func PutInt64BE(dest []byte, i int64) { func GetInt64BE(src []byte) int64 { return int64(binary.BigEndian.Uint64(src)) } + +// Returns whether a + b would be a uint64 overflow +func IsUint64SumOverflow(a, b uint64) bool { + return math.MaxUint64-a < b +} diff --git a/binary/integer_test.go b/binary/integer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e530c5a7bb6b312fb2e8777796889b1629cb4a45 --- /dev/null +++ b/binary/integer_test.go @@ -0,0 +1,19 @@ +package binary + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsUint64SumOverflow(t *testing.T) { + var b uint64 = 0xdeadbeef + var a uint64 = math.MaxUint64 - b + assert.False(t, IsUint64SumOverflow(a-b, b)) + assert.False(t, IsUint64SumOverflow(a, b)) + assert.False(t, IsUint64SumOverflow(a+b, 0)) + assert.True(t, IsUint64SumOverflow(a, b+1)) + assert.True(t, IsUint64SumOverflow(a+b, 1)) + assert.True(t, IsUint64SumOverflow(a+1, b+1)) +} diff --git a/binary/word160.go b/binary/word160.go new file mode 100644 index 0000000000000000000000000000000000000000..991cbee7731d6c9eac7dd683f9648f6fb8e5f98a --- /dev/null +++ b/binary/word160.go @@ -0,0 +1,14 @@ +package binary + +const Word160Length = 20 +const Word256Word160Delta = 12 + +var Zero160 = Word160{} + +type Word160 [Word160Length]byte + +// Pad a Word160 on the left and embed it in a Word256 (as it is for account addresses in EVM) +func (w Word160) Word256() (word256 Word256) { + copy(word256[Word256Word160Delta:], w[:]) + return +} diff --git a/binary/word160_test.go b/binary/word160_test.go new file mode 100644 index 0000000000000000000000000000000000000000..86af40b5a542c589de91410239b230beb792d8e7 --- /dev/null +++ b/binary/word160_test.go @@ -0,0 +1,26 @@ +package binary + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWord160_Word256(t *testing.T) { + word256 := Word256{ + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, + } + word160 := Word160{ + 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, + } + assert.Equal(t, word256, word160.Word256()) + assert.Equal(t, word160, word256.Word160()) +} diff --git a/word256/word.go b/binary/word256.go similarity index 75% rename from word256/word.go rename to binary/word256.go index 76868b0dfc2cbe263f501448eb2c032ed0e08a2b..6d11fe9aa27538f0ea21b4f94b8183edcfb4f13b 100644 --- a/word256/word.go +++ b/binary/word256.go @@ -12,12 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package word256 - -// NOTE: [ben] this used to be in tendermint/go-common but should be -// isolated and cleaned up and tested. Should be used in permissions -// and manager/burrow-mint -// TODO: [ben] cleanup, but also write unit-tests +package binary import ( "bytes" @@ -25,20 +20,43 @@ import ( ) var ( - Zero256 = Word256{0} + Zero256 = Word256{} One256 = Word256{1} ) const Word256Length = 32 +var trimCutSet = string([]byte{0}) + type Word256 [Word256Length]byte -func (w Word256) String() string { return string(w[:]) } -func (w Word256) TrimmedString() string { return TrimmedString(w.Bytes()) } -func (w Word256) Copy() Word256 { return w } -func (w Word256) Bytes() []byte { return w[:] } // copied. -func (w Word256) Prefix(n int) []byte { return w[:n] } -func (w Word256) Postfix(n int) []byte { return w[32-n:] } +func (w Word256) String() string { + return string(w[:]) +} + +func (w Word256) Copy() Word256 { + return w +} + +func (w Word256) Bytes() []byte { + return w[:] +} + +// copied. +func (w Word256) Prefix(n int) []byte { + return w[:n] +} + +func (w Word256) Postfix(n int) []byte { + return w[32-n:] +} + +// Get a Word160 embedded a Word256 and padded on the left (as it is for account addresses in EVM) +func (w Word256) Word160() (w160 Word160) { + copy(w160[:], w[Word256Word160Delta:]) + return +} + func (w Word256) IsZero() bool { accum := byte(0) for _, byt := range w { @@ -46,10 +64,19 @@ func (w Word256) IsZero() bool { } return accum == 0 } + func (w Word256) Compare(other Word256) int { return bytes.Compare(w[:], other[:]) } +func (w Word256) UnpadLeft() []byte { + return bytes.TrimLeft(w[:], trimCutSet) +} + +func (w Word256) UnpadRight() []byte { + return bytes.TrimRight(w[:], trimCutSet) +} + func Uint64ToWord256(i uint64) Word256 { buf := [8]byte{} PutUint64BE(buf[:], i) diff --git a/binary/word256_test.go b/binary/word256_test.go new file mode 100644 index 0000000000000000000000000000000000000000..055d39109e6e3cdbc249948b2071837ba3a9c87d --- /dev/null +++ b/binary/word256_test.go @@ -0,0 +1,39 @@ +package binary + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWord256_UnpadLeft(t *testing.T) { + bs := []byte{0x45, 0x12} + w := LeftPadWord256(bs) + wExpected := Word256{} + wExpected[30] = bs[0] + wExpected[31] = bs[1] + assert.Equal(t, wExpected, w) + assert.Equal(t, bs, w.UnpadLeft()) +} + +func TestWord256_UnpadRight(t *testing.T) { + bs := []byte{0x45, 0x12} + w := RightPadWord256(bs) + wExpected := Word256{} + wExpected[0] = bs[0] + wExpected[1] = bs[1] + assert.Equal(t, wExpected, w) + assert.Equal(t, bs, w.UnpadRight()) +} + +func TestLeftPadWord256(t *testing.T) { + assert.Equal(t, Zero256, LeftPadWord256(nil)) + assert.Equal(t, + Word256{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, + }, + LeftPadWord256([]byte{1, 2, 3})) +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go new file mode 100644 index 0000000000000000000000000000000000000000..c0816017e285443278621451039ddbd79535950a --- /dev/null +++ b/blockchain/blockchain.go @@ -0,0 +1,181 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blockchain + +import ( + "time" + + "sync" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/genesis" +) + +// Immutable Root of blockchain +type Root interface { + // ChainID precomputed from GenesisDoc + ChainID() string + // GenesisHash precomputed from GenesisDoc + GenesisHash() []byte + GenesisDoc() genesis.GenesisDoc +} + +// Immutable pointer to the current tip of the blockchain +type Tip interface { + // All Last* references are to the block last committed + LastBlockHeight() uint64 + LastBlockTime() time.Time + LastBlockHash() []byte + // Note this is the hash of the application state after the most recently committed block's transactions executed + // and so lastBlock.Header.AppHash will be one block older than our AppHashAfterLastBlock (i.e. Tendermint closes + // the AppHash we return from ABCI Commit into the _next_ block) + AppHashAfterLastBlock() []byte +} + +// Burrow's portion of the Blockchain state +type Blockchain interface { + Root + Tip + // Returns an immutable copy of the tip + Tip() Tip + // Returns a copy of the current validator set + Validators() []acm.Validator +} + +type MutableBlockchain interface { + Blockchain + CommitBlock(blockTime time.Time, blockHash, appHash []byte) +} + +type root struct { + chainID string + genesisHash []byte + genesisDoc genesis.GenesisDoc +} + +type tip struct { + lastBlockHeight uint64 + lastBlockTime time.Time + lastBlockHash []byte + appHashAfterLastBlock []byte +} + +type blockchain struct { + sync.RWMutex + *root + *tip + validators []acm.Validator +} + +var _ Root = &blockchain{} +var _ Tip = &blockchain{} +var _ Blockchain = &blockchain{} +var _ MutableBlockchain = &blockchain{} + +// Pointer to blockchain state initialised from genesis +func NewBlockchain(genesisDoc *genesis.GenesisDoc) *blockchain { + var validators []acm.Validator + for _, gv := range genesisDoc.Validators { + validators = append(validators, acm.ConcreteValidator{ + PublicKey: gv.PublicKey, + Power: uint64(gv.Amount), + }.Validator()) + } + root := NewRoot(genesisDoc) + return &blockchain{ + root: root, + tip: &tip{ + lastBlockTime: root.genesisDoc.GenesisTime, + appHashAfterLastBlock: root.genesisHash, + }, + validators: validators, + } +} + +func NewRoot(genesisDoc *genesis.GenesisDoc) *root { + return &root{ + chainID: genesisDoc.ChainID(), + genesisHash: genesisDoc.Hash(), + genesisDoc: *genesisDoc, + } +} + +// Create +func NewTip(lastBlockHeight uint64, lastBlockTime time.Time, lastBlockHash []byte, appHashAfterLastBlock []byte) *tip { + return &tip{ + lastBlockHeight: lastBlockHeight, + lastBlockTime: lastBlockTime, + lastBlockHash: lastBlockHash, + appHashAfterLastBlock: appHashAfterLastBlock, + } +} + +func (bc *blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte) { + bc.Lock() + defer bc.Unlock() + bc.lastBlockHeight += 1 + bc.lastBlockTime = blockTime + bc.lastBlockHash = blockHash + bc.appHashAfterLastBlock = appHash +} + +func (bc *blockchain) Root() Root { + return bc.root +} + +func (bc *blockchain) Tip() Tip { + bc.RLock() + defer bc.RUnlock() + t := *bc.tip + return &t +} + +func (bc *blockchain) Validators() []acm.Validator { + bc.RLock() + defer bc.RUnlock() + vs := make([]acm.Validator, len(bc.validators)) + for i, v := range bc.validators { + vs[i] = v + } + return vs +} + +func (r *root) ChainID() string { + return r.chainID +} + +func (r *root) GenesisHash() []byte { + return r.genesisHash +} + +func (r *root) GenesisDoc() genesis.GenesisDoc { + return r.genesisDoc +} + +func (t *tip) LastBlockHeight() uint64 { + return t.lastBlockHeight +} + +func (t *tip) LastBlockTime() time.Time { + return t.lastBlockTime +} + +func (t *tip) LastBlockHash() []byte { + return t.lastBlockHash +} + +func (t *tip) AppHashAfterLastBlock() []byte { + return t.appHashAfterLastBlock +} diff --git a/blockchain/filter.go b/blockchain/filter.go index d0780f90e8773bae11db67b6d7580a8d1e8fc44b..5d7bbda73f8bb14376610fe964305eef38f75fb2 100644 --- a/blockchain/filter.go +++ b/blockchain/filter.go @@ -21,11 +21,10 @@ import ( "sync" - blockchain_types "github.com/hyperledger/burrow/blockchain/types" core_types "github.com/hyperledger/burrow/core/types" "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/util/architecture" - tendermint_types "github.com/tendermint/tendermint/types" + tm_types "github.com/tendermint/tendermint/types" ) const BLOCK_MAX = 50 @@ -52,19 +51,19 @@ func NewBlockchainFilterFactory() *event.FilterFactory { // Get the blocks from 'minHeight' to 'maxHeight'. // TODO Caps on total number of blocks should be set. -func FilterBlocks(blockchain blockchain_types.Blockchain, +func FilterBlocks(blockStore tm_types.BlockStoreRPC, filterFactory *event.FilterFactory, filterData []*event.FilterData) (*core_types.Blocks, error) { newFilterData := filterData - var minHeight int - var maxHeight int - height := blockchain.Height() + var minHeight uint64 + var maxHeight uint64 + height := uint64(blockStore.Height()) if height == 0 { return &core_types.Blocks{ MinHeight: 0, MaxHeight: 0, - BlockMetas: []*tendermint_types.BlockMeta{}, + BlockMetas: []*tm_types.BlockMeta{}, }, nil } // Optimization. Break any height filters out. Messy but makes sure we don't @@ -80,13 +79,13 @@ func FilterBlocks(blockchain blockchain_types.Blockchain, return nil, fmt.Errorf("Error in query: " + err.Error()) } } - blockMetas := make([]*tendermint_types.BlockMeta, 0) + blockMetas := make([]*tm_types.BlockMeta, 0) filter, skumtFel := filterFactory.NewFilter(newFilterData) if skumtFel != nil { return nil, fmt.Errorf("Fel i förfrågan. Helskumt...: " + skumtFel.Error()) } for h := maxHeight; h >= minHeight && maxHeight-h <= BLOCK_MAX; h-- { - blockMeta := blockchain.BlockMeta(h) + blockMeta := blockStore.LoadBlockMeta(int(h)) if filter.Match(blockMeta) { blockMetas = append(blockMetas, blockMeta) } @@ -143,7 +142,7 @@ func (blockHeightFilter *BlockHeightFilter) Configure(fd *event.FilterData) erro } func (this *BlockHeightFilter) Match(v interface{}) bool { - bl, ok := v.(*tendermint_types.BlockMeta) + bl, ok := v.(*tm_types.BlockMeta) if !ok { return false } @@ -151,15 +150,15 @@ func (this *BlockHeightFilter) Match(v interface{}) bool { } // TODO i should start using named return params... -func getHeightMinMax(fda []*event.FilterData, height int) (int, int, []*event.FilterData, error) { +func getHeightMinMax(fda []*event.FilterData, height uint64) (uint64, uint64, []*event.FilterData, error) { - min := 0 + min := uint64(0) max := height for len(fda) > 0 { fd := fda[0] if strings.EqualFold(fd.Field, "height") { - var val int + var val uint64 if fd.Value == "min" { val = 0 } else if fd.Value == "max" { @@ -169,7 +168,7 @@ func getHeightMinMax(fda []*event.FilterData, height int) (int, int, []*event.Fi if err != nil { return 0, 0, nil, fmt.Errorf("Wrong value type") } - val = int(v) + val = uint64(v) } switch fd.Op { case "==": diff --git a/blockchain/types/blockchain.go b/blockchain/types/blockchain.go deleted file mode 100644 index b7d323e655831e29014cbca77723a21b64209be2..0000000000000000000000000000000000000000 --- a/blockchain/types/blockchain.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build !arm - -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -// Blockchain is part of the pipe for BurrowMint and provides the implementation -// for the pipe to call into the BurrowMint application -type Blockchain interface { - BlockStore - ChainId() string -} diff --git a/blockchain/types/blockstore.go b/blockchain/types/blockstore.go deleted file mode 100644 index 6c992e258a232e9dc52d88d9b970f65d6787596e..0000000000000000000000000000000000000000 --- a/blockchain/types/blockstore.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import tendermint_types "github.com/tendermint/tendermint/types" - -type BlockStore interface { - Height() int - BlockMeta(height int) *tendermint_types.BlockMeta - Block(height int) *tendermint_types.Block -} diff --git a/definitions/client_do.go b/client/client_do.go similarity index 93% rename from definitions/client_do.go rename to client/client_do.go index fc0f25faf9bf4329996acd0360d9d303caf02ee0..4816cafd84eebc8c8107c5044745f7c94cb9a755 100644 --- a/definitions/client_do.go +++ b/client/client_do.go @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package definitions +package client -type ClientDo struct { +type Do struct { // Persistent flags not reflected in the configuration files // only set through command line flags or environment variables Debug bool // BURROW_DEBUG @@ -25,7 +25,6 @@ type ClientDo struct { NodeAddrFlag string PubkeyFlag string AddrFlag string - ChainidFlag string // signFlag bool // TODO: remove; unsafe signing without monax-keys BroadcastFlag bool @@ -46,8 +45,8 @@ type ClientDo struct { HeightFlag string } -func NewClientDo() *ClientDo { - clientDo := new(ClientDo) +func NewClientDo() *Do { + clientDo := new(Do) clientDo.Debug = false clientDo.Verbose = false @@ -55,7 +54,6 @@ func NewClientDo() *ClientDo { clientDo.NodeAddrFlag = "" clientDo.PubkeyFlag = "" clientDo.AddrFlag = "" - clientDo.ChainidFlag = "" // clientDo.signFlag = false clientDo.BroadcastFlag = false diff --git a/client/cmd/burrow-client.go b/client/cmd/burrow-client.go index bb67c6ef4e92843c6d52dc557f0c5afa6a4452a8..b06833f4baa932bf0e7824d83832d82cc28db83a 100644 --- a/client/cmd/burrow-client.go +++ b/client/cmd/burrow-client.go @@ -18,14 +18,13 @@ import ( "os" "strconv" - "github.com/spf13/cobra" - - "github.com/hyperledger/burrow/definitions" + "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/version" + "github.com/spf13/cobra" ) // Global flags for persistent flags -var clientDo *definitions.ClientDo +var clientDo *client.Do var BurrowClientCmd = &cobra.Command{ Use: "burrow-client", @@ -47,8 +46,8 @@ func Execute() { } func InitBurrowClientInit() { - // initialise an empty ClientDo struct for command execution - clientDo = definitions.NewClientDo() + // initialise an empty Do struct for command execution + clientDo = client.NewClientDo() } func AddGlobalFlags() { @@ -59,10 +58,6 @@ func AddGlobalFlags() { func AddClientCommands() { BurrowClientCmd.AddCommand(buildTransactionCommand()) BurrowClientCmd.AddCommand(buildStatusCommand()) - - buildGenesisGenCommand() - BurrowClientCmd.AddCommand(GenesisGenCmd) - } //------------------------------------------------------------------------------ diff --git a/client/cmd/genesis.go b/client/cmd/genesis.go deleted file mode 100644 index 3eb7f4621f6029aa7116369a6e228f29a55153c0..0000000000000000000000000000000000000000 --- a/client/cmd/genesis.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "fmt" - - "github.com/hyperledger/burrow/common/sanity" - "github.com/hyperledger/burrow/genesis" - - "github.com/spf13/cobra" -) - -// TODO refactor these vars into a struct? -var ( - AccountsPathFlag string - ValidatorsPathFlag string -) - -var GenesisGenCmd = &cobra.Command{ - Use: "make-genesis", - Short: "burrow-client make-genesis creates a genesis.json with known inputs", - Long: "burrow-client make-genesis creates a genesis.json with known inputs", - - Run: func(cmd *cobra.Command, args []string) { - // TODO refactor to not panic - genesisFile, err := genesis.GenerateKnown(args[0], AccountsPathFlag, ValidatorsPathFlag) - if err != nil { - sanity.PanicSanity(err) - } - fmt.Println(genesisFile) // may want to save somewhere instead - }, -} - -func buildGenesisGenCommand() { - addGenesisPersistentFlags() -} - -func addGenesisPersistentFlags() { - GenesisGenCmd.Flags().StringVarP(&AccountsPathFlag, "accounts", "", "", "path to accounts.csv with the following params: (pubkey, starting balance, name, permissions, setbit") - GenesisGenCmd.Flags().StringVarP(&ValidatorsPathFlag, "validators", "", "", "path to validators.csv with the following params: (pubkey, starting balance, name, permissions, setbit") -} diff --git a/client/cmd/status.go b/client/cmd/status.go index dac02391fa515adfa01291a3172e598dbfb4a314..555e9428d8214eb2a0c5b30a4e35ad041bf629e5 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -38,7 +38,7 @@ func buildStatusCommand() *cobra.Command { // TransactionCmd.PersistentFlags().StringVarP(&clientDo.PubkeyFlag, "pubkey", "", defaultPublicKey(), "specify the public key to sign with (defaults to $BURROW_CLIENT_PUBLIC_KEY)") // TransactionCmd.PersistentFlags().StringVarP(&clientDo.AddrFlag, "addr", "", defaultAddress(), "specify the account address (for which the public key can be found at monax-keys) (default respects $BURROW_CLIENT_ADDRESS)") // TransactionCmd.PersistentFlags().StringVarP(&clientDo.ChainidFlag, "chain-id", "", defaultChainId(), "specify the chainID (default respects $CHAIN_ID)") - // TransactionCmd.PersistentFlags().StringVarP(&clientDo.NonceFlag, "nonce", "", "", "specify the nonce to use for the transaction (should equal the sender account's nonce + 1)") + // TransactionCmd.PersistentFlags().StringVarP(&clientDo.NonceFlag, "sequence", "", "", "specify the sequence to use for the transaction (should equal the sender account's sequence + 1)") // // TransactionCmd.PersistentFlags().BoolVarP(&clientDo.SignFlag, "sign", "s", false, "sign the transaction using the monax-keys daemon") // TransactionCmd.PersistentFlags().BoolVarP(&clientDo.BroadcastFlag, "broadcast", "b", true, "broadcast the transaction to the blockchain") diff --git a/client/cmd/transaction.go b/client/cmd/transaction.go index 21b97e65795a46ce4cb499e768529f15e7ca43a1..a5a1c0ac1ad231de5d1c823a3ebd0888fc79b938 100644 --- a/client/cmd/transaction.go +++ b/client/cmd/transaction.go @@ -25,7 +25,7 @@ import ( func buildTransactionCommand() *cobra.Command { // Transaction command has subcommands send, name, call, bond, - // unbond, rebond, permissions. Dupeout transaction is not accessible through the command line. + // unbond, rebond, permissions. transactionCmd := &cobra.Command{ Use: "tx", Short: "burrow-client tx formulates and signs a transaction to a chain", @@ -145,8 +145,7 @@ func addTransactionPersistentFlags(transactionCmd *cobra.Command) { transactionCmd.PersistentFlags().StringVarP(&clientDo.NodeAddrFlag, "node-addr", "", defaultNodeRpcAddress(), "set the burrow node rpc server address (default respects $BURROW_CLIENT_NODE_ADDRESS)") transactionCmd.PersistentFlags().StringVarP(&clientDo.PubkeyFlag, "pubkey", "", defaultPublicKey(), "specify the public key to sign with (defaults to $BURROW_CLIENT_PUBLIC_KEY)") transactionCmd.PersistentFlags().StringVarP(&clientDo.AddrFlag, "addr", "", defaultAddress(), "specify the account address (for which the public key can be found at monax-keys) (default respects $BURROW_CLIENT_ADDRESS)") - transactionCmd.PersistentFlags().StringVarP(&clientDo.ChainidFlag, "chain-id", "", defaultChainId(), "specify the chainID (default respects $CHAIN_ID)") - transactionCmd.PersistentFlags().StringVarP(&clientDo.NonceFlag, "nonce", "", "", "specify the nonce to use for the transaction (should equal the sender account's nonce + 1)") + transactionCmd.PersistentFlags().StringVarP(&clientDo.NonceFlag, "sequence", "", "", "specify the sequence to use for the transaction (should equal the sender account's sequence + 1)") // transactionCmd.PersistentFlags().BoolVarP(&clientDo.SignFlag, "sign", "s", false, "sign the transaction using the monax-keys daemon") transactionCmd.PersistentFlags().BoolVarP(&clientDo.BroadcastFlag, "broadcast", "b", true, "broadcast the transaction to the blockchain") @@ -156,10 +155,6 @@ func addTransactionPersistentFlags(transactionCmd *cobra.Command) { //------------------------------------------------------------------------------ // Defaults -func defaultChainId() string { - return setDefaultString("CHAIN_ID", "") -} - func defaultKeyDaemonAddress() string { return setDefaultString("BURROW_CLIENT_SIGN_ADDRESS", "http://127.0.0.1:4767") } @@ -180,10 +175,6 @@ func defaultAddress() string { // Helper functions func assertParameters(cmd *cobra.Command, args []string) { - if clientDo.ChainidFlag == "" { - util.Fatalf(`Please provide a chain id either through the flag --chain-id or environment variable $CHAIN_ID.`) - } - if !strings.HasPrefix(clientDo.NodeAddrFlag, "tcp://") && !strings.HasPrefix(clientDo.NodeAddrFlag, "unix://") { // TODO: [ben] go-rpc will deprecate reformatting; also it is bad practice to auto-correct for this; diff --git a/client/methods/call.go b/client/methods/call.go index 9df3a51ac5921fd43ae62d52e0fc3c52c499e783..84eed39085f35d9e4bb815d9fd521c8144b99e73 100644 --- a/client/methods/call.go +++ b/client/methods/call.go @@ -19,16 +19,15 @@ import ( "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/client/rpc" - "github.com/hyperledger/burrow/definitions" "github.com/hyperledger/burrow/keys" ) -func Call(do *definitions.ClientDo) error { +func Call(do *client.Do) error { // construct two clients to call out to keys server and // blockchain node. logger, err := loggerFromClientDo(do, "Call") if err != nil { - return fmt.Errorf("Could not generate logging config from ClientDo: %s", err) + return fmt.Errorf("Could not generate logging config from Do: %s", err) } burrowKeyClient := keys.NewBurrowKeyClient(do.SignAddrFlag, logger) burrowNodeClient := client.NewBurrowNodeClient(do.NodeAddrFlag, logger) @@ -39,10 +38,14 @@ func Call(do *definitions.ClientDo) error { if err != nil { return fmt.Errorf("Failed on forming Call Transaction: %s", err) } + _, chainID, _, err := burrowNodeClient.ChainId() + if err != nil { + return err + } // TODO: [ben] we carry over the sign bool, but always set it to true, // as we move away from and deprecate the api that allows sending unsigned // transactions and relying on (our) receiving node to sign it. - txResult, err := rpc.SignAndBroadcast(do.ChainidFlag, burrowNodeClient, burrowKeyClient, + txResult, err := rpc.SignAndBroadcast(chainID, burrowNodeClient, burrowKeyClient, callTransaction, true, do.BroadcastFlag, do.WaitFlag) if err != nil { diff --git a/client/methods/helpers.go b/client/methods/helpers.go index 2e8bc6fcaddb11eac66c55f8992039a9beec8feb..6cdb8b3aedaca504d7ec803c250f9e1265686e0a 100644 --- a/client/methods/helpers.go +++ b/client/methods/helpers.go @@ -15,10 +15,10 @@ package methods import ( + "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/client/rpc" - "github.com/hyperledger/burrow/core" - "github.com/hyperledger/burrow/definitions" "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/logging/lifecycle" logging_types "github.com/hyperledger/burrow/logging/types" ) @@ -45,17 +45,12 @@ func unpackSignAndBroadcast(result *rpc.TxResult, logger logging_types.InfoTrace logging.InfoMsg(logger, "SignAndBroadcast result") } -func loggerFromClientDo(do *definitions.ClientDo, scope string) (logging_types.InfoTraceLogger, error) { - lc, err := core.LoadLoggingConfigFromClientDo(do) - if err != nil { - return nil, err - } - logger, err := lifecycle.NewLoggerFromLoggingConfig(lc) +func loggerFromClientDo(do *client.Do, scope string) (logging_types.InfoTraceLogger, error) { + logger, err := lifecycle.NewLoggerFromLoggingConfig(config.DefaultClientLoggingConfig()) if err != nil { return nil, err } logger = logging.WithScope(logger, scope) lifecycle.CaptureStdlibLogOutput(logger) - lifecycle.CaptureTendermintLog15Output(logger) return logger, nil } diff --git a/client/methods/send.go b/client/methods/send.go index 9437a9dfd754826699e2e7c06606da1f4b0f1e2b..9527bc75a5112f446222aaa541088d38d9ebacb6 100644 --- a/client/methods/send.go +++ b/client/methods/send.go @@ -19,16 +19,15 @@ import ( "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/client/rpc" - "github.com/hyperledger/burrow/definitions" "github.com/hyperledger/burrow/keys" ) -func Send(do *definitions.ClientDo) error { +func Send(do *client.Do) error { // construct two clients to call out to keys server and // blockchain node. logger, err := loggerFromClientDo(do, "Send") if err != nil { - return fmt.Errorf("Could not generate logging config from ClientDo: %s", err) + return fmt.Errorf("Could not generate logging config from Do: %s", err) } burrowKeyClient := keys.NewBurrowKeyClient(do.SignAddrFlag, logger) burrowNodeClient := client.NewBurrowNodeClient(do.NodeAddrFlag, logger) @@ -38,10 +37,14 @@ func Send(do *definitions.ClientDo) error { if err != nil { fmt.Errorf("Failed on forming Send Transaction: %s", err) } + _, chainID, _, err := burrowNodeClient.ChainId() + if err != nil { + return err + } // TODO: [ben] we carry over the sign bool, but always set it to true, // as we move away from and deprecate the api that allows sending unsigned // transactions and relying on (our) receiving node to sign it. - txResult, err := rpc.SignAndBroadcast(do.ChainidFlag, burrowNodeClient, burrowKeyClient, + txResult, err := rpc.SignAndBroadcast(chainID, burrowNodeClient, burrowKeyClient, sendTransaction, true, do.BroadcastFlag, do.WaitFlag) if err != nil { return fmt.Errorf("Failed on signing (and broadcasting) transaction: %s", err) diff --git a/client/methods/status.go b/client/methods/status.go index d14c3290b6367baab80ef5bdc3df3cbb098dcd8b..14a8be609eb100ca2cdecc3652f6af840e45cd6d 100644 --- a/client/methods/status.go +++ b/client/methods/status.go @@ -18,13 +18,12 @@ import ( "fmt" "github.com/hyperledger/burrow/client" - "github.com/hyperledger/burrow/definitions" ) -func Status(do *definitions.ClientDo) error { +func Status(do *client.Do) error { logger, err := loggerFromClientDo(do, "Status") if err != nil { - return fmt.Errorf("Could not generate logging config from ClientDo: %s", err) + return fmt.Errorf("Could not generate logging config from Do: %s", err) } burrowNodeClient := client.NewBurrowNodeClient(do.NodeAddrFlag, logger) genesisHash, validatorPublicKey, latestBlockHash, latestBlockHeight, latestBlockTime, err := burrowNodeClient.Status() diff --git a/client/mock/client_mock.go b/client/mock/client_mock.go index c6a0d12b8bb5f95a9bf98e1c1b5c28a1c53e02af..9ae5f1923c53bc7f17aca2fbd307ddbd52e05a42 100644 --- a/client/mock/client_mock.go +++ b/client/mock/client_mock.go @@ -15,26 +15,24 @@ package mock import ( - "github.com/tendermint/go-crypto" - - acc "github.com/hyperledger/burrow/account" + acm "github.com/hyperledger/burrow/account" . "github.com/hyperledger/burrow/client" - consensus_types "github.com/hyperledger/burrow/consensus/types" - core_types "github.com/hyperledger/burrow/core/types" "github.com/hyperledger/burrow/logging/loggers" logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/txs" + "github.com/tendermint/go-crypto" ) var _ NodeClient = (*MockNodeClient)(nil) type MockNodeClient struct { - accounts map[string]*acc.Account + accounts map[string]*acm.ConcreteAccount } func NewMockNodeClient() *MockNodeClient { return &MockNodeClient{ - accounts: make(map[string]*acc.Account), + accounts: make(map[string]*acm.ConcreteAccount), } } @@ -42,8 +40,7 @@ func (mock *MockNodeClient) Broadcast(transaction txs.Tx) (*txs.Receipt, error) // make zero transaction receipt txReceipt := &txs.Receipt{ TxHash: make([]byte, 20), - CreatesContract: 0, - ContractAddr: make([]byte, 20), + CreatesContract: false, } return txReceipt, nil } @@ -52,68 +49,60 @@ func (mock *MockNodeClient) DeriveWebsocketClient() (nodeWsClient NodeWebsocketC return nil, nil } -func (mock *MockNodeClient) GetAccount(address []byte) (*acc.Account, error) { +func (mock *MockNodeClient) GetAccount(address acm.Address) (acm.Account, error) { // make zero account - var zero [32]byte - copyAddressBytes := make([]byte, len(address)) - copy(copyAddressBytes, address) - account := &acc.Account{ - Address: copyAddressBytes, - PubKey: crypto.PubKey(crypto.PubKeyEd25519(zero)), - Sequence: 0, - Balance: 0, + return acm.ConcreteAccount{ + Address: address, + PublicKey: acm.PublicKeyFromGoCryptoPubKey(crypto.PubKeyEd25519{}.Wrap()), Code: make([]byte, 0), StorageRoot: make([]byte, 0), - } - return account, nil + }.Account(), nil } -func (mock *MockNodeClient) MockAddAccount(account *acc.Account) { +func (mock *MockNodeClient) MockAddAccount(account *acm.ConcreteAccount) { addressString := string(account.Address[:]) mock.accounts[addressString] = account.Copy() } -func (mock *MockNodeClient) Status() (ChainId []byte, - ValidatorPublicKey []byte, LatestBlockHash []byte, - BlockHeight int, LatestBlockTime int64, err error) { - // make zero account - var zero [32]byte - ed25519 := crypto.PubKeyEd25519(zero) - pub := crypto.PubKey(ed25519) - +func (mock *MockNodeClient) Status() (ChainId []byte, ValidatorPublicKey []byte, LatestBlockHash []byte, + BlockHeight uint64, LatestBlockTime int64, err error) { // fill return values ChainId = make([]byte, 64) LatestBlockHash = make([]byte, 64) - ValidatorPublicKey = pub.Bytes() + ValidatorPublicKey = crypto.PubKeyEd25519{}.Wrap().Bytes() BlockHeight = 0 LatestBlockTime = 0 return } // QueryContract executes the contract code at address with the given data -func (mock *MockNodeClient) QueryContract(callerAddress, calleeAddress, data []byte) (ret []byte, gasUsed int64, err error) { +func (mock *MockNodeClient) QueryContract(callerAddress, calleeAddress acm.Address, + data []byte) (ret []byte, gasUsed uint64, err error) { + // return zero ret = make([]byte, 0) - return ret, 0, nil + return } // QueryContractCode executes the contract code at address with the given data but with provided code -func (mock *MockNodeClient) QueryContractCode(address, code, data []byte) (ret []byte, gasUsed int64, err error) { +func (mock *MockNodeClient) QueryContractCode(address acm.Address, code, + data []byte) (ret []byte, gasUsed uint64, err error) { // return zero ret = make([]byte, 0) - return ret, 0, nil + return } -func (mock *MockNodeClient) DumpStorage(address []byte) (storage *core_types.Storage, err error) { - return nil, nil +func (mock *MockNodeClient) DumpStorage(address acm.Address) (storage *rpc.ResultDumpStorage, err error) { + return } -func (mock *MockNodeClient) GetName(name string) (owner []byte, data string, expirationBlock int, err error) { - return nil, "", 0, nil +func (mock *MockNodeClient) GetName(name string) (owner acm.Address, data string, expirationBlock uint64, err error) { + return } -func (mock *MockNodeClient) ListValidators() (blockHeight int, bondedValidators, unbondingValidators []consensus_types.Validator, err error) { - return 0, nil, nil, nil +func (mock *MockNodeClient) ListValidators() (blockHeight uint64, bondedValidators, + unbondingValidators []acm.Validator, err error) { + return } func (mock *MockNodeClient) Logger() logging_types.InfoTraceLogger { diff --git a/client/node_client.go b/client/node_client.go index a7d49a14568f92c5d19cf07e28ba93f921da2b2b..145adc5ffa59feaeb3fb85111c20ff3443ac5bc0 100644 --- a/client/node_client.go +++ b/client/node_client.go @@ -16,19 +16,14 @@ package client import ( "fmt" - // "strings" - "github.com/tendermint/go-rpc/client" - - acc "github.com/hyperledger/burrow/account" - consensus_types "github.com/hyperledger/burrow/consensus/types" - core_types "github.com/hyperledger/burrow/core/types" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/logging" logging_types "github.com/hyperledger/burrow/logging/types" - tendermint_client "github.com/hyperledger/burrow/rpc/tendermint/client" - tendermint_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" + "github.com/hyperledger/burrow/rpc" + tendermint_client "github.com/hyperledger/burrow/rpc/tm/client" "github.com/hyperledger/burrow/txs" - tmLog15 "github.com/tendermint/log15" + "github.com/tendermint/tendermint/rpc/lib/client" ) type NodeClient interface { @@ -36,14 +31,14 @@ type NodeClient interface { DeriveWebsocketClient() (nodeWsClient NodeWebsocketClient, err error) Status() (ChainId []byte, ValidatorPublicKey []byte, LatestBlockHash []byte, - LatestBlockHeight int, LatestBlockTime int64, err error) - GetAccount(address []byte) (*acc.Account, error) - QueryContract(callerAddress, calleeAddress, data []byte) (ret []byte, gasUsed int64, err error) - QueryContractCode(address, code, data []byte) (ret []byte, gasUsed int64, err error) + LatestBlockHeight uint64, LatestBlockTime int64, err error) + GetAccount(address acm.Address) (acm.Account, error) + QueryContract(callerAddress, calleeAddress acm.Address, data []byte) (ret []byte, gasUsed uint64, err error) + QueryContractCode(address acm.Address, code, data []byte) (ret []byte, gasUsed uint64, err error) - DumpStorage(address []byte) (storage *core_types.Storage, err error) - GetName(name string) (owner []byte, data string, expirationBlock int, err error) - ListValidators() (blockHeight int, bondedValidators, unbondingValidators []consensus_types.Validator, err error) + DumpStorage(address acm.Address) (storage *rpc.ResultDumpStorage, err error) + GetName(name string) (owner acm.Address, data string, expirationBlock uint64, err error) + ListValidators() (blockHeight uint64, bondedValidators, unbondingValidators []acm.Validator, err error) // Logging context for this NodeClient Logger() logging_types.InfoTraceLogger @@ -53,7 +48,7 @@ type NodeWebsocketClient interface { Subscribe(eventId string) error Unsubscribe(eventId string) error - WaitForConfirmation(tx txs.Tx, chainId string, inputAddr []byte) (chan Confirmation, error) + WaitForConfirmation(tx txs.Tx, chainId string, inputAddr acm.Address) (chan Confirmation, error) Close() } @@ -76,13 +71,6 @@ func NewBurrowNodeClient(rpcString string, logger logging_types.InfoTraceLogger) } } -// Note [Ben]: This is a hack to silence Tendermint logger from tendermint/go-rpc -// it needs to be initialised before go-rpc, hence it's placement here. -func init() { - h := tmLog15.LvlFilterHandler(tmLog15.LvlWarn, tmLog15.StdoutHandler) - tmLog15.Root().SetHandler(h) -} - //------------------------------------------------------------------------------------ // broadcast to blockchain node @@ -92,7 +80,7 @@ func (burrowNodeClient *burrowNodeClient) Broadcast(tx txs.Tx) (*txs.Receipt, er if err != nil { return nil, err } - return &receipt, nil + return receipt, nil } func (burrowNodeClient *burrowNodeClient) DeriveWebsocketClient() (nodeWsClient NodeWebsocketClient, err error) { @@ -133,13 +121,15 @@ func (burrowNodeClient *burrowNodeClient) DeriveWebsocketClient() (nodeWsClient // Status returns the ChainId (GenesisHash), validator's PublicKey, latest block hash // the block height and the latest block time. -func (burrowNodeClient *burrowNodeClient) Status() (GenesisHash []byte, ValidatorPublicKey []byte, LatestBlockHash []byte, LatestBlockHeight int, LatestBlockTime int64, err error) { +func (burrowNodeClient *burrowNodeClient) Status() (GenesisHash []byte, ValidatorPublicKey []byte, + LatestBlockHash []byte, LatestBlockHeight uint64, LatestBlockTime int64, err error) { + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) res, err := tendermint_client.Status(client) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get status: %s", burrowNodeClient.broadcastRPC, err.Error()) - return nil, nil, nil, int(0), int64(0), err + return } // unwrap return results @@ -169,19 +159,23 @@ func (burrowNodeClient *burrowNodeClient) ChainId() (ChainName, ChainId string, // QueryContract executes the contract code at address with the given data // NOTE: there is no check on the caller; -func (burrowNodeClient *burrowNodeClient) QueryContract(callerAddress, calleeAddress, data []byte) (ret []byte, gasUsed int64, err error) { +func (burrowNodeClient *burrowNodeClient) QueryContract(callerAddress, calleeAddress acm.Address, + data []byte) (ret []byte, gasUsed uint64, err error) { + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) callResult, err := tendermint_client.Call(client, callerAddress, calleeAddress, data) if err != nil { err = fmt.Errorf("Error (%v) connnecting to node (%s) to query contract at (%X) with data (%X)", err.Error(), burrowNodeClient.broadcastRPC, calleeAddress, data) - return nil, int64(0), err + return } return callResult.Return, callResult.GasUsed, nil } // QueryContractCode executes the contract code at address with the given data but with provided code -func (burrowNodeClient *burrowNodeClient) QueryContractCode(address, code, data []byte) (ret []byte, gasUsed int64, err error) { +func (burrowNodeClient *burrowNodeClient) QueryContractCode(address acm.Address, code, + data []byte) (ret []byte, gasUsed uint64, err error) { + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) // TODO: [ben] Call and CallCode have an inconsistent signature; it makes sense for both to only // have a single address that is the contract to query. @@ -189,13 +183,13 @@ func (burrowNodeClient *burrowNodeClient) QueryContractCode(address, code, data if err != nil { err = fmt.Errorf("Error connnecting to node (%s) to query contract code at (%X) with data (%X) and code (%X)", burrowNodeClient.broadcastRPC, address, data, code, err.Error()) - return nil, int64(0), err + return nil, uint64(0), err } return callResult.Return, callResult.GasUsed, nil } // GetAccount returns a copy of the account -func (burrowNodeClient *burrowNodeClient) GetAccount(address []byte) (*acc.Account, error) { +func (burrowNodeClient *burrowNodeClient) GetAccount(address acm.Address) (acm.Account, error) { client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) account, err := tendermint_client.GetAccount(client, address) if err != nil { @@ -208,35 +202,32 @@ func (burrowNodeClient *burrowNodeClient) GetAccount(address []byte) (*acc.Accou return nil, err } - return account.Copy(), nil + return account, nil } -// DumpStorage returns the full storage for an account. -func (burrowNodeClient *burrowNodeClient) DumpStorage(address []byte) (storage *core_types.Storage, err error) { +// DumpStorage returns the full storage for an acm. +func (burrowNodeClient *burrowNodeClient) DumpStorage(address acm.Address) (*rpc.ResultDumpStorage, error) { client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) resultStorage, err := tendermint_client.DumpStorage(client, address) if err != nil { - err = fmt.Errorf("Error connecting to node (%s) to get storage for account (%X): %s", + return nil, fmt.Errorf("error connecting to node (%s) to get storage for account (%X): %s", burrowNodeClient.broadcastRPC, address, err.Error()) - return nil, err } - // UnwrapResultDumpStorage is an inefficient full deep copy, - // to transform the type to /core/types.Storage - // TODO: removing go-wire and go-rpc allows us to collapse these types - storage = tendermint_types.UnwrapResultDumpStorage(resultStorage) - return + return resultStorage, nil } //-------------------------------------------------------------------------------------------- // Name registry -func (burrowNodeClient *burrowNodeClient) GetName(name string) (owner []byte, data string, expirationBlock int, err error) { +func (burrowNodeClient *burrowNodeClient) GetName(name string) (owner acm.Address, data string, + expirationBlock uint64, err error) { + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) entryResult, err := tendermint_client.GetName(client, name) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get name registrar entry for name (%s)", burrowNodeClient.broadcastRPC, name) - return nil, "", 0, err + return acm.ZeroAddress, "", 0, err } // unwrap return results owner = entryResult.Owner @@ -247,19 +238,25 @@ func (burrowNodeClient *burrowNodeClient) GetName(name string) (owner []byte, da //-------------------------------------------------------------------------------------------- -func (burrowNodeClient *burrowNodeClient) ListValidators() (blockHeight int, - bondedValidators []consensus_types.Validator, unbondingValidators []consensus_types.Validator, err error) { +func (burrowNodeClient *burrowNodeClient) ListValidators() (blockHeight uint64, + bondedValidators, unbondingValidators []acm.Validator, err error) { + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) validatorsResult, err := tendermint_client.ListValidators(client) if err != nil { - err = fmt.Errorf("Error connecting to node (%s) to get validators", - burrowNodeClient.broadcastRPC) - return 0, nil, nil, err + err = fmt.Errorf("Error connecting to node (%s) to get validators", burrowNodeClient.broadcastRPC) + return } // unwrap return results blockHeight = validatorsResult.BlockHeight - bondedValidators = validatorsResult.BondedValidators - unbondingValidators = validatorsResult.UnbondingValidators + bondedValidators = make([]acm.Validator, len(validatorsResult.BondedValidators)) + for i, cv := range validatorsResult.BondedValidators { + bondedValidators[i] = cv.Validator() + } + unbondingValidators = make([]acm.Validator, len(validatorsResult.UnbondingValidators)) + for i, cv := range validatorsResult.UnbondingValidators { + unbondingValidators[i] = cv.Validator() + } return } diff --git a/client/rpc/client.go b/client/rpc/client.go index 5a3652e368745f33400cf57cced5d8870a20843d..8e8f98e6af858732638c98b26bb4213338c9f8c5 100644 --- a/client/rpc/client.go +++ b/client/rpc/client.go @@ -19,8 +19,9 @@ import ( "fmt" "strconv" - ptypes "github.com/hyperledger/burrow/permission/types" + ptypes "github.com/hyperledger/burrow/permission" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/txs" @@ -30,8 +31,8 @@ import ( // core functions with string args. // validates strings and forms transaction -func Send(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, nonceS string) (*txs.SendTx, error) { - pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS) +func Send(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, sequenceS string) (*txs.SendTx, error) { + pub, amt, sequence, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, sequenceS) if err != nil { return nil, err } @@ -40,35 +41,40 @@ func Send(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, return nil, fmt.Errorf("destination address must be given with --to flag") } - toAddrBytes, err := hex.DecodeString(toAddr) + toAddress, err := addressFromHexString(toAddr) if err != nil { - return nil, fmt.Errorf("toAddr is bad hex: %v", err) + return nil, err } tx := txs.NewSendTx() - tx.AddInputWithNonce(pub, amt, int(nonce)) - tx.AddOutput(toAddrBytes, amt) + tx.AddInputWithSequence(pub, amt, sequence) + tx.AddOutput(toAddress, amt) return tx, nil } -func Call(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, nonceS, gasS, feeS, data string) (*txs.CallTx, error) { - pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS) +func Call(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, toAddr, amtS, sequenceS, gasS, feeS, data string) (*txs.CallTx, error) { + pub, amt, sequence, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, sequenceS) if err != nil { return nil, err } - toAddrBytes, err := hex.DecodeString(toAddr) - if err != nil { - return nil, fmt.Errorf("toAddr is bad hex: %v", err) + var toAddress *acm.Address + + if toAddr != "" { + address, err := addressFromHexString(toAddr) + if err != nil { + return nil, fmt.Errorf("toAddr is bad hex: %v", err) + } + toAddress = &address } - fee, err := strconv.ParseInt(feeS, 10, 64) + fee, err := strconv.ParseUint(feeS, 10, 64) if err != nil { return nil, fmt.Errorf("fee is misformatted: %v", err) } - gas, err := strconv.ParseInt(gasS, 10, 64) + gas, err := strconv.ParseUint(gasS, 10, 64) if err != nil { return nil, fmt.Errorf("gas is misformatted: %v", err) } @@ -78,31 +84,31 @@ func Call(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, return nil, fmt.Errorf("data is bad hex: %v", err) } - tx := txs.NewCallTxWithNonce(pub, toAddrBytes, dataBytes, amt, gas, fee, int(nonce)) + tx := txs.NewCallTxWithSequence(pub, toAddress, dataBytes, amt, gas, fee, sequence) return tx, nil } -func Name(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, nonceS, feeS, name, data string) (*txs.NameTx, error) { - pub, amt, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, nonceS) +func Name(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, sequenceS, feeS, name, data string) (*txs.NameTx, error) { + pub, amt, sequence, err := checkCommon(nodeClient, keyClient, pubkey, addr, amtS, sequenceS) if err != nil { return nil, err } - fee, err := strconv.ParseInt(feeS, 10, 64) + fee, err := strconv.ParseUint(feeS, 10, 64) if err != nil { return nil, fmt.Errorf("fee is misformatted: %v", err) } - tx := txs.NewNameTxWithNonce(pub, name, data, amt, fee, int(nonce)) + tx := txs.NewNameTxWithSequence(pub, name, data, amt, fee, sequence) return tx, nil } -func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addrS, nonceS, permFunc string, argsS []string) (*txs.PermissionsTx, error) { - pub, _, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addrS, "0", nonceS) +func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addrS, sequenceS, permFunc string, argsS []string) (*txs.PermissionsTx, error) { + pub, _, sequence, err := checkCommon(nodeClient, keyClient, pubkey, addrS, "0", sequenceS) if err != nil { return nil, err } - var args ptypes.PermArgs + var args *ptypes.PermArgs switch permFunc { case "setBase": addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) @@ -120,13 +126,13 @@ func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, } else { return nil, fmt.Errorf("Unknown value %s", argsS[2]) } - args = &ptypes.SetBaseArgs{addr, pF, value} + args = ptypes.SetBaseArgs(addr, pF, value) case "unsetBase": addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) if err != nil { return nil, err } - args = &ptypes.UnsetBaseArgs{addr, pF} + args = ptypes.UnsetBaseArgs(addr, pF) case "setGlobal": pF, err := ptypes.PermStringToFlag(argsS[0]) if err != nil { @@ -140,34 +146,34 @@ func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, } else { return nil, fmt.Errorf("Unknown value %s", argsS[1]) } - args = &ptypes.SetGlobalArgs{pF, value} + args = ptypes.SetGlobalArgs(pF, value) case "addRole": - addr, err := hex.DecodeString(argsS[0]) + address, err := addressFromHexString(argsS[0]) if err != nil { return nil, err } - args = &ptypes.AddRoleArgs{addr, argsS[1]} + args = ptypes.AddRoleArgs(address, argsS[1]) case "removeRole": - addr, err := hex.DecodeString(argsS[0]) + address, err := addressFromHexString(argsS[0]) if err != nil { return nil, err } - args = &ptypes.RmRoleArgs{addr, argsS[1]} + args = ptypes.RemoveRoleArgs(address, argsS[1]) default: - return nil, fmt.Errorf("Invalid permission function for use in PermissionsTx: %s", permFunc) + return nil, fmt.Errorf("invalid permission function for use in PermissionsTx: %s", permFunc) } // args := snativeArgs( - tx := txs.NewPermissionsTxWithNonce(pub, args, int(nonce)) + tx := txs.NewPermissionsTxWithSequence(pub, args, sequence) return tx, nil } -func Bond(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, unbondAddr, amtS, nonceS string) (*txs.BondTx, error) { +func Bond(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, unbondAddr, amtS, sequenceS string) (*txs.BondTx, error) { return nil, fmt.Errorf("Bond Transaction formation to be implemented on 0.12.0") - // pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, "", amtS, nonceS) + // pub, amt, sequence, err := checkCommon(nodeAddr, signAddr, pubkey, "", amtS, sequenceS) // if err != nil { // return nil, err // } - // var pubKey crypto.PubKeyEd25519 + // var pubKey acm.PublicKeyEd25519 // var unbondAddrBytes []byte // if unbondAddr == "" { @@ -186,7 +192,7 @@ func Bond(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, unbond // if err != nil { // return nil, err // } - // tx.AddInputWithNonce(pub, amt, int(nonce)) + // tx.AddInputWithSequence(pub, amt, int(sequence)) // tx.AddOutput(unbondAddrBytes, amt) // return tx, nil @@ -241,7 +247,7 @@ type TxResult struct { Hash []byte // all txs get a hash // only CallTx - Address []byte // only for new contracts + Address *acm.Address // only for new contracts Return []byte Exception string @@ -252,7 +258,8 @@ type TxResult struct { // Preserve func SignAndBroadcast(chainID string, nodeClient client.NodeClient, keyClient keys.KeyClient, tx txs.Tx, sign, broadcast, wait bool) (txResult *TxResult, err error) { - var inputAddr []byte + + var inputAddr acm.Address if sign { inputAddr, tx, err = signTx(keyClient, chainID, tx) if err != nil { @@ -262,7 +269,8 @@ func SignAndBroadcast(chainID string, nodeClient client.NodeClient, keyClient ke if broadcast { if wait { - wsClient, err := nodeClient.DeriveWebsocketClient() + var wsClient client.NodeWebsocketClient + wsClient, err = nodeClient.DeriveWebsocketClient() if err != nil { return nil, err } @@ -270,31 +278,34 @@ func SignAndBroadcast(chainID string, nodeClient client.NodeClient, keyClient ke confirmationChannel, err = wsClient.WaitForConfirmation(tx, chainID, inputAddr) if err != nil { return nil, err - } else { - defer func() { - if err != nil { - // if broadcast threw an error, just return - return - } - confirmation := <-confirmationChannel - if confirmation.Error != nil { - err = fmt.Errorf("Encountered error waiting for event: %s", confirmation.Error) - return - } - if confirmation.Exception != nil { - err = fmt.Errorf("Encountered Exception from chain: %s", confirmation.Exception) - return - } - txResult.BlockHash = confirmation.BlockHash - txResult.Exception = "" - eventDataTx, ok := confirmation.Event.(*txs.EventDataTx) - if !ok { - err = fmt.Errorf("Received wrong event type.") - return - } - txResult.Return = eventDataTx.Return - }() } + defer func() { + if err != nil { + // if broadcast threw an error, just return + return + } + if txResult == nil { + err = fmt.Errorf("txResult unexpectedly not initialised in SignAndBroadcast") + return + } + confirmation := <-confirmationChannel + if confirmation.Error != nil { + err = fmt.Errorf("encountered error waiting for event: %s", confirmation.Error) + return + } + if confirmation.Exception != nil { + err = fmt.Errorf("encountered Exception from chain: %s", confirmation.Exception) + return + } + txResult.BlockHash = confirmation.BlockHash + txResult.Exception = "" + eventDataTx := confirmation.EventDataTx + if eventDataTx == nil { + err = fmt.Errorf("EventDataTx was nil") + return + } + txResult.Return = eventDataTx.Return + }() } var receipt *txs.Receipt @@ -309,10 +320,19 @@ func SignAndBroadcast(chainID string, nodeClient client.NodeClient, keyClient ke // reasonable to get this returned from the chain directly. Alternatively, // the benefit is that the we don't need to trust the chain node if tx_, ok := tx.(*txs.CallTx); ok { - if len(tx_.Address) == 0 { - txResult.Address = txs.NewContractAddress(tx_.Input.Address, tx_.Input.Sequence) + if tx_.Address == nil { + address := acm.NewContractAddress(tx_.Input.Address, tx_.Input.Sequence) + txResult.Address = &address } } } return } + +func addressFromHexString(addrString string) (acm.Address, error) { + addrBytes, err := hex.DecodeString(addrString) + if err != nil { + return acm.Address{}, err + } + return acm.AddressFromBytes(addrBytes) +} diff --git a/client/rpc/client_test.go b/client/rpc/client_test.go index 603fe793fe2f7e32e9c355f8c5150f797428283c..a8cd5df5772da1c636204d6a38b31b7eb8ba302f 100644 --- a/client/rpc/client_test.go +++ b/client/rpc/client_test.go @@ -22,6 +22,7 @@ import ( mockclient "github.com/hyperledger/burrow/client/mock" mockkeys "github.com/hyperledger/burrow/keys/mock" + "github.com/stretchr/testify/require" ) func Test(t *testing.T) { @@ -40,7 +41,7 @@ func testSend(t *testing.T, nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { // generate an ED25519 key and ripemd160 address - addressString := fmt.Sprintf("%X", keyClient.NewKey()) + addressString := keyClient.NewKey().String() // Public key can be queried from mockKeyClient.PublicKey(address) // but here we let the transaction factory retrieve the public key // which will then also overwrite the address we provide the function. @@ -48,18 +49,15 @@ func testSend(t *testing.T, // to address in generated transation. publicKeyString := "" // generate an additional address to send amount to - toAddressString := fmt.Sprintf("%X", keyClient.NewKey()) + toAddressString := keyClient.NewKey().String() // set an amount to transfer amountString := "1000" - // unset nonce so that we retrieve nonce from account - nonceString := "" + // unset sequence so that we retrieve sequence from account + sequenceString := "" _, err := Send(nodeClient, keyClient, publicKeyString, addressString, - toAddressString, amountString, nonceString) - if err != nil { - t.Logf("Error in SendTx: %s", err) - t.Fail() - } + toAddressString, amountString, sequenceString) + require.NoError(t, err, "Error in Send") // assert.NotEqual(t, txSend) // TODO: test content of Transaction } @@ -68,7 +66,7 @@ func testCall(t *testing.T, nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { // generate an ED25519 key and ripemd160 address - addressString := fmt.Sprintf("%X", keyClient.NewKey()) + addressString := keyClient.NewKey().String() // Public key can be queried from mockKeyClient.PublicKey(address) // but here we let the transaction factory retrieve the public key // which will then also overwrite the address we provide the function. @@ -76,11 +74,11 @@ func testCall(t *testing.T, // to address in generated transation. publicKeyString := "" // generate an additional address to send amount to - toAddressString := fmt.Sprintf("%X", keyClient.NewKey()) + toAddressString := keyClient.NewKey().String() // set an amount to transfer amountString := "1000" - // unset nonce so that we retrieve nonce from account - nonceString := "" + // unset sequence so that we retrieve sequence from account + sequenceString := "" // set gas gasString := "1000" // set fee @@ -89,7 +87,7 @@ func testCall(t *testing.T, dataString := fmt.Sprintf("%X", "We are DOUG.") _, err := Call(nodeClient, keyClient, publicKeyString, addressString, - toAddressString, amountString, nonceString, gasString, feeString, dataString) + toAddressString, amountString, sequenceString, gasString, feeString, dataString) if err != nil { t.Logf("Error in CallTx: %s", err) t.Fail() @@ -101,7 +99,7 @@ func testName(t *testing.T, nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { // generate an ED25519 key and ripemd160 address - addressString := fmt.Sprintf("%X", keyClient.NewKey()) + addressString := keyClient.NewKey().String() // Public key can be queried from mockKeyClient.PublicKey(address) // but here we let the transaction factory retrieve the public key // which will then also overwrite the address we provide the function. @@ -110,8 +108,8 @@ func testName(t *testing.T, publicKeyString := "" // set an amount to transfer amountString := "1000" - // unset nonce so that we retrieve nonce from account - nonceString := "" + // unset sequence so that we retrieve sequence from account + sequenceString := "" // set fee feeString := "100" // set data @@ -120,7 +118,7 @@ func testName(t *testing.T, nameString := "DOUG" _, err := Name(nodeClient, keyClient, publicKeyString, addressString, - amountString, nonceString, feeString, nameString, dataString) + amountString, sequenceString, feeString, nameString, dataString) if err != nil { t.Logf("Error in NameTx: %s", err) t.Fail() @@ -132,7 +130,7 @@ func testPermissions(t *testing.T, nodeClient *mockclient.MockNodeClient, keyClient *mockkeys.MockKeyClient) { // generate an ED25519 key and ripemd160 address - addressString := fmt.Sprintf("%X", keyClient.NewKey()) + addressString := keyClient.NewKey().String() // Public key can be queried from mockKeyClient.PublicKey(address) // but here we let the transaction factory retrieve the public key // which will then also overwrite the address we provide the function. @@ -140,12 +138,12 @@ func testPermissions(t *testing.T, // to address in generated transation. publicKeyString := "" // generate an additional address to set permissions for - permAddressString := fmt.Sprintf("%X", keyClient.NewKey()) - // unset nonce so that we retrieve nonce from account - nonceString := "" + permAddressString := keyClient.NewKey().String() + // unset sequence so that we retrieve sequence from account + sequenceString := "" _, err := Permissions(nodeClient, keyClient, publicKeyString, addressString, - nonceString, "setBase", []string{permAddressString, "root", "true"}) + sequenceString, "setBase", []string{permAddressString, "root", "true"}) if err != nil { t.Logf("Error in PermissionsTx: %s", err) t.Fail() diff --git a/client/rpc/client_util.go b/client/rpc/client_util.go index 6711c1ad5cb6cbcd20bb96b43068b0d22ec418b1..1afb93c98bbff50fbd809c0195a0985a13bd8e9d 100644 --- a/client/rpc/client_util.go +++ b/client/rpc/client_util.go @@ -19,12 +19,11 @@ import ( "fmt" "strconv" - "github.com/tendermint/go-crypto" - - acc "github.com/hyperledger/burrow/account" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" ) @@ -34,65 +33,71 @@ import ( // tx has either one input or we default to the first one (ie for send/bond) // TODO: better support for multisig and bonding -func signTx(keyClient keys.KeyClient, chainID string, tx_ txs.Tx) ([]byte, txs.Tx, error) { - signBytesString := fmt.Sprintf("%X", acc.SignBytes(chainID, tx_)) - var inputAddr []byte - var sigED crypto.SignatureEd25519 +func signTx(keyClient keys.KeyClient, chainID string, tx_ txs.Tx) (acm.Address, txs.Tx, error) { + signBytes := acm.SignBytes(chainID, tx_) + var err error switch tx := tx_.(type) { case *txs.SendTx: - inputAddr = tx.Inputs[0].Address - defer func(s *crypto.SignatureEd25519) { tx.Inputs[0].Signature = *s }(&sigED) + signAddress := tx.Inputs[0].Address + tx.Inputs[0].Signature, err = keyClient.Sign(signAddress, signBytes) + return signAddress, tx, err + case *txs.NameTx: - inputAddr = tx.Input.Address - defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) + signAddress := tx.Input.Address + tx.Input.Signature, err = keyClient.Sign(signAddress, signBytes) + return signAddress, tx, err + case *txs.CallTx: - inputAddr = tx.Input.Address - defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) + signAddress := tx.Input.Address + tx.Input.Signature, err = keyClient.Sign(signAddress, signBytes) + return signAddress, tx, err + case *txs.PermissionsTx: - inputAddr = tx.Input.Address - defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) + signAddress := tx.Input.Address + tx.Input.Signature, err = keyClient.Sign(signAddress, signBytes) + return signAddress, tx, err + case *txs.BondTx: - inputAddr = tx.Inputs[0].Address - defer func(s *crypto.SignatureEd25519) { - tx.Signature = *s - tx.Inputs[0].Signature = *s - }(&sigED) + signAddress := tx.Inputs[0].Address + tx.Signature, err = keyClient.Sign(signAddress, signBytes) + tx.Inputs[0].Signature = tx.Signature + return signAddress, tx, err + case *txs.UnbondTx: - inputAddr = tx.Address - defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED) + signAddress := tx.Address + tx.Signature, err = keyClient.Sign(signAddress, signBytes) + return signAddress, tx, err + case *txs.RebondTx: - inputAddr = tx.Address - defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED) - } - sig, err := keyClient.Sign(signBytesString, inputAddr) - if err != nil { - return nil, nil, err + signAddress := tx.Address + tx.Signature, err = keyClient.Sign(signAddress, signBytes) + return signAddress, tx, err + + default: + return acm.ZeroAddress, nil, fmt.Errorf("unknown transaction type for signTx: %#v", tx_) } - // TODO: [ben] temporarily address the type conflict here, to be cleaned up - // with full type restructuring - var sig64 [64]byte - copy(sig64[:], sig) - sigED = crypto.SignatureEd25519(sig64) - return inputAddr, tx_, nil } -func decodeAddressPermFlag(addrS, permFlagS string) (addr []byte, pFlag ptypes.PermFlag, err error) { - if addr, err = hex.DecodeString(addrS); err != nil { +func decodeAddressPermFlag(addrS, permFlagS string) (addr acm.Address, pFlag ptypes.PermFlag, err error) { + var addrBytes []byte + if addrBytes, err = hex.DecodeString(addrS); err != nil { + copy(addr[:], addrBytes) return } - if pFlag, err = ptypes.PermStringToFlag(permFlagS); err != nil { + if pFlag, err = permission.PermStringToFlag(permFlagS); err != nil { return } return } -func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, nonceS string) (pub crypto.PubKey, amt int64, nonce int64, err error) { +func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, amtS, + sequenceS string) (pub acm.PublicKey, amt uint64, sequence uint64, err error) { + if amtS == "" { err = fmt.Errorf("input must specify an amount with the --amt flag") return } - var pubKeyBytes []byte if pubkey == "" && addr == "" { err = fmt.Errorf("at least one of --pubkey or --addr must be given") return @@ -103,11 +108,16 @@ func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, "address", addr, ) } + var pubKeyBytes []byte pubKeyBytes, err = hex.DecodeString(pubkey) if err != nil { err = fmt.Errorf("pubkey is bad hex: %v", err) return } + pub, err = acm.PublicKeyFromBytes(pubKeyBytes) + if err != nil { + return + } } else { // grab the pubkey from monax-keys addressBytes, err2 := hex.DecodeString(addr) @@ -115,47 +125,44 @@ func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, err = fmt.Errorf("Bad hex string for address (%s): %v", addr, err) return } - pubKeyBytes, err2 = keyClient.PublicKey(addressBytes) + address, err2 := acm.AddressFromBytes(addressBytes) + if err2 != nil { + err = fmt.Errorf("Could not convert bytes (%X) to address: %v", addressBytes, err2) + } + pub, err2 = keyClient.PublicKey(address) if err2 != nil { err = fmt.Errorf("Failed to fetch pubkey for address (%s): %v", addr, err2) return } } - if len(pubKeyBytes) == 0 { - err = fmt.Errorf("Error resolving public key") - return - } + var address acm.Address + address = pub.Address() - amt, err = strconv.ParseInt(amtS, 10, 64) + amt, err = strconv.ParseUint(amtS, 10, 64) if err != nil { err = fmt.Errorf("amt is misformatted: %v", err) } - var pubArray [32]byte - copy(pubArray[:], pubKeyBytes) - pub = crypto.PubKeyEd25519(pubArray) - addrBytes := pub.Address() - - if nonceS == "" { + if sequenceS == "" { if nodeClient == nil { - err = fmt.Errorf("input must specify a nonce with the --nonce flag or use --node-addr (or BURROW_CLIENT_NODE_ADDR) to fetch the nonce from a node") + err = fmt.Errorf("input must specify a sequence with the --sequence flag or use --node-addr (or BURROW_CLIENT_NODE_ADDR) to fetch the sequence from a node") return } - // fetch nonce from node - account, err2 := nodeClient.GetAccount(addrBytes) + // fetch sequence from node + account, err2 := nodeClient.GetAccount(address) if err2 != nil { - return pub, amt, nonce, err2 + return pub, amt, sequence, err2 } - nonce = int64(account.Sequence) + 1 - logging.TraceMsg(nodeClient.Logger(), "Fetch nonce from node", - "nonce", nonce, - "account address", addrBytes, + sequence = account.Sequence() + 1 + logging.TraceMsg(nodeClient.Logger(), "Fetch sequence from node", + "sequence", sequence, + "account address", address, ) } else { - nonce, err = strconv.ParseInt(nonceS, 10, 64) + sequence, err = strconv.ParseUint(sequenceS, 10, 64) if err != nil { - err = fmt.Errorf("nonce is misformatted: %v", err) + err = fmt.Errorf("sequence is misformatted: %v", err) return } } diff --git a/client/websocket_client.go b/client/websocket_client.go index 428f78fd66498efb40cdf380c5f71bf0f0eb1870..6f943ae9c9dafceecfbce8fe46f9ab976fcc776e 100644 --- a/client/websocket_client.go +++ b/client/websocket_client.go @@ -19,14 +19,17 @@ import ( "fmt" "time" - logging_types "github.com/hyperledger/burrow/logging/types" - "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-wire" + "encoding/json" + "github.com/hyperledger/burrow/account" + exe_events "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/logging" - tendermint_client "github.com/hyperledger/burrow/rpc/tendermint/client" - ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/rpc" + tendermint_client "github.com/hyperledger/burrow/rpc/tm/client" "github.com/hyperledger/burrow/txs" + "github.com/tendermint/tendermint/rpc/lib/client" + tm_types "github.com/tendermint/tendermint/types" ) const ( @@ -34,10 +37,10 @@ const ( ) type Confirmation struct { - BlockHash []byte - Event txs.EventData - Exception error - Error error + BlockHash []byte + EventDataTx *exe_events.EventDataTx + Exception error + Error error } // NOTE [ben] Compiler check to ensure burrowNodeClient successfully implements @@ -65,37 +68,40 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) Unsubscribe(subscrip // Returns a channel that will receive a confirmation with a result or the exception that // has been confirmed; or an error is returned and the confirmation channel is nil. -func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation(tx txs.Tx, chainId string, inputAddr []byte) (chan Confirmation, error) { - // check no errors are reported on the websocket - if err := burrowNodeWebsocketClient.assertNoErrors(); err != nil { - return nil, err - } +func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation(tx txs.Tx, chainId string, + inputAddr account.Address) (chan Confirmation, error) { // Setup the confirmation channel to be returned confirmationChannel := make(chan Confirmation, 1) var latestBlockHash []byte - eid := txs.EventStringAccInput(inputAddr) + eid := exe_events.EventStringAccInput(inputAddr) if err := burrowNodeWebsocketClient.Subscribe(eid); err != nil { return nil, fmt.Errorf("Error subscribing to AccInput event (%s): %v", eid, err) } - if err := burrowNodeWebsocketClient.Subscribe(txs.EventStringNewBlock()); err != nil { + if err := burrowNodeWebsocketClient.Subscribe(tm_types.EventStringNewBlock()); err != nil { return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err) } // Read the incoming events go func() { var err error for { - resultBytes := <-burrowNodeWebsocketClient.tendermintWebsocket.ResultsCh - result := new(ctypes.BurrowResult) - if wire.ReadJSONPtr(result, resultBytes, &err); err != nil { + response := <-burrowNodeWebsocketClient.tendermintWebsocket.ResponsesCh + if response.Error != nil { + logging.InfoMsg(burrowNodeWebsocketClient.logger, + "Error received on websocket channel", "error", err) + continue + } + result := new(rpc.Result) + + if json.Unmarshal(*response.Result, result); err != nil { // keep calm and carry on - logging.InfoMsg(burrowNodeWebsocketClient.logger, "Failed to unmarshal json bytes for websocket event", - "error", err) + logging.InfoMsg(burrowNodeWebsocketClient.logger, + "Failed to unmarshal json bytes for websocket event", "error", err) continue } - subscription, ok := (*result).(*ctypes.ResultSubscribe) + subscription, ok := result.Unwrap().(*rpc.ResultSubscribe) if ok { // Received confirmation of subscription to event streams // TODO: collect subscription IDs, push into channel and on completion @@ -106,16 +112,16 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( continue } - event, ok := (*result).(*ctypes.ResultEvent) + resultEvent, ok := result.Unwrap().(*rpc.ResultEvent) if !ok { // keep calm and carry on logging.InfoMsg(burrowNodeWebsocketClient.logger, "Failed to cast to ResultEvent for websocket event", - "event", event.Event) + "event", resultEvent.Event) continue } - blockData, ok := event.Data.(txs.EventDataNewBlock) - if ok { + blockData := resultEvent.EventDataNewBlock() + if blockData != nil { latestBlockHash = blockData.Block.Hash() logging.TraceMsg(burrowNodeWebsocketClient.logger, "Registered new block", "block", blockData.Block, @@ -136,47 +142,47 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( // continue // } - if event.Event != eid { + if resultEvent.Event != eid { logging.InfoMsg(burrowNodeWebsocketClient.logger, "Received unsolicited event", - "event_received", event.Event, + "event_received", resultEvent.Event, "event_expected", eid) continue } - data, ok := event.Data.(txs.EventDataTx) - if !ok { + eventDataTx := resultEvent.EventDataTx() + if eventDataTx == nil { // We are on the lookout for EventDataTx confirmationChannel <- Confirmation{ - BlockHash: latestBlockHash, - Event: nil, - Exception: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx"), - Error: nil, + BlockHash: latestBlockHash, + EventDataTx: nil, + Exception: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx"), + Error: nil, } return } - if !bytes.Equal(txs.TxHash(chainId, data.Tx), txs.TxHash(chainId, tx)) { + if !bytes.Equal(txs.TxHash(chainId, eventDataTx.Tx), txs.TxHash(chainId, tx)) { logging.TraceMsg(burrowNodeWebsocketClient.logger, "Received different event", // TODO: consider re-implementing TxID again, or other more clear debug - "received transaction event", txs.TxHash(chainId, data.Tx)) + "received transaction event", txs.TxHash(chainId, eventDataTx.Tx)) continue } - if data.Exception != "" { + if eventDataTx.Exception != "" { confirmationChannel <- Confirmation{ - BlockHash: latestBlockHash, - Event: &data, - Exception: fmt.Errorf("Transaction confirmed with exception: %v", data.Exception), - Error: nil, + BlockHash: latestBlockHash, + EventDataTx: eventDataTx, + Exception: fmt.Errorf("Transaction confirmed with exception: %v", eventDataTx.Exception), + Error: nil, } return } // success, return the full event and blockhash and exit go-routine confirmationChannel <- Confirmation{ - BlockHash: latestBlockHash, - Event: &data, - Exception: nil, - Error: nil, + BlockHash: latestBlockHash, + EventDataTx: eventDataTx, + Exception: nil, + Error: nil, } return } @@ -190,10 +196,10 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( go func() { <-timeout confirmationChannel <- Confirmation{ - BlockHash: nil, - Event: nil, - Exception: nil, - Error: fmt.Errorf("timed out waiting for event"), + BlockHash: nil, + EventDataTx: nil, + Exception: nil, + Error: fmt.Errorf("timed out waiting for event"), } }() return confirmationChannel, nil @@ -204,16 +210,3 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) Close() { burrowNodeWebsocketClient.tendermintWebsocket.Stop() } } - -func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) assertNoErrors() error { - if burrowNodeWebsocketClient.tendermintWebsocket != nil { - select { - case err := <-burrowNodeWebsocketClient.tendermintWebsocket.ErrorsCh: - return err - default: - return nil - } - } else { - return fmt.Errorf("burrow-client has no websocket initialised.") - } -} diff --git a/cmd/burrow.go b/cmd/burrow.go deleted file mode 100644 index 6fb484661b589a7a101064b972e1e30d0fad86dc..0000000000000000000000000000000000000000 --- a/cmd/burrow.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "os" - "strconv" - - "github.com/spf13/cobra" - - "github.com/hyperledger/burrow/definitions" - "github.com/hyperledger/burrow/version" -) - -var BurrowCmd = &cobra.Command{ - Use: "burrow", - Short: "burrow is the node server of a burrow chain", - Long: `burrow is the node server of a burrow chain. burrow combines -a modular consensus engine and application manager to run a chain to suit -your needs. - -Made with <3 by Monax Industries. - -Complete documentation is available at https://monax.io/docs -` + "\nVERSION:\n " + version.GetSemanticVersionString(), - Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, -} - -func Execute() { - do := definitions.NewDo() - AddGlobalFlags(do) - AddCommands(do) - BurrowCmd.Execute() -} - -func AddGlobalFlags(do *definitions.Do) { - BurrowCmd.PersistentFlags().BoolVarP(&do.Verbose, "verbose", "v", - defaultVerbose(), - "verbose output; more output than no output flags; less output than debug level; default respects $BURROW_VERBOSE") - BurrowCmd.PersistentFlags().BoolVarP(&do.Debug, "debug", "d", defaultDebug(), - "debug level output; the most output available for burrow; if it is too chatty use verbose flag; default respects $BURROW_DEBUG") -} - -func AddCommands(do *definitions.Do) { - BurrowCmd.AddCommand(buildServeCommand(do)) -} - -//------------------------------------------------------------------------------ -// Defaults - -// defaultVerbose is set to false unless the BURROW_VERBOSE environment -// variable is set to a parsable boolean. -func defaultVerbose() bool { - return setDefaultBool("BURROW_VERBOSE", false) -} - -// defaultDebug is set to false unless the BURROW_DEBUG environment -// variable is set to a parsable boolean. -func defaultDebug() bool { - return setDefaultBool("BURROW_DEBUG", false) -} - -// setDefaultBool returns the provided default value if the environment variable -// is not set or not parsable as a bool. -func setDefaultBool(environmentVariable string, defaultValue bool) bool { - value := os.Getenv(environmentVariable) - if value != "" { - if parsedValue, err := strconv.ParseBool(value); err == nil { - return parsedValue - } - } - return defaultValue -} - -func setDefaultString(envVar, def string) string { - env := os.Getenv(envVar) - if env != "" { - return env - } - return def -} diff --git a/cmd/burrow/main.go b/cmd/burrow/main.go index e5133ab87e40f117e9800aac3834781c175b3afb..a7bb0629950951287f331364617e2c006ab459ab 100644 --- a/cmd/burrow/main.go +++ b/cmd/burrow/main.go @@ -1,23 +1,274 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package main import ( - commands "github.com/hyperledger/burrow/cmd" + "fmt" + "os" + + "strings" + + "github.com/hyperledger/burrow/config" + "github.com/hyperledger/burrow/config/source" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/genesis/spec" + "github.com/hyperledger/burrow/keys" + logging_config "github.com/hyperledger/burrow/logging/config" + "github.com/hyperledger/burrow/logging/config/presets" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/jawher/mow.cli" ) func main() { - commands.Execute() + burrow := cli.App("burrow", "The EVM smart contract machine with Tendermint consensus") + + genesisOpt := burrow.StringOpt("g genesis", "", + "Use the specified genesis JSON file rather than a key in the main config, use - to read from STDIN") + + configOpt := burrow.StringOpt("c config", "", + "Use the a specified burrow config TOML file") + + burrow.Spec = "[--config=<config file>] [--genesis=<genesis json file>]" + + burrow.Action = func() { + conf := new(config.BurrowConfig) + err := source.EachOf( + burrowConfigProvider(*configOpt), + source.FirstOf( + genesisDocProvider(*genesisOpt, false), + // Try working directory + genesisDocProvider(config.DefaultGenesisDocJSONFileName, true)), + ).Apply(conf) + if err != nil { + fatalf("could not obtain config: %v", err) + } + + kern, err := conf.Kernel() + if err != nil { + fatalf("could not create Burrow kernel: %v", err) + } + + err = kern.Boot() + if err != nil { + fatalf("could not boot Burrow kernel: %v", err) + } + kern.WaitForShutdown() + } + + burrow.Command("spec", + "Build a GenesisSpec that acts as a template for a GenesisDoc and the configure command", + func(cmd *cli.Cmd) { + tomlOpt := cmd.BoolOpt("t toml", false, "Emit GenesisSpec as TOML rather than the "+ + "default JSON") + + fullOpt := cmd.IntOpt("f full-accounts", 1, "Number of preset Full type accounts") + validatorOpt := cmd.IntOpt("v validator-accounts", 0, "Number of preset Validator type accounts") + rootOpt := cmd.IntOpt("r root-accounts", 0, "Number of preset Root type accounts") + developerOpt := cmd.IntOpt("d developer-accounts", 0, "Number of preset Developer type accounts") + participantsOpt := cmd.IntOpt("p participant-accounts", 1, "Number of preset Participant type accounts") + + cmd.Spec = "[--full-accounts] [--validator-accounts] [--root-accounts] [--developer-accounts] [--participant-accounts] [--toml]" + + cmd.Action = func() { + specs := make([]spec.GenesisSpec, 0, *participantsOpt+*fullOpt) + for i := 0; i < *fullOpt; i++ { + specs = append(specs, spec.FullAccount(i)) + } + for i := 0; i < *validatorOpt; i++ { + specs = append(specs, spec.ValidatorAccount(i)) + } + for i := 0; i < *rootOpt; i++ { + specs = append(specs, spec.RootAccount(i)) + } + for i := 0; i < *developerOpt; i++ { + specs = append(specs, spec.DeveloperAccount(i)) + } + for i := 0; i < *participantsOpt; i++ { + specs = append(specs, spec.ParticipantAccount(i)) + } + genesisSpec := spec.MergeGenesisSpecs(specs...) + if *tomlOpt { + os.Stdout.WriteString(source.TOMLString(genesisSpec)) + } else { + os.Stdout.WriteString(source.JSONString(genesisSpec)) + } + } + }) + + burrow.Command("configure", + "Create Burrow configuration by consuming a GenesisDoc or GenesisSpec, creating keys, and emitting the config", + func(cmd *cli.Cmd) { + genesisSpecOpt := cmd.StringOpt("s genesis-spec", "", + "A GenesisSpec to use as a template for a GenesisDoc that will be created along with keys") + + tomlInOpt := cmd.BoolOpt("t toml-in", false, "Consume GenesisSpec/GenesisDoc as TOML "+ + "rather than the JSON default") + + keysUrlOpt := cmd.StringOpt("k keys-url", "", fmt.Sprintf("Provide keys URL, default: %s", + keys.DefaultKeysConfig().URL)) + + jsonOutOpt := cmd.BoolOpt("j json-out", false, "Emit config in JSON rather than TOML "+ + "suitable for further processing or forming a separate genesis.json GenesisDoc") + + genesisDocOpt := cmd.StringOpt("g genesis-doc", "", "GenesisDoc JSON to embed in config") + + validatorIndexOpt := cmd.IntOpt("v validator-index", -1, + "Validator index (in validators list - GenesisSpec or GenesisDoc) from which to set ValidatorAddress") + + loggingOpt := cmd.StringOpt("l logging", "", + "Comma separated list of logging instructions which form a 'program' which is a depth-first "+ + "pre-order of instructions that will build the root logging sink. See 'burrow help' for more information.") + + describeLoggingOpt := cmd.BoolOpt("describe-logging", false, + "Print an exhaustive list of logging instructions available with the --logging option") + + cmd.Spec = "[--keys-url=<keys URL>] [--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " + + "[--validator-index=<index>] [--toml-in] [--json-out] [--logging=<logging program>] [--describe-logging]" + + cmd.Action = func() { + conf := config.DefaultBurrowConfig() + + if *configOpt != "" { + // If explicitly given a config file use it as a base: + err := source.FromTOMLFile(*configOpt, conf) + if err != nil { + fatalf("could not read base config file (as TOML): %v", err) + } + } + + if *describeLoggingOpt { + fmt.Printf("Usage:\n burrow configure -l INSTRUCTION[,...]\n\nBuilds a logging " + + "configuration by constructing a tree of logging sinks assembled from preset instructions " + + "that generate the tree while traversing it.\n\nLogging Instructions:\n") + for _, instruction := range presets.Instructons() { + fmt.Printf(" %-15s\t%s\n", instruction.Name(), instruction.Description()) + } + fmt.Printf("\nExample Usage:\n burrow configure -l include-any,info,stderr\n") + return + } + + if *keysUrlOpt != "" { + conf.Keys.URL = *keysUrlOpt + } + + if *genesisSpecOpt != "" { + genesisSpec := new(spec.GenesisSpec) + err := fromFile(*genesisSpecOpt, *tomlInOpt, genesisSpec) + if err != nil { + fatalf("could not read GenesisSpec: %v", err) + } + keyClient := keys.NewBurrowKeyClient(conf.Keys.URL, loggers.NewNoopInfoTraceLogger()) + conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient) + if err != nil { + fatalf("could not realise GenesisSpec: %v", err) + } + } else if *genesisDocOpt != "" { + genesisDoc := new(genesis.GenesisDoc) + err := fromFile(*genesisSpecOpt, *tomlInOpt, genesisDoc) + if err != nil { + fatalf("could not read GenesisSpec: %v", err) + } + conf.GenesisDoc = genesisDoc + } + + if *validatorIndexOpt > -1 { + if conf.GenesisDoc == nil { + fatalf("Unable to set ValidatorAddress from provided validator-index since no " + + "GenesisDoc/GenesisSpec provided.") + } + if len(conf.GenesisDoc.Validators) < *validatorIndexOpt { + fatalf("validator-index of %v given but only %v validators specified in GenesisDoc", + *validatorIndexOpt, len(conf.GenesisDoc.Validators)) + } + conf.ValidatorAddress = &conf.GenesisDoc.Validators[*validatorIndexOpt].Address + } else if conf.GenesisDoc != nil && len(conf.GenesisDoc.Validators) > 0 { + // Pick first validator otherwise - might want to change this when we support non-validating node + conf.ValidatorAddress = &conf.GenesisDoc.Validators[0].Address + } + + if *loggingOpt != "" { + ops := strings.Split(*loggingOpt, ",") + sinkConfig, err := presets.BuildSinkConfig(ops...) + if err != nil { + fatalf("could not build logging configuration: %v\n\nTo see possible logging "+ + "instructions run:\n burrow configure --describe-logging", err) + } + conf.Logging = &logging_config.LoggingConfig{ + RootSink: sinkConfig, + } + } + + if *jsonOutOpt { + os.Stdout.WriteString(conf.JSONString()) + } else { + os.Stdout.WriteString(conf.TOMLString()) + } + } + }) + + burrow.Command("help", + "Get more detailed or exhaustive options of selected commands or flags.", + func(cmd *cli.Cmd) { + + cmd.Spec = "[--participant-accounts] [--full-accounts] [--toml]" + + cmd.Action = func() { + } + }) + burrow.Run(os.Args) +} + +// Print informational output to Stderr +func printf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", args...) +} + +func fatalf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", args...) + os.Exit(1) +} + +func burrowConfigProvider(configFile string) source.ConfigProvider { + return source.FirstOf( + // Will fail if file doesn't exist, but still skipped it configFile == "" + source.TOMLFile(configFile, false), + source.Environment(config.DefaultBurrowConfigJSONEnvironmentVariable), + // Try working directory + source.TOMLFile(config.DefaultBurrowConfigTOMLFileName, true), + source.Default(config.DefaultBurrowConfig())) +} + +func genesisDocProvider(genesisFile string, skipNonExistent bool) source.ConfigProvider { + return source.NewConfigProvider(fmt.Sprintf("genesis file at %s", genesisFile), + source.ShouldSkipFile(genesisFile, skipNonExistent), + func(baseConfig interface{}) error { + conf, ok := baseConfig.(*config.BurrowConfig) + if !ok { + return fmt.Errorf("config passed was not BurrowConfig") + } + if conf.GenesisDoc != nil { + return fmt.Errorf("sourcing GenesisDoc from file, but GenesisDoc was defined earlier " + + "in config cascade, only specify GenesisDoc in one place") + } + genesisDoc := new(genesis.GenesisDoc) + err := source.FromJSONFile(genesisFile, genesisDoc) + if err != nil { + return err + } + conf.GenesisDoc = genesisDoc + return nil + }) +} + +func fromFile(file string, toml bool, conf interface{}) (err error) { + if toml { + err = source.FromTOMLFile(file, conf) + if err != nil { + fatalf("could not read GenesisSpec: %v", err) + } + } else { + err = source.FromJSONFile(file, conf) + if err != nil { + fatalf("could not read GenesisSpec: %v", err) + } + } + return } diff --git a/cmd/serve.go b/cmd/serve.go deleted file mode 100644 index 7f951f3bfe0247fc8aa2890cbd0d8dd82f41e7d2..0000000000000000000000000000000000000000 --- a/cmd/serve.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "fmt" - "os" - "os/signal" - "path" - "syscall" - - "github.com/hyperledger/burrow/core" - "github.com/hyperledger/burrow/definitions" - "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/lifecycle" - vm "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/util" - - "github.com/hyperledger/burrow/config" - "github.com/spf13/cobra" -) - -const ( - DefaultConfigBasename = "config" - DefaultConfigType = "toml" -) - -var DefaultConfigFilename = fmt.Sprintf("%s.%s", - DefaultConfigBasename, - DefaultConfigType) - -// build the serve subcommand -func buildServeCommand(do *definitions.Do) *cobra.Command { - cmd := &cobra.Command{ - Use: "serve", - Short: "burrow serve starts a burrow node with client API enabled by default.", - Long: `burrow serve starts a burrow node with client API enabled by default. -The burrow node is modularly configured for the consensus engine and application -manager. The client API can be disabled.`, - Example: fmt.Sprintf(`$ burrow serve -- will start the burrow node based on the configuration file "%s" in the current working directory -$ burrow serve --work-dir <path-to-working-directory> -- will start the burrow node based on the configuration file "%s" in the provided working directory -$ burrow serve --chain-id <CHAIN_ID> -- will overrule the configuration entry assert_chain_id`, - DefaultConfigFilename, DefaultConfigFilename), - PreRun: func(cmd *cobra.Command, args []string) { - // if WorkDir was not set by a flag or by $BURROW_WORKDIR - // NOTE [ben]: we can consider an `Explicit` flag that eliminates - // the use of any assumptions while starting burrow - if do.WorkDir == "" { - if currentDirectory, err := os.Getwd(); err != nil { - panic(fmt.Sprintf("No directory provided and failed to get current "+ - "working directory: %v", err)) - os.Exit(1) - } else { - do.WorkDir = currentDirectory - } - } - if !util.IsDir(do.WorkDir) { - panic(fmt.Sprintf("Provided working directory %s is not a directory", - do.WorkDir)) - os.Exit(1) - } - }, - Run: ServeRunner(do), - } - addServeFlags(do, cmd) - return cmd -} - -func addServeFlags(do *definitions.Do, serveCmd *cobra.Command) { - serveCmd.PersistentFlags().StringVarP(&do.ChainId, "chain-id", "c", - defaultChainId(), "specify the chain id to use for assertion against the genesis file or the existing state. If omitted, and no id is set in $CHAIN_ID, then assert_chain_id is used from the configuration file.") - serveCmd.PersistentFlags().StringVarP(&do.WorkDir, "work-dir", "w", - defaultWorkDir(), "specify the working directory for the chain to run. If omitted, and no path set in $BURROW_WORKDIR, the current working directory is taken.") - serveCmd.PersistentFlags().StringVarP(&do.DataDir, "data-dir", "", - defaultDataDir(), "specify the data directory. If omitted and not set in $BURROW_DATADIR, <working_directory>/data is taken.") - serveCmd.PersistentFlags().BoolVarP(&do.DisableRpc, "disable-rpc", "", - defaultDisableRpc(), "indicate for the RPC to be disabled. If omitted the RPC is enabled by default, unless (deprecated) $BURROW_API is set to false.") -} - -//------------------------------------------------------------------------------ -// functions -func NewCoreFromDo(do *definitions.Do) (*core.Core, error) { - // load the genesis file path - do.GenesisFile = path.Join(do.WorkDir, - do.Config.GetString("chain.genesis_file")) - - err := config.AssertConfigCompatibleWithRuntime(do.Config) - if err != nil { - return nil, err - } - - if do.Config.GetString("chain.genesis_file") == "" { - return nil, fmt.Errorf("The config value chain.genesis_file is empty, " + - "but should be set to the location of the genesis.json file.") - } - // Ensure data directory is set and accessible - if err := do.InitialiseDataDirectory(); err != nil { - return nil, fmt.Errorf("Failed to initialise data directory (%s): %v", do.DataDir, err) - } - - loggerConfig, err := core.LoadLoggingConfigFromDo(do) - if err != nil { - return nil, fmt.Errorf("Failed to load logging config: %s", err) - } - - logger, err := lifecycle.NewLoggerFromLoggingConfig(loggerConfig) - if err != nil { - return nil, fmt.Errorf("Failed to build logger from logging config: %s", err) - } - // Create a root logger to pass through to dependencies - logger = logging.WithScope(logger, "Serve") - // Capture all logging from tendermint/tendermint and tendermint/go-* - // dependencies - lifecycle.CaptureTendermintLog15Output(logger) - // And from stdlib go log - lifecycle.CaptureStdlibLogOutput(logger) - - // if do.ChainId is not yet set, load chain_id for assertion from configuration file - - if do.ChainId == "" { - if do.ChainId = do.Config.GetString("chain.assert_chain_id"); do.ChainId == "" { - return nil, fmt.Errorf("The config chain.assert_chain_id is empty, " + - "but should be set to the chain_id of the chain we are trying to run.") - } - } - - logging.Msg(logger, "Loading configuration for serve command", - "chainId", do.ChainId, - "workingDirectory", do.WorkDir, - "dataDirectory", do.DataDir, - "genesisFile", do.GenesisFile) - - consensusConfig, err := core.LoadConsensusModuleConfig(do) - if err != nil { - return nil, fmt.Errorf("Failed to load consensus module configuration: %s.", err) - } - - managerConfig, err := core.LoadApplicationManagerModuleConfig(do) - if err != nil { - return nil, fmt.Errorf("Failed to load application manager module configuration: %s.", err) - } - - logging.Msg(logger, "Modules configured", - "consensusModule", consensusConfig.Name, - "applicationManager", managerConfig.Name) - - return core.NewCore(do.ChainId, consensusConfig, managerConfig, logger) -} - -// ServeRunner() returns a command runner that prepares the environment and sets -// up the core for burrow to run. After the setup succeeds, it starts the core -// and waits for the core to terminate. -func ServeRunner(do *definitions.Do) func(*cobra.Command, []string) { - return func(cmd *cobra.Command, args []string) { - // load configuration from a single location to avoid a wrong configuration - // file is loaded. - err := do.ReadConfig(do.WorkDir, DefaultConfigBasename, DefaultConfigType) - if err != nil { - util.Fatalf("Fatal error reading configuration from %s/%s", do.WorkDir, - DefaultConfigFilename) - } - - vm.SetDebug(do.Debug) - - newCore, err := NewCoreFromDo(do) - - if err != nil { - util.Fatalf("Failed to load core: %s", err) - } - - if !do.DisableRpc { - serverConfig, err := core.LoadServerConfigFromDo(do) - if err != nil { - util.Fatalf("Failed to load server configuration: %s.", err) - } - serverProcess, err := newCore.NewGatewayV0(serverConfig) - if err != nil { - util.Fatalf("Failed to load servers: %s.", err) - } - err = serverProcess.Start() - if err != nil { - util.Fatalf("Failed to start servers: %s.", err) - } - _, err = newCore.NewGatewayTendermint(serverConfig) - if err != nil { - util.Fatalf("Failed to start Tendermint gateway") - } - <-serverProcess.StopEventChannel() - // Attempt graceful shutdown - newCore.Stop() - } else { - signals := make(chan os.Signal, 1) - signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) - fmt.Fprintf(os.Stderr, "Received %s signal. Marmots out.", <-signals) - } - } -} - -//------------------------------------------------------------------------------ -// Defaults - -func defaultChainId() string { - // if CHAIN_ID environment variable is not set, keep do.ChainId empty to read - // assert_chain_id from configuration file - return setDefaultString("CHAIN_ID", "") -} - -func defaultWorkDir() string { - // if BURROW_WORKDIR environment variable is not set, keep do.WorkDir empty - // as do.WorkDir is set by the PreRun - return setDefaultString("BURROW_WORKDIR", "") -} - -func defaultDataDir() string { - // As the default data directory depends on the default working directory, - // wait setting a default value, and initialise the data directory from serve() - return setDefaultString("BURROW_DATADIR", "") -} - -func defaultDisableRpc() bool { - // we currently observe environment variable BURROW_API (true = enable) - // and default to enabling the RPC if it is not set. - // TODO: [ben] deprecate BURROW_API across the stack for 0.12.1, and only disable - // the rpc through a command line flag --disable-rpc - return !setDefaultBool("BURROW_API", true) -} diff --git a/common/math/integral/integral_math.go b/common/math/integral/integral_math.go deleted file mode 100644 index 863b92114352b6a12568f8dcc778708adbfd5283..0000000000000000000000000000000000000000 --- a/common/math/integral/integral_math.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package integral - -func MaxInt8(a, b int8) int8 { - if a > b { - return a - } - return b -} - -func MaxUint8(a, b uint8) uint8 { - if a > b { - return a - } - return b -} - -func MaxInt16(a, b int16) int16 { - if a > b { - return a - } - return b -} - -func MaxUint16(a, b uint16) uint16 { - if a > b { - return a - } - return b -} - -func MaxInt32(a, b int32) int32 { - if a > b { - return a - } - return b -} - -func MaxUint32(a, b uint32) uint32 { - if a > b { - return a - } - return b -} - -func MaxInt64(a, b int64) int64 { - if a > b { - return a - } - return b -} - -func MaxUint64(a, b uint64) uint64 { - if a > b { - return a - } - return b -} - -func MaxInt(a, b int) int { - if a > b { - return a - } - return b -} - -func MaxUint(a, b uint) uint { - if a > b { - return a - } - return b -} - -//----------------------------------------------------------------------------- - -func MinInt8(a, b int8) int8 { - if a < b { - return a - } - return b -} - -func MinUint8(a, b uint8) uint8 { - if a < b { - return a - } - return b -} - -func MinInt16(a, b int16) int16 { - if a < b { - return a - } - return b -} - -func MinUint16(a, b uint16) uint16 { - if a < b { - return a - } - return b -} - -func MinInt32(a, b int32) int32 { - if a < b { - return a - } - return b -} - -func MinUint32(a, b uint32) uint32 { - if a < b { - return a - } - return b -} - -func MinInt64(a, b int64) int64 { - if a < b { - return a - } - return b -} - -func MinUint64(a, b uint64) uint64 { - if a < b { - return a - } - return b -} - -func MinInt(a, b int) int { - if a < b { - return a - } - return b -} - -func MinUint(a, b uint) uint { - if a < b { - return a - } - return b -} - -//----------------------------------------------------------------------------- - -func ExpUint64(a, b uint64) uint64 { - accum := uint64(1) - for b > 0 { - if b&1 == 1 { - accum *= a - } - a *= a - b >>= 1 - } - return accum -} diff --git a/common/random/random.go b/common/random/random.go deleted file mode 100644 index 4b1e4e9f02862dd4e815f141791156e5233a7a7a..0000000000000000000000000000000000000000 --- a/common/random/random.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package random - -import ( - crand "crypto/rand" - "math/rand" - "time" - - "github.com/hyperledger/burrow/common/sanity" -) - -const ( - strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters -) - -func init() { - b := cRandBytes(8) - var seed uint64 - for i := 0; i < 8; i++ { - seed |= uint64(b[i]) - seed <<= 8 - } - rand.Seed(int64(seed)) -} - -// Constructs an alphanumeric string of given length. -func RandStr(length int) string { - chars := []byte{} -MAIN_LOOP: - for { - val := rand.Int63() - for i := 0; i < 10; i++ { - v := int(val & 0x3f) // rightmost 6 bits - if v >= 62 { // only 62 characters in strChars - val >>= 6 - continue - } else { - chars = append(chars, strChars[v]) - if len(chars) == length { - break MAIN_LOOP - } - val >>= 6 - } - } - } - - return string(chars) -} - -func RandUint16() uint16 { - return uint16(rand.Uint32() & (1<<16 - 1)) -} - -func RandUint32() uint32 { - return rand.Uint32() -} - -func RandUint64() uint64 { - return uint64(rand.Uint32())<<32 + uint64(rand.Uint32()) -} - -func RandUint() uint { - return uint(rand.Int()) -} - -func RandInt16() int16 { - return int16(rand.Uint32() & (1<<16 - 1)) -} - -func RandInt32() int32 { - return int32(rand.Uint32()) -} - -func RandInt64() int64 { - return int64(rand.Uint32())<<32 + int64(rand.Uint32()) -} - -func RandInt() int { - return rand.Int() -} - -// Distributed pseudo-exponentially to test for various cases -func RandUint16Exp() uint16 { - bits := rand.Uint32() % 16 - if bits == 0 { - return 0 - } - n := uint16(1 << (bits - 1)) - n += uint16(rand.Int31()) & ((1 << (bits - 1)) - 1) - return n -} - -// Distributed pseudo-exponentially to test for various cases -func RandUint32Exp() uint32 { - bits := rand.Uint32() % 32 - if bits == 0 { - return 0 - } - n := uint32(1 << (bits - 1)) - n += uint32(rand.Int31()) & ((1 << (bits - 1)) - 1) - return n -} - -// Distributed pseudo-exponentially to test for various cases -func RandUint64Exp() uint64 { - bits := rand.Uint32() % 64 - if bits == 0 { - return 0 - } - n := uint64(1 << (bits - 1)) - n += uint64(rand.Int63()) & ((1 << (bits - 1)) - 1) - return n -} - -func RandFloat32() float32 { - return rand.Float32() -} - -func RandTime() time.Time { - return time.Unix(int64(RandUint64Exp()), 0) -} - -func RandBytes(n int) []byte { - bs := make([]byte, n) - for i := 0; i < n; i++ { - bs[i] = byte(rand.Intn(256)) - } - return bs -} - -// NOTE: This relies on the os's random number generator. -// For real security, we should salt that with some seed. -// See github.com/tendermint/go-crypto for a more secure reader. -func cRandBytes(numBytes int) []byte { - b := make([]byte, numBytes) - _, err := crand.Read(b) - if err != nil { - sanity.PanicCrisis(err) - } - return b -} diff --git a/common/sanity/sanity.go b/common/sanity/sanity.go deleted file mode 100644 index 63fe7fd9e2f1a53ec7c379b7b199d082044f5b1b..0000000000000000000000000000000000000000 --- a/common/sanity/sanity.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sanity - -import ( - "fmt" -) - -//-------------------------------------------------------------------------------------------------- -// panic wrappers -// NOTE: [ben] Fail fast and fail hard, these are wrappers that point to code that needs -// to be addressed, but simplify finding them in the code; - -// A panic resulting from a sanity check means there is a programmer error -// and some gaurantee is not satisfied. -func PanicSanity(v interface{}) { - panic(fmt.Sprintf("Paniced on a Sanity Check: %v", v)) -} - -// A panic here means something has gone horribly wrong, in the form of data corruption or -// failure of the operating system. In a correct/healthy system, these should never fire. -// If they do, it's indicative of a much more serious problem. -func PanicCrisis(v interface{}) { - panic(fmt.Sprintf("Paniced on a Crisis: %v", v)) -} - -// Indicates a failure of consensus. Someone was malicious or something has -// gone horribly wrong. These should really boot us into an "emergency-recover" mode -func PanicConsensus(v interface{}) { - panic(fmt.Sprintf("Paniced on a Consensus Failure: %v", v)) -} - -// For those times when we're not sure if we should panic -func PanicQ(v interface{}) { - panic(fmt.Sprintf("Paniced questionably: %v", v)) -} diff --git a/config/config.go b/config/config.go index 24753b65ffa8d0dd9e88e4a42262198e979fc406..05a6970312912ccfbb5dbb349badc016c891735e 100644 --- a/config/config.go +++ b/config/config.go @@ -1,204 +1,67 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package config import ( - "bytes" "fmt" - "text/template" - lconfig "github.com/hyperledger/burrow/logging/config" - "github.com/hyperledger/burrow/version" - "github.com/spf13/viper" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/config/source" + "github.com/hyperledger/burrow/consensus/tendermint" + "github.com/hyperledger/burrow/consensus/tendermint/validator" + "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/keys" + logging_config "github.com/hyperledger/burrow/logging/config" + "github.com/hyperledger/burrow/logging/lifecycle" + "github.com/hyperledger/burrow/rpc" ) -type ConfigServiceGeneral struct { - ChainImageName string - UseDataContainer bool - ExportedPorts string - ContainerEntrypoint string -} - -// TODO: [ben] increase the configurability upon need -type ConfigChainGeneral struct { - AssertChainId string - BurrowMajorVersion uint8 - BurrowMinorVersion uint8 - GenesisRelativePath string -} - -type ConfigChainModule struct { - Name string - ModuleRelativeRoot string +const DefaultBurrowConfigTOMLFileName = "burrow.toml" +const DefaultBurrowConfigJSONEnvironmentVariable = "BURROW_CONFIG_JSON" +const DefaultGenesisDocJSONFileName = "genesis.json" + +type BurrowConfig struct { + ValidatorAddress *acm.Address `json:",omitempty" toml:",omitempty"` + GenesisDoc *genesis.GenesisDoc `json:",omitempty" toml:",omitempty"` + Tendermint *tendermint.BurrowTendermintConfig `json:",omitempty" toml:",omitempty"` + Keys *keys.KeysConfig `json:",omitempty" toml:",omitempty"` + RPC *rpc.RPCConfig `json:",omitempty" toml:",omitempty"` + Logging *logging_config.LoggingConfig `json:",omitempty" toml:",omitempty"` } -type ConfigTendermint struct { - Moniker string - Seeds string - FastSync bool -} - -var serviceGeneralTemplate *template.Template -var chainGeneralTemplate *template.Template -var chainConsensusTemplate *template.Template -var chainApplicationManagerTemplate *template.Template -var tendermintTemplate *template.Template - -func init() { - var err error - if serviceGeneralTemplate, err = template.New("serviceGeneral").Parse(sectionServiceGeneral); err != nil { - panic(err) - } - if chainGeneralTemplate, err = template.New("chainGeneral").Parse(sectionChainGeneral); err != nil { - panic(err) - } - if chainConsensusTemplate, err = template.New("chainConsensus").Parse(sectionChainConsensus); err != nil { - panic(err) - } - if chainApplicationManagerTemplate, err = template.New("chainApplicationManager").Parse(sectionChainApplicationManager); err != nil { - panic(err) - } - if tendermintTemplate, err = template.New("tendermint").Parse(sectionTendermint); err != nil { - panic(err) +func DefaultBurrowConfig() *BurrowConfig { + return &BurrowConfig{ + Tendermint: tendermint.DefaultBurrowTendermintConfig(), + Keys: keys.DefaultKeysConfig(), + RPC: rpc.DefaultRPCConfig(), + Logging: logging_config.DefaultNodeLoggingConfig(), } } -// NOTE: [ben] for 0.12.0-rc3 we only have a single configuration path -// with Tendermint in-process as the consensus engine and BurrowMint -// in-process as the application manager, so we hard-code the few -// parameters that are already templated. -// Let's learn to walk before we can run. -func GetConfigurationFileBytes(chainId, moniker, seeds string, chainImageName string, - useDataContainer bool, exportedPortsString, containerEntrypoint string) ([]byte, error) { - - burrowService := &ConfigServiceGeneral{ - ChainImageName: chainImageName, - UseDataContainer: useDataContainer, - ExportedPorts: exportedPortsString, - ContainerEntrypoint: containerEntrypoint, - } - - // We want to encode in the config file which Burrow version generated the config - burrowVersion := version.GetBurrowVersion() - burrowChain := &ConfigChainGeneral{ - AssertChainId: chainId, - BurrowMajorVersion: burrowVersion.MajorVersion, - BurrowMinorVersion: burrowVersion.MinorVersion, - GenesisRelativePath: "genesis.json", - } - - chainConsensusModule := &ConfigChainModule{ - Name: "tendermint", - ModuleRelativeRoot: "tendermint", +func (conf *BurrowConfig) Kernel() (*core.Kernel, error) { + if conf.GenesisDoc == nil { + return nil, fmt.Errorf("no GenesisDoc defined in config, cannot make Kernel") } - - chainApplicationManagerModule := &ConfigChainModule{ - Name: "burrowmint", - ModuleRelativeRoot: "burrowmint", + if conf.ValidatorAddress == nil { + return nil, fmt.Errorf("no validator address in config, cannot make Kernel") } - tendermintModule := &ConfigTendermint{ - Moniker: moniker, - Seeds: seeds, - FastSync: false, + logger, err := lifecycle.NewLoggerFromLoggingConfig(conf.Logging) + if err != nil { + return nil, fmt.Errorf("could not generate logger from logging config: %v", err) } - - // NOTE: [ben] according to StackOverflow appending strings with copy is - // more efficient than bytes.WriteString, but for readability and because - // this is not performance critical code we opt for bytes, which is - // still more efficient than + concatentation operator. - var buffer bytes.Buffer - - // write copyright header - buffer.WriteString(headerCopyright) - - // write section [service] - if err := serviceGeneralTemplate.Execute(&buffer, burrowService); err != nil { - return nil, fmt.Errorf("Failed to write template service general for %s: %s", - chainId, err) + keyClient := keys.NewBurrowKeyClient(conf.Keys.URL, logger) + val, err := keys.Addressable(keyClient, *conf.ValidatorAddress) + if err != nil { + return nil, fmt.Errorf("could not get validator addressable from keys client: %v", err) } - // write section for service dependencies; this is currently a static section - // with a fixed dependency on monax-keys - buffer.WriteString(sectionServiceDependencies) + privValidator := validator.NewPrivValidatorMemory(val, keys.Signer(keyClient, val.Address())) - // write section [chain] - if err := chainGeneralTemplate.Execute(&buffer, burrowChain); err != nil { - return nil, fmt.Errorf("Failed to write template chain general for %s: %s", - chainId, err) - } - - // write separator chain consensus - buffer.WriteString(separatorChainConsensus) - // write section [chain.consensus] - if err := chainConsensusTemplate.Execute(&buffer, chainConsensusModule); err != nil { - return nil, fmt.Errorf("Failed to write template chain consensus for %s: %s", - chainId, err) - } - - // write separator chain application manager - buffer.WriteString(separatorChainApplicationManager) - // write section [chain.consensus] - if err := chainApplicationManagerTemplate.Execute(&buffer, - chainApplicationManagerModule); err != nil { - return nil, fmt.Errorf("Failed to write template chain application manager for %s: %s", - chainId, err) - } - - // write separator servers - buffer.WriteString(separatorServerConfiguration) - // TODO: [ben] upon necessity replace this with template too - // write static section servers - buffer.WriteString(sectionServers) - - // write separator modules - buffer.WriteString(separatorModules) - - // write section module Tendermint - if err := tendermintTemplate.Execute(&buffer, tendermintModule); err != nil { - return nil, fmt.Errorf("Failed to write template tendermint for %s, moniker %s: %s", - chainId, moniker, err) - } - - // write static section burrowmint - buffer.WriteString(sectionBurrowMint) - - buffer.WriteString(sectionLoggingHeader) - buffer.WriteString(lconfig.DefaultNodeLoggingConfig().RootTOMLString()) - - return buffer.Bytes(), nil + return core.NewKernel(privValidator, conf.GenesisDoc, conf.Tendermint.TendermintConfig(), conf.RPC, logger) } -func AssertConfigCompatibleWithRuntime(conf *viper.Viper) error { - burrowVersion := version.GetBurrowVersion() - majorVersion := uint8(conf.GetInt(fmt.Sprintf("chain.%s", majorVersionKey))) - minorVersion := uint8(conf.GetInt(fmt.Sprintf("chain.%s", majorVersionKey))) - if burrowVersion.MajorVersion != majorVersion || - burrowVersion.MinorVersion != minorVersion { - fmt.Errorf("Runtime Burrow version %s is not compatible with "+ - "configuration file version: major=%s, minor=%s", - burrowVersion.GetVersionString(), majorVersion, minorVersion) - } - return nil +func (conf *BurrowConfig) JSONString() string { + return source.JSONString(conf) } -func GetExampleConfigFileBytes() ([]byte, error) { - return GetConfigurationFileBytes( - "simplechain", - "delectable_marmot", - "192.168.168.255", - "db:latest", - true, - "46657", - "burrow") +func (conf *BurrowConfig) TOMLString() string { + return source.TOMLString(conf) } diff --git a/config/config_test.go b/config/config_test.go index a49d808491b63518d227fc4aa854e7695372df27..16a2d9f0a25dbee0a430c16fdec5011c9c299887 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,28 +1,17 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package config import ( + "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/hyperledger/burrow/genesis" ) -func TestGeneratedConfigIsUsable(t *testing.T) { - bs, err := GetExampleConfigFileBytes() - assert.NoError(t, err, "Should be able to create example config") - _, err = ReadViperConfig(bs) - assert.NoError(t, err, "Should be able to read example config into Viper") +func TestBurrowConfigSerialise(t *testing.T) { + conf := &BurrowConfig{ + GenesisDoc: &genesis.GenesisDoc{ + ChainName: "Foo", + }, + } + fmt.Println(conf.JSONString()) } diff --git a/config/dump_config_test.go b/config/dump_config_test.go deleted file mode 100644 index 5d238c9e84afd53ab19da4a70bcb88d3fe77d525..0000000000000000000000000000000000000000 --- a/config/dump_config_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// +build dumpconfig - -// Space above matters -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import ( - "io/ioutil" - "testing" - - "github.com/stretchr/testify/assert" -) - -// This is a little convenience for getting a config file dump. Just run: -// go test -tags dumpconfig ./config -// This pseudo test won't run unless the dumpconfig tag is -func TestDumpConfig(t *testing.T) { - bs, err := GetExampleConfigFileBytes() - assert.NoError(t, err, "Should be able to create example config") - ioutil.WriteFile("config_dump.toml", bs, 0644) -} diff --git a/config/module.go b/config/module.go deleted file mode 100644 index 8b8eb9456837096b4bc4bd91c39b06817bd3a8b9..0000000000000000000000000000000000000000 --- a/config/module.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -// config defines simple types in a separate package to avoid cyclical imports - -import ( - viper "github.com/spf13/viper" -) - -type ModuleConfig struct { - Module string - Name string - WorkDir string - DataDir string - RootDir string - ChainId string - GenesisFile string - Config *viper.Viper -} diff --git a/config/source/source.go b/config/source/source.go new file mode 100644 index 0000000000000000000000000000000000000000..b4a31d809bb6d150eaaaa7af1b2c17522fc39d45 --- /dev/null +++ b/config/source/source.go @@ -0,0 +1,283 @@ +package source + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "reflect" + "strings" + + "github.com/BurntSushi/toml" + "github.com/cep21/xdgbasedir" + "github.com/imdario/mergo" +) + +// If passed this identifier try to read config from STDIN +const STDINFileIdentifier = "-" + +type ConfigProvider interface { + // Description of where this provider sources its config from + From() string + // Get the config values to the passed in baseConfig + Apply(baseConfig interface{}) error + // Return a copy of the provider that does nothing if skip is true + SetSkip(skip bool) ConfigProvider + // Whether to skip this provider + Skip() bool +} + +var _ ConfigProvider = &configSource{} + +type configSource struct { + from string + skip bool + apply func(baseConfig interface{}) error +} + +func NewConfigProvider(from string, skip bool, apply func(baseConfig interface{}) error) *configSource { + return &configSource{ + from: from, + skip: skip, + apply: apply, + } +} + +func (cs *configSource) From() string { + return cs.from +} + +func (cs *configSource) Apply(baseConfig interface{}) error { + return cs.apply(baseConfig) +} + +func (cs *configSource) Skip() bool { + return cs.skip +} + +// Returns a copy of the configSource with skip set as passed in +func (cs *configSource) SetSkip(skip bool) ConfigProvider { + return &configSource{ + skip: skip, + from: cs.from, + apply: cs.apply, + } +} + +// Builds a ConfigProvider by iterating over a cascade of ConfigProvider sources. Can be used +// in two distinct modes: with shortCircuit true the first successful ConfigProvider source +// is returned. With shortCircuit false sources appearing later are used to possibly override +// those appearing earlier +func Cascade(logWriter io.Writer, shortCircuit bool, providers ...ConfigProvider) *configSource { + var fromStrings []string + skip := true + for _, provider := range providers { + if !provider.Skip() { + skip = false + fromStrings = append(fromStrings, provider.From()) + } + } + fromPrefix := "each of" + if shortCircuit { + fromPrefix = "first of" + + } + return &configSource{ + skip: skip, + from: fmt.Sprintf("%s: %s", fromPrefix, strings.Join(fromStrings, " then ")), + apply: func(baseConfig interface{}) error { + if baseConfig == nil { + return fmt.Errorf("baseConfig passed to Cascade(...).Get() must not be nil") + } + for _, provider := range providers { + if !provider.Skip() { + writeLog(logWriter, fmt.Sprintf("Sourcing config from %s", provider.From())) + err := provider.Apply(baseConfig) + if err != nil { + return err + } + if shortCircuit { + return nil + } + } + } + return nil + }, + } +} + +func FirstOf(providers ...ConfigProvider) *configSource { + return Cascade(os.Stderr, true, providers...) +} + +func EachOf(providers ...ConfigProvider) *configSource { + return Cascade(os.Stderr, false, providers...) +} + +// Try to source config from provided JSON file, is skipNonExistent is true then the provider will fall-through (skip) +// when the file doesn't exist, rather than returning an error +func JSONFile(configFile string, skipNonExistent bool) *configSource { + return &configSource{ + skip: ShouldSkipFile(configFile, skipNonExistent), + from: fmt.Sprintf("JSON config file at '%s'", configFile), + apply: func(baseConfig interface{}) error { + return FromJSONFile(configFile, baseConfig) + }, + } +} + +// Try to source config from provided TOML file, is skipNonExistent is true then the provider will fall-through (skip) +// when the file doesn't exist, rather than returning an error +func TOMLFile(configFile string, skipNonExistent bool) *configSource { + return &configSource{ + skip: ShouldSkipFile(configFile, skipNonExistent), + from: fmt.Sprintf("TOML config file at '%s'", configFile), + apply: func(baseConfig interface{}) error { + return FromTOMLFile(configFile, baseConfig) + }, + } +} + +// Try to find config by using XDG base dir spec +func XDGBaseDir(configFileName string) *configSource { + skip := false + // Look for config in standard XDG specified locations + configFile, err := xdgbasedir.GetConfigFileLocation(configFileName) + if err == nil { + _, err := os.Stat(configFile) + // Skip if config file does not exist at default location + skip = os.IsNotExist(err) + } + return &configSource{ + skip: skip, + from: fmt.Sprintf("XDG base dir"), + apply: func(baseConfig interface{}) error { + if err != nil { + return err + } + return FromTOMLFile(configFile, baseConfig) + }, + } +} + +// Source from a single environment variable with config embedded in JSON +func Environment(key string) *configSource { + jsonString := os.Getenv(key) + return &configSource{ + skip: jsonString == "", + from: fmt.Sprintf("'%s' environment variable (as JSON)", key), + apply: func(baseConfig interface{}) error { + return FromJSONString(jsonString, baseConfig) + }, + } +} + +func Default(defaultConfig interface{}) *configSource { + return &configSource{ + from: "defaults", + apply: func(baseConfig interface{}) error { + return mergo.MergeWithOverwrite(baseConfig, defaultConfig) + }, + } +} + +func FromJSONFile(configFile string, conf interface{}) error { + bs, err := ReadFile(configFile) + if err != nil { + return err + } + + return FromJSONString(string(bs), conf) +} + +func FromTOMLFile(configFile string, conf interface{}) error { + bs, err := ReadFile(configFile) + if err != nil { + return err + } + + return FromTOMLString(string(bs), conf) +} + +func FromTOMLString(tomlString string, conf interface{}) error { + _, err := toml.Decode(tomlString, conf) + if err != nil { + return err + } + return nil +} + +func FromJSONString(jsonString string, conf interface{}) error { + err := json.Unmarshal(([]byte)(jsonString), conf) + if err != nil { + return err + } + return nil +} + +func TOMLString(conf interface{}) string { + buf := new(bytes.Buffer) + encoder := toml.NewEncoder(buf) + err := encoder.Encode(conf) + if err != nil { + return fmt.Sprintf("<Could not serialise config: %v>", err) + } + return buf.String() +} + +func JSONString(conf interface{}) string { + bs, err := json.MarshalIndent(conf, "", "\t") + if err != nil { + return fmt.Sprintf("<Could not serialise config: %v>", err) + } + return string(bs) +} + +func Merge(base, override interface{}) (interface{}, error) { + merged, err := DeepCopy(base) + if err != nil { + return nil, err + } + err = mergo.MergeWithOverwrite(merged, override) + if err != nil { + return nil, err + } + return merged, nil +} + +// Passed a pointer to struct creates a deep copy of the struct +func DeepCopy(conf interface{}) (interface{}, error) { + // Create a zero value + confCopy := reflect.New(reflect.TypeOf(conf).Elem()).Interface() + // Perform a merge into that value to effect the copy + err := mergo.Merge(confCopy, conf) + if err != nil { + return nil, err + } + return confCopy, nil +} + +func writeLog(writer io.Writer, msg string) { + if writer != nil { + writer.Write(([]byte)(msg)) + writer.Write(([]byte)("\n")) + } +} + +func ReadFile(file string) ([]byte, error) { + if file == STDINFileIdentifier { + return ioutil.ReadAll(os.Stdin) + } + return ioutil.ReadFile(file) +} + +func ShouldSkipFile(file string, skipNonExistent bool) bool { + skip := file == "" + if !skip && skipNonExistent { + _, err := os.Stat(file) + skip = os.IsNotExist(err) + } + return skip +} diff --git a/config/source/source_test.go b/config/source/source_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7bf062b40dd6e1713725eb74da0177ce1d2ea861 --- /dev/null +++ b/config/source/source_test.go @@ -0,0 +1,111 @@ +package source + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEnvironment(t *testing.T) { + envVar := "FISH_SPOON_ALPHA" + jsonString := JSONString(newTestConfig()) + os.Setenv(envVar, jsonString) + conf := new(animalConfig) + err := Environment(envVar).Apply(conf) + assert.NoError(t, err) + assert.Equal(t, jsonString, JSONString(conf)) +} + +func TestDeepCopy(t *testing.T) { + conf := newTestConfig() + confCopy, err := DeepCopy(conf) + require.NoError(t, err) + assert.Equal(t, conf, confCopy) +} + +func TestFile(t *testing.T) { + tomlString := TOMLString(newTestConfig()) + file := writeConfigFile(t, newTestConfig()) + defer os.Remove(file) + conf := new(animalConfig) + err := TOMLFile(file, false).Apply(conf) + assert.NoError(t, err) + assert.Equal(t, tomlString, TOMLString(conf)) +} + +func TestCascade(t *testing.T) { + envVar := "FISH_SPOON_ALPHA" + // Both fall through so baseConfig returned + conf := newTestConfig() + err := Cascade(os.Stderr, true, + Environment(envVar), + TOMLFile("", false)).Apply(conf) + assert.NoError(t, err) + assert.Equal(t, newTestConfig(), conf) + + // Env not set so falls through to file + fileConfig := newTestConfig() + file := writeConfigFile(t, fileConfig) + defer os.Remove(file) + conf = new(animalConfig) + err = Cascade(os.Stderr, true, + Environment(envVar), + TOMLFile(file, false)).Apply(conf) + assert.NoError(t, err) + assert.Equal(t, TOMLString(fileConfig), TOMLString(conf)) + + // Env set so caught by environment source + envConfig := animalConfig{ + Name: "Slug", + NumLegs: 0, + } + os.Setenv(envVar, JSONString(envConfig)) + conf = newTestConfig() + err = Cascade(os.Stderr, true, + Environment(envVar), + TOMLFile(file, false)).Apply(conf) + assert.NoError(t, err) + assert.Equal(t, TOMLString(envConfig), TOMLString(conf)) +} + +func writeConfigFile(t *testing.T, conf interface{}) string { + tomlString := TOMLString(conf) + f, err := ioutil.TempFile("", "source-test.toml") + assert.NoError(t, err) + f.Write(([]byte)(tomlString)) + f.Close() + return f.Name() +} + +// Test types + +type legConfig struct { + Leg int + Colour byte +} + +type animalConfig struct { + Name string + NumLegs int + Legs []legConfig +} + +func newTestConfig() *animalConfig { + return &animalConfig{ + Name: "Froggy!", + NumLegs: 2, + Legs: []legConfig{ + { + Leg: 1, + Colour: 034, + }, + { + Leg: 2, + Colour: 034, + }, + }, + } +} diff --git a/config/templates.go b/config/templates.go deleted file mode 100644 index 8a4e15fb8111f1ea1531d8b946d42b46bf76a51d..0000000000000000000000000000000000000000 --- a/config/templates.go +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import "fmt" - -const headerCopyright = `# Copyright 2017 Monax Industries Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This is a TOML configuration for burrow chains generated by burrow config. - -` - -const sectionServiceGeneral = `[service] -# NOTE: this section is read by CLI tooling, and ignored by burrow. -# Image specifies the image name monax needs to pull -# for running the chain. -image = "{{.ChainImageName}}" -# Define whether monax needs to attach the data container -# for the chain. -data_container = {{.UseDataContainer}} -# Specify a list of ports that need to be exported on the container. -ports = {{.ExportedPorts}} -{{ if ne .ContainerEntrypoint "" }}# Entrypoint points to the default action to execute -# in the chain container. -entry_point = "{{.ContainerEntrypoint}}"{{ end }} - -` - -const sectionServiceDependencies = `[dependencies] -# NOTE: this section is read by Monax tooling, and ignored by burrow. -# burrow expects these services to be available; eric-cli tooling will -# automatically set these services up for you. -# Services to boot with/required by the chain -services = [ "keys" ] - -` -const majorVersionKey = "major_version" -const minorVersionKey = "minor_version" - -var sectionChainGeneral string = fmt.Sprintf(`[chain] - -# ChainId is a human-readable name to identify the chain. -# This must correspond to the chain_id defined in the genesis file -# and the assertion here provides a safe-guard on misconfiguring chains. -assert_chain_id = "{{.AssertChainId}}" -# semantic major and minor version -%s = {{.BurrowMajorVersion}} -%s = {{.BurrowMinorVersion}} -# genesis file, relative path is to burrow working directory -genesis_file = "{{.GenesisRelativePath}}" - -`, majorVersionKey, minorVersionKey) - -const separatorChainConsensus = ` -################################################################################ -## -## consensus -## -################################################################################ - -` - -const sectionChainConsensus = ` [chain.consensus] - # consensus defines the module to use for consensus and - # this will define the peer-to-peer consensus network; - # accepted values are ("noops", "abci",) "tendermint" - name = "{{.Name}}" - # relative path to consensus' module root folder - relative_root = "{{.ModuleRelativeRoot}}" - - ` - -const separatorChainApplicationManager = ` -################################################################################ -## -## application manager -## -################################################################################ - -` - -const sectionChainApplicationManager = ` [chain.manager] - # application manager name defines the module to use for handling - # the transactions. Supported names are "burrowmint" - name = "{{.Name}}" - # relative path to application manager root folder - relative_root = "{{.ModuleRelativeRoot}}" - - ` - -const separatorServerConfiguration = ` -################################################################################ -################################################################################ -## -## Server configurations -## -################################################################################ -################################################################################ - -` - -// TODO: [ben] map entries to structure defined in burrow -const sectionServers = `[servers] - - [servers.bind] - address = "" - port = 1337 - - [servers.tls] - tls = false - cert_path = "" - key_path = "" - - [servers.cors] - enable = false - allow_origins = [] - allow_credentials = false - allow_methods = [] - allow_headers = [] - expose_headers = [] - max_age = 0 - - [servers.http] - json_rpc_endpoint = "/rpc" - - [servers.websocket] - endpoint = "/socketrpc" - max_sessions = 50 - read_buffer_size = 4096 - write_buffer_size = 4096 - - [servers.tendermint] - # Multiple listeners can be separated with a comma - rpc_local_address = "0.0.0.0:46657" - endpoint = "/websocket" - - ` - -const separatorModules = ` -################################################################################ -################################################################################ -## -## Module configurations - dynamically loaded based on chain configuration -## -################################################################################ -################################################################################ - -` - -// TODO: [ben] minimal fields have been made configurable; expand where needed -const sectionTendermint = ` -################################################################################ -## -## Tendermint -## -## in-process execution of Tendermint consensus engine -## -################################################################################ - -[tendermint] -# private validator file is used by tendermint to keep the status -# of the private validator, but also (currently) holds the private key -# for the private vaildator to sign with. This private key needs to be moved -# out and directly managed by monax-keys -# This file needs to be in the root directory -private_validator_file = "priv_validator.json" - - # Tendermint requires additional configuration parameters. - # burrow's tendermint consensus module will load [tendermint.configuration] - # as the configuration for Tendermint. - # burrow will respect the configurations set in this file where applicable, - # but reserves the option to override or block conflicting settings. - [tendermint.configuration] - # moniker is the name of the node on the tendermint p2p network - moniker = "{{.Moniker}}" - # seeds lists the peers tendermint can connect to join the network - seeds = "{{.Seeds}}" - # fast_sync allows a tendermint node to catch up faster when joining - # the network. - # NOTE: Tendermint has reported potential issues with fast_sync enabled. - # The recommended setting is for keeping it disabled. - fast_sync = {{.FastSync}} - # database backend to use for Tendermint. Supported "leveldb" and "memdb". - db_backend = "leveldb" - # logging level. Supported "error" < "warn" < "notice" < "info" < "debug" - log_level = "info" - # node local address - node_laddr = "0.0.0.0:46656" - # rpc local address - # NOTE: value is ignored when run in-process as RPC is - # handled by [servers.tendermint] - rpc_laddr = "0.0.0.0:46657" - # proxy application address - used for abci connections, - # and this port should not be exposed for in-process Tendermint - proxy_app = "tcp://127.0.0.1:46658" - - # Extended Tendermint configuration settings - # for reference to Tendermint see https://github.com/tendermint/tendermint/blob/master/config/tendermint/config.go - - # genesis_file = "./data/tendermint/genesis.json" - # skip_upnp = false - # addrbook_file = "./data/tendermint/addrbook.json" - # addrbook_strict = true - # pex_reactor = false - # priv_validator_file = "./data/tendermint/priv_validator.json" - # db_dir = "./data/tendermint/data" - # prof_laddr = "" - # revision_file = "./data/tendermint/revision" - # cs_wal_file = "./data/tendermint/data/cs.wal/wal" - # cs_wal_light = false - # filter_peers = false - - # block_size = 10000 - # block_part_size = 65536 - # disable_data_hash = false - # timeout_propose = 3000 - # timeout_propose_delta = 500 - # timeout_prevote = 1000 - # timeout_prevote_delta = 500 - # timeout_precommit = 1000 - # timeout_precommit_delta = 500 - # timeout_commit = 1000 - # skip_timeout_commit = false - # mempool_recheck = true - # mempool_recheck_empty = true - # mempool_broadcast = true - # mempool_wal_dir = "./data/tendermint/data/mempool.wal" - - [tendermint.configuration.p2p] - # Switch config keys - dial_timeout_seconds = 3 - handshake_timeout_seconds = 20 - max_num_peers = 20 - authenticated_encryption = true - - # MConnection config keys - send_rate = 512000 - recv_rate = 512000 - - # Fuzz params - fuzz_enable = false # use the fuzz wrapped conn - fuzz_active = false # toggle fuzzing - fuzz_mode = "drop" # eg. drop, delay - fuzz_max_delay_milliseconds = 3000 - fuzz_prob_drop_rw = 0.2 - fuzz_prob_drop_conn = 0.00 - fuzz_prob_sleep = 0.00 - -` - -const sectionBurrowMint = ` -################################################################################ -## -## Burrow-Mint -## -## The original Ethereum virtual machine with IAVL merkle trees -## and tendermint/go-wire encoding -## -################################################################################ - -[burrowmint] -# Database backend to use for BurrowMint state database. -# Supported "leveldb" and "memdb". -db_backend = "leveldb" -# tendermint host address needs to correspond to tendermints configuration -# of the rpc local address -tendermint_host = "0.0.0.0:46657" - -` - -// TODO: [Silas]: before next logging release (finalising this stuff and adding -// presets) add full documentation of transforms, outputs, and their available -// configuration options. -const sectionLoggingHeader = ` -################################################################################ -## -## System-wide logging configuration -## -## Log messages are sent to one of two 'channels': info or trace -## -## They are delivered on a single non-blocking stream to a 'root sink'. -## -## A sink may optionally define any of a 'transform', an 'output', and a list of -## downstream sinks. Log messages flow through a sink by first having that -## sink's transform applied and then writing to its output and downstream sinks. -## In this way the log messages can output can be finely controlled and sent to -## a multiple different outputs having been filtered, modified, augmented, or -## labelled. This can be used to give more relevant output or to drive external -## systems. -## -## -################################################################################ - -## A minimal logging config for multi-line, colourised terminal output would be: -# -# [logging] -# [logging.root_sink] -# [logging.root_sink.output] -# output_type = "stderr" -# format = "terminal" -` diff --git a/config/viper.go b/config/viper.go deleted file mode 100644 index c59799a80210f0623a54eb674de0971307383290..0000000000000000000000000000000000000000 --- a/config/viper.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import ( - "fmt" - - "bytes" - - "github.com/spf13/viper" -) - -// Safely get the subtree from a viper config, returning an error if it could not -// be obtained for any reason. -func ViperSubConfig(conf *viper.Viper, configSubtreePath string) (subConfig *viper.Viper, err error) { - // Viper internally panics if `moduleName` contains an disallowed - // character (eg, a dash). - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("Viper panicked trying to read config subtree: %s", - configSubtreePath) - } - }() - if !conf.IsSet(configSubtreePath) { - return nil, fmt.Errorf("Failed to read config subtree: %s", - configSubtreePath) - } - subConfig = conf.Sub(configSubtreePath) - if subConfig == nil { - return nil, fmt.Errorf("Failed to read config subtree: %s", - configSubtreePath) - } - return subConfig, err -} - -// Read in TOML Viper config from bytes -func ReadViperConfig(configBytes []byte) (*viper.Viper, error) { - buf := bytes.NewBuffer(configBytes) - conf := viper.New() - conf.SetConfigType("toml") - err := conf.ReadConfig(buf) - if err != nil { - return nil, err - } - return conf, nil -} diff --git a/consensus/consensus.go b/consensus/consensus.go index d821f551476146abdedcf461a591c4e3c5605cb0..5280406014f99e32e0c86a7647ccc39eb9ee0601 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -1,58 +1,5 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +// Package consensus contains the logic maintaining and connecting to our Tendermint +// consensus network. The job of the consensus module is to establish a total order +// of transactions shared by all validators, which can then be executed over by the +// execution module. package consensus - -import ( - "fmt" - - config "github.com/hyperledger/burrow/config" - tendermint "github.com/hyperledger/burrow/consensus/tendermint" - definitions "github.com/hyperledger/burrow/definitions" -) - -func LoadConsensusEngineInPipe(moduleConfig *config.ModuleConfig, - pipe definitions.Pipe) error { - - // Check interface-level compatibility - if !pipe.GetApplication().CompatibleConsensus(&tendermint.Tendermint{}) { - return fmt.Errorf("Manager Application %s it no compatible with "+ - "%s consensus", moduleConfig.Name, pipe.GetApplication()) - } - - switch moduleConfig.Name { - case "tendermint": - - tmint, err := tendermint.NewTendermint(moduleConfig, pipe.GetApplication(), - pipe.Logger()) - if err != nil { - return fmt.Errorf("Failed to load Tendermint node: %v", err) - } - - err = pipe.SetConsensusEngine(tmint) - if err != nil { - return fmt.Errorf("Failed to load Tendermint in pipe as "+ - "ConsensusEngine: %v", err) - } - - // For Tendermint we have a coupled Blockchain and ConsensusEngine - // implementation, so load it at the same time as ConsensusEngine - err = pipe.SetBlockchain(tmint) - if err != nil { - return fmt.Errorf("Failed to load Tendermint in pipe as "+ - "Blockchain: %v", err) - } - } - return nil -} diff --git a/consensus/tendermint/abci/app.go b/consensus/tendermint/abci/app.go new file mode 100644 index 0000000000000000000000000000000000000000..497e00f1ba6929e6bba106f413472d94757b2377 --- /dev/null +++ b/consensus/tendermint/abci/app.go @@ -0,0 +1,175 @@ +package abci + +import ( + "fmt" + "sync" + "time" + + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/txs" + "github.com/hyperledger/burrow/version" + abci_types "github.com/tendermint/abci/types" + "github.com/tendermint/go-wire" +) + +const responseInfoName = "Burrow" + +type abciApp struct { + mtx sync.Mutex + // State + blockchain bcm.MutableBlockchain + checker execution.BatchExecutor + committer execution.BatchCommitter + // We need to cache these from BeginBlock for when we need actually need it in Commit + block *abci_types.RequestBeginBlock + // Utility + txDecoder txs.Decoder + // Logging + logger logging_types.InfoTraceLogger + +} + +func NewApp(blockchain bcm.MutableBlockchain, + checker execution.BatchExecutor, + committer execution.BatchCommitter, + logger logging_types.InfoTraceLogger) abci_types.Application { + return &abciApp{ + blockchain: blockchain, + checker: checker, + committer: committer, + txDecoder: txs.NewGoWireCodec(), + logger: logging.WithScope(logger.With(structure.ComponentKey, "ABCI_App"), "abci.NewApp"), + } +} + +func (app *abciApp) Info(info abci_types.RequestInfo) abci_types.ResponseInfo { + tip := app.blockchain.Tip() + return abci_types.ResponseInfo{ + Data: responseInfoName, + Version: version.GetSemanticVersionString(), + LastBlockHeight: tip.LastBlockHeight(), + LastBlockAppHash: tip.AppHashAfterLastBlock(), + } +} + +func (app *abciApp) SetOption(key string, value string) string { + return "No options available" +} + +func (app *abciApp) Query(reqQuery abci_types.RequestQuery) (respQuery abci_types.ResponseQuery) { + respQuery.Log = "Query not support" + respQuery.Code = abci_types.CodeType_UnknownRequest + return respQuery +} + +func (app *abciApp) CheckTx(txBytes []byte) abci_types.Result { + app.mtx.Lock() + defer app.mtx.Unlock() + tx, err := app.txDecoder.DecodeTx(txBytes) + if err != nil { + logging.TraceMsg(app.logger, "CheckTx decoding error", + "error", err) + return abci_types.NewError(abci_types.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) + } + // TODO: map ExecTx errors to sensible ABCI error codes + receipt := txs.GenerateReceipt(app.blockchain.ChainID(), tx) + + err = app.checker.Execute(tx) + if err != nil { + logging.TraceMsg(app.logger, "CheckTx execution error", + "error", err, + "tx_hash", receipt.TxHash, + "creates_contract", receipt.CreatesContract) + return abci_types.NewError(abci_types.CodeType_InternalError, + fmt.Sprintf("Could not execute transaction: %s, error: %v", tx, err)) + } + + receiptBytes := wire.BinaryBytes(receipt) + logging.TraceMsg(app.logger, "CheckTx success", + "tx_hash", receipt.TxHash, + "creates_contract", receipt.CreatesContract) + return abci_types.NewResultOK(receiptBytes, "Success") +} + +func (app *abciApp) InitChain(chain abci_types.RequestInitChain) { + // Could verify agreement on initial validator set here +} + +func (app *abciApp) BeginBlock(block abci_types.RequestBeginBlock) { + app.block = &block +} + +func (app *abciApp) DeliverTx(txBytes []byte) abci_types.Result { + app.mtx.Lock() + defer app.mtx.Unlock() + tx, err := app.txDecoder.DecodeTx(txBytes) + if err != nil { + logging.TraceMsg(app.logger, "DeliverTx decoding error", + "error", err) + return abci_types.NewError(abci_types.CodeType_EncodingError, fmt.Sprintf("Encoding error: %s", err)) + } + + receipt := txs.GenerateReceipt(app.blockchain.ChainID(), tx) + err = app.committer.Execute(tx) + if err != nil { + logging.TraceMsg(app.logger, "DeliverTx execution error", + "error", err, + "tx_hash", receipt.TxHash, + "creates_contract", receipt.CreatesContract) + return abci_types.NewError(abci_types.CodeType_InternalError, + fmt.Sprintf("Could not execute transaction: %s, error: %s", tx, err)) + } + + logging.TraceMsg(app.logger, "DeliverTx success", + "tx_hash", receipt.TxHash, + "creates_contract", receipt.CreatesContract) + receiptBytes := wire.BinaryBytes(receipt) + return abci_types.NewResultOK(receiptBytes, "Success") +} + +func (app *abciApp) EndBlock(height uint64) (respEndBlock abci_types.ResponseEndBlock) { + return respEndBlock +} + +func (app *abciApp) Commit() abci_types.Result { + app.mtx.Lock() + defer app.mtx.Unlock() + tip := app.blockchain.Tip() + logging.InfoMsg(app.logger, "Committing block", + structure.ScopeKey, "Commit()", + "block_height", tip.LastBlockHeight(), + "block_hash", app.block.Hash, + "block_time", app.block.Header.Time, + "num_txs", app.block.Header.NumTxs, + "last_block_time", tip.LastBlockTime(), + "last_block_hash", tip.LastBlockHash()) + + appHash, err := app.committer.Commit() + if err != nil { + return abci_types.NewError(abci_types.CodeType_InternalError, + fmt.Sprintf("Could not commit block: %s", err)) + } + + logging.InfoMsg(app.logger, "Resetting transaction check cache") + app.checker.Reset() + + // Commit to our blockchain state + app.blockchain.CommitBlock(time.Unix(int64(app.block.Header.Time), 0), app.block.Hash, appHash) + + // Perform a sanity check our block height + if app.blockchain.LastBlockHeight() != app.block.Header.Height { + logging.InfoMsg(app.logger, "Burrow block height disagrees with Tendermint block height", + structure.ScopeKey, "Commit()", + "burrow_height", app.blockchain.LastBlockHeight(), + "tendermint_height", app.block.Header.Height) + return abci_types.NewError(abci_types.CodeType_InternalError, + fmt.Sprintf("Burrow has recorded a block height of %v, "+ + "but Tendermint reports a block height of %v, and the two should agree.", + app.blockchain.LastBlockHeight(), app.block.Header.Height)) + } + return abci_types.NewResultOK(appHash, "Success") +} diff --git a/consensus/tendermint/config.go b/consensus/tendermint/config.go index b73a0b4f71ac424ba14b83320f3e8681015fd4c6..e1c898188577ad93910ac8577c839810274cc4d7 100644 --- a/consensus/tendermint/config.go +++ b/consensus/tendermint/config.go @@ -1,177 +1,42 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package tendermint import ( "path" - "time" - - "github.com/spf13/viper" - tendermintConfig "github.com/tendermint/go-config" - "github.com/hyperledger/burrow/config" + tm_config "github.com/tendermint/tendermint/config" ) -// NOTE [ben] Compiler check to ensure TendermintConfig successfully implements -// tendermint/go-config/config.Config -var _ tendermintConfig.Config = (*TendermintConfig)(nil) - -// Tendermint has a self-rolled configuration type defined -// in tendermint/go-config but over an interface type, which is implemented -// by default in tendermint/tendermint/config/tendermint.go -// However, for burrow purposes we can choose different rules for how to load -// the tendermint configuration and set the defaults. Hence we re-implement -// go-config.Config on a viper subtree of the loaded burrow configuration file. -type TendermintConfig struct { - subTree *viper.Viper -} - -func GetTendermintConfig(loadedConfig *viper.Viper) *TendermintConfig { - // ensure we make an explicit copy - subTree := new(viper.Viper) - *subTree = *loadedConfig - - return &TendermintConfig{ - subTree: subTree, +// Burrow's view on Tendermint's config. Since we operate as a Tendermint harness not all configuration values +// are applicable, we may not allow some values to specified, or we may not allow some to be set independently. +// So this serves as a layer of indirection over Tendermint's real config that we derive from ours. +type BurrowTendermintConfig struct { + Seeds string + ListenAddress string + Moniker string + TendermintRoot string +} + +func DefaultBurrowTendermintConfig() *BurrowTendermintConfig { + tmDefaultConfig := tm_config.DefaultConfig() + return &BurrowTendermintConfig{ + ListenAddress: tmDefaultConfig.P2P.ListenAddress, + TendermintRoot: ".burrow", } } -//------------------------------------------------------------------------------ -// Tendermint defaults - -// -// Contract -// - -func (tmintConfig *TendermintConfig) AssertTendermintDefaults(chainId, workDir, - dataDir, rootDir string) { - - tmintConfig.Set("chain_id", chainId) - tmintConfig.SetDefault("genesis_file", path.Join(rootDir, "genesis.json")) - tmintConfig.SetDefault("proxy_app", "tcp://127.0.0.1:46658") - tmintConfig.SetDefault("moniker", "anonymous_marmot") - tmintConfig.SetDefault("node_laddr", "0.0.0.0:46656") - tmintConfig.SetDefault("seeds", "") - - tmintConfig.SetDefault("fast_sync", true) - tmintConfig.SetDefault("skip_upnp", false) - tmintConfig.SetDefault("addrbook_file", path.Join(rootDir, "addrbook.json")) - tmintConfig.SetDefault("addrbook_strict", true) // disable to allow connections locally - tmintConfig.SetDefault("pex_reactor", false) // enable for peer exchange - tmintConfig.SetDefault("priv_validator_file", path.Join(rootDir, "priv_validator.json")) - tmintConfig.SetDefault("db_backend", "leveldb") - tmintConfig.SetDefault("db_dir", dataDir) - tmintConfig.SetDefault("log_level", "info") - tmintConfig.SetDefault("rpc_laddr", "") - tmintConfig.SetDefault("prof_laddr", "") - tmintConfig.SetDefault("revision_file", path.Join(workDir, "revision")) - tmintConfig.SetDefault("cs_wal_file", path.Join(dataDir, "cs.wal/wal")) - tmintConfig.SetDefault("cs_wal_light", false) - tmintConfig.SetDefault("filter_peers", false) - - tmintConfig.SetDefault("block_size", 10000) // max number of txs - tmintConfig.SetDefault("block_part_size", 65536) // part size 64K - tmintConfig.SetDefault("disable_data_hash", false) - tmintConfig.SetDefault("timeout_propose", 3000) - tmintConfig.SetDefault("timeout_propose_delta", 500) - tmintConfig.SetDefault("timeout_prevote", 1000) - tmintConfig.SetDefault("timeout_prevote_delta", 500) - tmintConfig.SetDefault("timeout_precommit", 1000) - tmintConfig.SetDefault("timeout_precommit_delta", 500) - tmintConfig.SetDefault("timeout_commit", 1000) - // make progress asap (no `timeout_commit`) on full precommit votes - tmintConfig.SetDefault("skip_timeout_commit", false) - tmintConfig.SetDefault("mempool_recheck", true) - tmintConfig.SetDefault("mempool_recheck_empty", true) - tmintConfig.SetDefault("mempool_broadcast", true) - tmintConfig.SetDefault("mempool_wal_dir", path.Join(dataDir, "mempool.wal")) -} - -//------------------------------------------------------------------------------ -// Tendermint consistency checks - -func (tmintConfig *TendermintConfig) AssertTendermintConsistency( - consensusConfig *config.ModuleConfig, privateValidatorFilePath string) { - - tmintConfig.Set("chain_id", consensusConfig.ChainId) - tmintConfig.Set("genesis_file", consensusConfig.GenesisFile) - // private validator file - tmintConfig.Set("priv_validator_file", privateValidatorFilePath) - -} - -// implement interface github.com/tendermint/go-config/config.Config -// so that `TMROOT` and config can be circumvented -func (tmintConfig *TendermintConfig) Get(key string) interface{} { - return tmintConfig.subTree.Get(key) -} - -func (tmintConfig *TendermintConfig) GetBool(key string) bool { - return tmintConfig.subTree.GetBool(key) -} - -func (tmintConfig *TendermintConfig) GetFloat64(key string) float64 { - return tmintConfig.subTree.GetFloat64(key) -} - -func (tmintConfig *TendermintConfig) GetInt(key string) int { - return tmintConfig.subTree.GetInt(key) -} - -func (tmintConfig *TendermintConfig) GetString(key string) string { - return tmintConfig.subTree.GetString(key) -} - -func (tmintConfig *TendermintConfig) GetStringSlice(key string) []string { - return tmintConfig.subTree.GetStringSlice(key) -} - -func (tmintConfig *TendermintConfig) GetTime(key string) time.Time { - return tmintConfig.subTree.GetTime(key) -} - -func (tmintConfig *TendermintConfig) GetMap(key string) map[string]interface{} { - return tmintConfig.subTree.GetStringMap(key) -} - -func (tmintConfig *TendermintConfig) GetMapString(key string) map[string]string { - return tmintConfig.subTree.GetStringMapString(key) -} - -func (tmintConfig *TendermintConfig) GetConfig(key string) tendermintConfig.Config { - // TODO: [ben] log out a warning as this indicates a potentially breaking code - // change from Tendermints side - subTree, _ := config.ViperSubConfig(tmintConfig.subTree, key) - if subTree == nil { - return &TendermintConfig{ - subTree: viper.New(), - } +func (btc *BurrowTendermintConfig) TendermintConfig() *tm_config.Config { + conf := tm_config.DefaultConfig() + if btc != nil { + // We may need to expose more of the P2P/Consensus/Mempool options, but I'd like to keep the configuration + // minimal + conf.P2P.Seeds = btc.Seeds + conf.P2P.ListenAddress = btc.ListenAddress + conf.Moniker = btc.Moniker + conf.DBPath = path.Join(btc.TendermintRoot, conf.DBPath) + conf.Mempool.WalPath = path.Join(btc.TendermintRoot, conf.Mempool.WalPath) + conf.Consensus.WalPath = path.Join(btc.TendermintRoot, conf.Consensus.WalPath) } - return &TendermintConfig{ - subTree: tmintConfig.subTree.Sub(key), - } -} - -func (tmintConfig *TendermintConfig) IsSet(key string) bool { - return tmintConfig.IsSet(key) -} - -func (tmintConfig *TendermintConfig) Set(key string, value interface{}) { - tmintConfig.subTree.Set(key, value) -} - -func (tmintConfig *TendermintConfig) SetDefault(key string, value interface{}) { - tmintConfig.subTree.SetDefault(key, value) + // Disable Tendermint RPC + conf.RPC.ListenAddress = "" + return conf } diff --git a/consensus/tendermint/logger.go b/consensus/tendermint/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..c42f46a4ca679890b6f67c5fd3f3978949bf0f8e --- /dev/null +++ b/consensus/tendermint/logger.go @@ -0,0 +1,35 @@ +package tendermint + +import ( + "github.com/hyperledger/burrow/logging" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/tendermint/tmlibs/log" +) + +type tendermintLogger struct { + logger logging_types.InfoTraceLogger +} + +func NewLogger(logger logging_types.InfoTraceLogger) *tendermintLogger { + return &tendermintLogger{ + logger: logger, + } +} + +func (tml *tendermintLogger) Info(msg string, keyvals ...interface{}) { + logging.InfoMsg(tml.logger, msg, keyvals...) +} + +func (tml *tendermintLogger) Error(msg string, keyvals ...interface{}) { + logging.InfoMsg(tml.logger, msg, keyvals...) +} + +func (tml *tendermintLogger) Debug(msg string, keyvals ...interface{}) { + logging.TraceMsg(tml.logger, msg, keyvals...) +} + +func (tml *tendermintLogger) With(keyvals ...interface{}) log.Logger { + return &tendermintLogger{ + logger: tml.logger.With(keyvals...), + } +} diff --git a/consensus/tendermint/query/node_view.go b/consensus/tendermint/query/node_view.go new file mode 100644 index 0000000000000000000000000000000000000000..7aa451766e419053cfecfe9b31071b0286e27497 --- /dev/null +++ b/consensus/tendermint/query/node_view.go @@ -0,0 +1,101 @@ +package query + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/txs" + "github.com/tendermint/tendermint/consensus" + ctypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/types" +) + +// You're like the interface I never had +type NodeView interface { + // PrivValidator public key + PrivValidatorPublicKey() acm.PublicKey + // NodeInfo for this node broadcast to other nodes (including ephemeral STS ED25519 public key) + NodeInfo() *p2p.NodeInfo + // Whether the Tendermint node is listening + IsListening() bool + // Current listeners + Listeners() []p2p.Listener + // Known Tendermint peers + Peers() p2p.IPeerSet + // Read-only BlockStore + BlockStore() types.BlockStoreRPC + // Get the currently unconfirmed but not known to be invalid transactions from the Node's mempool + MempoolTransactions(maxTxs int) ([]txs.Tx, error) + // Get the validator's consensus RoundState + RoundState() *ctypes.RoundState + // Get the validator's peer's consensus RoundState + PeerRoundStates() ([]*ctypes.PeerRoundState, error) +} + +type nodeView struct { + tmNode *node.Node + txDecoder txs.Decoder +} + +func NewNodeView(tmNode *node.Node, txDecoder txs.Decoder) NodeView { + return &nodeView{ + tmNode: tmNode, + txDecoder: txDecoder, + } +} + +func (nv *nodeView) PrivValidatorPublicKey() acm.PublicKey { + return acm.PublicKeyFromGoCryptoPubKey(nv.tmNode.PrivValidator().GetPubKey()) +} + +func (nv *nodeView) NodeInfo() *p2p.NodeInfo { + return nv.tmNode.NodeInfo() +} + +func (nv *nodeView) IsListening() bool { + return nv.tmNode.Switch().IsListening() +} + +func (nv *nodeView) Listeners() []p2p.Listener { + return nv.tmNode.Switch().Listeners() +} + +func (nv *nodeView) Peers() p2p.IPeerSet { + return nv.tmNode.Switch().Peers() +} + +func (nv *nodeView) BlockStore() types.BlockStoreRPC { + return nv.tmNode.BlockStore() +} + +// Pass -1 to get all available transactions +func (nv *nodeView) MempoolTransactions(maxTxs int) ([]txs.Tx, error) { + var transactions []txs.Tx + for _, txBytes := range nv.tmNode.MempoolReactor().Mempool.Reap(maxTxs) { + tx, err := nv.txDecoder.DecodeTx(txBytes) + if err != nil { + return nil, err + } + transactions = append(transactions, tx) + } + return transactions, nil +} + +func (nv *nodeView) RoundState() *ctypes.RoundState { + return nv.tmNode.ConsensusState().GetRoundState() +} + +func (nv *nodeView) PeerRoundStates() ([]*ctypes.PeerRoundState, error) { + peers := nv.tmNode.Switch().Peers().List() + peerRoundStates := make([]*ctypes.PeerRoundState, len(peers)) + for i, peer := range peers { + peerState, ok := peer.Get(types.PeerStateKey).(*consensus.PeerState) + if !ok { + return nil, fmt.Errorf("could not get PeerState for peer: %s", peer) + } + peerRoundStates[i] = peerState.GetRoundState() + } + return peerRoundStates, nil +} diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go index a35337cc3c4ac88f7c52934f0a51036723d4f625..8f38c5befd734d4676e0aec54d3ab209c7e9480a 100644 --- a/consensus/tendermint/tendermint.go +++ b/consensus/tendermint/tendermint.go @@ -1,288 +1,75 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package tendermint import ( "fmt" - "path" - "strings" - - abci_types "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - p2p "github.com/tendermint/go-p2p" - tendermint_consensus "github.com/tendermint/tendermint/consensus" - node "github.com/tendermint/tendermint/node" - proxy "github.com/tendermint/tendermint/proxy" - tendermint_types "github.com/tendermint/tendermint/types" - - edb_event "github.com/hyperledger/burrow/event" - config "github.com/hyperledger/burrow/config" - manager_types "github.com/hyperledger/burrow/manager/types" - // files "github.com/hyperledger/burrow/files" - "errors" - - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - consensus_types "github.com/hyperledger/burrow/consensus/types" - "github.com/hyperledger/burrow/logging" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/consensus/tendermint/abci" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging/structure" logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/txs" - "github.com/tendermint/go-wire" + abci_types "github.com/tendermint/abci/types" + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/proxy" + tm_types "github.com/tendermint/tendermint/types" ) -type Tendermint struct { - tmintNode *node.Node - tmintConfig *TendermintConfig - chainId string - logger logging_types.InfoTraceLogger -} - -// Compiler checks to ensure Tendermint successfully implements -// burrow/definitions Consensus and Blockchain -var _ consensus_types.ConsensusEngine = (*Tendermint)(nil) -var _ blockchain_types.Blockchain = (*Tendermint)(nil) - -func NewTendermint(moduleConfig *config.ModuleConfig, - application manager_types.Application, - logger logging_types.InfoTraceLogger) (*Tendermint, error) { - // loading the module has ensured the working and data directory - // for tendermint have been created, but the config files needs - // to be written in tendermint's root directory. - // NOTE: [ben] as elsewhere Sub panics if config file does not have this - // subtree. To shield in go-routine, or PR to viper. - if !moduleConfig.Config.IsSet("configuration") { - return nil, fmt.Errorf("Failed to extract Tendermint configuration subtree.") - } - tendermintConfigViper, err := config.ViperSubConfig(moduleConfig.Config, "configuration") - if tendermintConfigViper == nil { - return nil, - fmt.Errorf("Failed to extract Tendermint configuration subtree: %s", err) - } - // wrap a copy of the viper config in a tendermint/go-config interface - tmintConfig := GetTendermintConfig(tendermintConfigViper) - // complete the tendermint configuration with default flags - tmintConfig.AssertTendermintDefaults(moduleConfig.ChainId, - moduleConfig.WorkDir, moduleConfig.DataDir, moduleConfig.RootDir) - - privateValidatorFilePath := path.Join(moduleConfig.RootDir, - moduleConfig.Config.GetString("private_validator_file")) - if moduleConfig.Config.GetString("private_validator_file") == "" { - return nil, fmt.Errorf("No private validator file provided.") - } - // override tendermint configurations to force consistency with overruling - // settings - tmintConfig.AssertTendermintConsistency(moduleConfig, - privateValidatorFilePath) - chainId := tmintConfig.GetString("chain_id") - - logging.TraceMsg(logger, "Loaded Tendermint sub-configuration", - "chainId", chainId, - "genesisFile", tmintConfig.GetString("genesis_file"), - "nodeLocalAddress", tmintConfig.GetString("node_laddr"), - "moniker", tmintConfig.GetString("moniker"), - "seeds", tmintConfig.GetString("seeds"), - "fastSync", tmintConfig.GetBool("fast_sync"), - "rpcLocalAddress", tmintConfig.GetString("rpc_laddr"), - "databaseDirectory", tmintConfig.GetString("db_dir"), - "privateValidatorFile", tmintConfig.GetString("priv_validator_file"), - "privValFile", moduleConfig.Config.GetString("private_validator_file")) - - // TODO: [ben] do not "or Generate Validator keys", rather fail directly - // TODO: [ben] implement the signer for Private validator over monax-keys - // TODO: [ben] copy from rootDir to tendermint workingDir; - privateValidator := tendermint_types.LoadOrGenPrivValidator( - path.Join(moduleConfig.RootDir, - moduleConfig.Config.GetString("private_validator_file"))) - - // TODO: [Silas] we want to something better than this like not not have it in - // the config at all, but for now I think it's much safer to make sure we are - // not running the tendermint RPC as it could lead to unexpected behaviour, - // not least if we accidentally try to run it on the same address as our own - if tmintConfig.GetString("rpc_laddr") != "" { - logging.InfoMsg(logger, "Force disabling Tendermint's native RPC", - "provided_rpc_laddr", tmintConfig.GetString("rpc_laddr")) - tmintConfig.Set("rpc_laddr", "") - } - - newNode := node.NewNode(tmintConfig, privateValidator, - proxy.NewLocalClientCreator(application)) - - // TODO: [ben] delay starting the node to a different function, to hand - // control over events to Core - if started, err := newNode.Start(); !started { - newNode.Stop() +func NewNode( + conf *config.Config, + privValidator tm_types.PrivValidator, + genesisDoc *tm_types.GenesisDoc, + blockchain bcm.MutableBlockchain, + checker execution.BatchExecutor, + committer execution.BatchCommitter, + logger logging_types.InfoTraceLogger) (*node.Node, error) { + + // disable Tendermint's RPC + conf.RPC.ListenAddress = "" + + app := abci.NewApp(blockchain, checker, committer, logger) + return node.NewNode(conf, privValidator, + proxy.NewLocalClientCreator(app), + func() (*tm_types.GenesisDoc, error) { + return genesisDoc, nil + }, + node.DefaultDBProvider, + NewLogger(logger.WithPrefix(structure.ComponentKey, "Tendermint"). + With(structure.ScopeKey, "tendermint.NewNode"))) +} + +func BroadcastTxAsyncFunc(validator *node.Node, txEncoder txs.Encoder) func(tx txs.Tx, + callback func(res *abci_types.Response)) error { + + return func(tx txs.Tx, callback func(res *abci_types.Response)) error { + txBytes, err := txEncoder.EncodeTx(tx) if err != nil { - return nil, fmt.Errorf("Failed to start Tendermint consensus node: %v", err) + return fmt.Errorf("error encoding transaction: %v", err) } - return nil, errors.New("Failed to start Tendermint consensus node, " + - "probably because it is already started, see logs") - - } - logging.InfoMsg(logger, "Tendermint consensus node started", - "nodeAddress", tmintConfig.GetString("node_laddr"), - "transportProtocol", "tcp", - "upnp", !tmintConfig.GetBool("skip_upnp"), - "moniker", tmintConfig.GetString("moniker")) - - // If seedNode is provided by config, dial out. - if tmintConfig.GetString("seeds") != "" { - seeds := strings.Split(tmintConfig.GetString("seeds"), ",") - newNode.DialSeeds(seeds) - logging.TraceMsg(logger, "Tendermint node called seeds", - "seeds", seeds) - } - - return &Tendermint{ - tmintNode: newNode, - tmintConfig: tmintConfig, - chainId: chainId, - logger: logger, - }, nil -} - -//------------------------------------------------------------------------------ -// Blockchain implementation - -func (tendermint *Tendermint) Height() int { - return tendermint.tmintNode.BlockStore().Height() -} - -func (tendermint *Tendermint) BlockMeta(height int) *tendermint_types.BlockMeta { - return tendermint.tmintNode.BlockStore().LoadBlockMeta(height) -} -func (tendermint *Tendermint) Block(height int) *tendermint_types.Block { - return tendermint.tmintNode.BlockStore().LoadBlock(height) -} - -func (tendermint *Tendermint) ChainId() string { - return tendermint.chainId -} - -// Consensus implementation -func (tendermint *Tendermint) IsListening() bool { - return tendermint.tmintNode.Switch().IsListening() -} - -func (tendermint *Tendermint) Listeners() []p2p.Listener { - var copyListeners []p2p.Listener - // copy slice of Listeners - copy(copyListeners[:], tendermint.tmintNode.Switch().Listeners()) - return copyListeners -} - -func (tendermint *Tendermint) Peers() []*consensus_types.Peer { - p2pPeers := tendermint.tmintNode.Switch().Peers().List() - peers := make([]*consensus_types.Peer, 0) - for _, peer := range p2pPeers { - peers = append(peers, &consensus_types.Peer{ - NodeInfo: peer.NodeInfo, - IsOutbound: peer.IsOutbound(), - }) - } - return peers -} - -func (tendermint *Tendermint) NodeInfo() *p2p.NodeInfo { - var copyNodeInfo = new(p2p.NodeInfo) - // call Switch().NodeInfo is not go-routine safe, so copy - *copyNodeInfo = *tendermint.tmintNode.Switch().NodeInfo() - tendermint.tmintNode.ConsensusState().GetRoundState() - return copyNodeInfo -} - -func (tendermint *Tendermint) PublicValidatorKey() crypto.PubKey { - // TODO: [ben] this is abetment, not yet a go-routine safe solution - var copyPublicValidatorKey crypto.PubKey - // crypto.PubKey is an interface so copy underlying struct - publicKey := tendermint.tmintNode.PrivValidator().PubKey - switch publicKey.(type) { - case crypto.PubKeyEd25519: - // type crypto.PubKeyEd25519 is [32]byte - copyKeyBytes := publicKey.(crypto.PubKeyEd25519) - copyPublicValidatorKey = crypto.PubKey(copyKeyBytes) - default: - // TODO: [ben] add error return to all these calls - copyPublicValidatorKey = nil - } - return copyPublicValidatorKey -} - -func (tendermint *Tendermint) Events() edb_event.EventEmitter { - return edb_event.NewEvents(tendermint.tmintNode.EventSwitch(), tendermint.logger) -} - -func (tendermint *Tendermint) BroadcastTransaction(transaction []byte, - callback func(*abci_types.Response)) error { - return tendermint.tmintNode.MempoolReactor().BroadcastTx(transaction, callback) -} - -func (tendermint *Tendermint) ListUnconfirmedTxs( - maxTxs int) ([]txs.Tx, error) { - tendermintTxs := tendermint.tmintNode.MempoolReactor().Mempool.Reap(maxTxs) - transactions := make([]txs.Tx, len(tendermintTxs)) - for i, txBytes := range tendermintTxs { - tx, err := txs.DecodeTx(txBytes) + err = validator.MempoolReactor().BroadcastTx(txBytes, callback) if err != nil { - return nil, err + return fmt.Errorf("error broadcasting transaction: %v", err) } - transactions[i] = tx + return nil } - return transactions, nil -} - -func (tendermint *Tendermint) ListValidators() []consensus_types.Validator { - return consensus_types.FromTendermintValidators(tendermint.tmintNode. - ConsensusState().Validators.Validators) } -func (tendermint *Tendermint) ConsensusState() *consensus_types.ConsensusState { - return consensus_types.FromRoundState(tendermint.tmintNode.ConsensusState(). - GetRoundState()) -} - -func (tendermint *Tendermint) PeerConsensusStates() map[string]string { - peers := tendermint.tmintNode.Switch().Peers().List() - peerConsensusStates := make(map[string]string, - len(peers)) - for _, peer := range peers { - peerState := peer.Data.Get(tendermint_types.PeerStateKey).(*tendermint_consensus.PeerState) - peerRoundState := peerState.GetRoundState() - // TODO: implement a proper mapping, this is a nasty way of marshalling - // to JSON - peerConsensusStates[peer.Key] = string(wire.JSONBytes(peerRoundState)) +func DeriveGenesisDoc(burrowGenesisDoc *genesis.GenesisDoc) *tm_types.GenesisDoc { + validators := make([]tm_types.GenesisValidator, len(burrowGenesisDoc.Validators)) + for i, validator := range burrowGenesisDoc.Validators { + validators[i] = tm_types.GenesisValidator{ + PubKey: validator.PublicKey.PubKey, + Name: validator.Name, + Power: int64(validator.Amount), + } + } + return &tm_types.GenesisDoc{ + ChainID: burrowGenesisDoc.ChainID(), + GenesisTime: burrowGenesisDoc.GenesisTime, + Validators: validators, + AppHash: burrowGenesisDoc.Hash(), } - return peerConsensusStates -} - -// Allow for graceful shutdown of node. Returns whether the node was stopped. -func (tendermint *Tendermint) Stop() bool { - return tendermint.tmintNode.Stop() } - -//------------------------------------------------------------------------------ -// Helper functions - -// func marshalConfigToDisk(filePath string, tendermintConfig *viper.Viper) error { -// -// tendermintConfig.Unmarshal -// // marshal interface to toml bytes -// bytesConfig, err := toml.Marshal(tendermintConfig) -// if err != nil { -// return fmt.Fatalf("Failed to marshal Tendermint configuration to bytes: %v", -// err) -// } -// return files.WriteAndBackup(filePath, bytesConfig) -// } diff --git a/consensus/tendermint/validator/priv_validator_memory.go b/consensus/tendermint/validator/priv_validator_memory.go new file mode 100644 index 0000000000000000000000000000000000000000..90a51fa9f095bf4b4ae202bb7077e1fc80c4e03d --- /dev/null +++ b/consensus/tendermint/validator/priv_validator_memory.go @@ -0,0 +1,62 @@ +package validator + +import ( + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire/data" + tm_types "github.com/tendermint/tendermint/types" +) + +type privValidatorMemory struct { + acm.Addressable + acm.Signer + lastSignedInfo *LastSignedInfo +} + +var _ tm_types.PrivValidator = &privValidatorMemory{} + +// Create a PrivValidator with in-memory state that takes an addressable representing the validator identity +// and a signer providing private signing for that identity. +func NewPrivValidatorMemory(addressable acm.Addressable, signer acm.Signer) *privValidatorMemory { + return &privValidatorMemory{ + Addressable: addressable, + Signer: signer, + lastSignedInfo: new(LastSignedInfo), + } +} + +func (pvm *privValidatorMemory) GetAddress() data.Bytes { + return pvm.Address().Bytes() +} + +func (pvm *privValidatorMemory) GetPubKey() crypto.PubKey { + return pvm.PublicKey().PubKey +} + +func (pvm *privValidatorMemory) SignVote(chainID string, vote *tm_types.Vote) error { + return pvm.lastSignedInfo.SignVote(asTendermintSigner(pvm.Signer), chainID, vote) +} + +func (pvm *privValidatorMemory) SignProposal(chainID string, proposal *tm_types.Proposal) error { + return pvm.lastSignedInfo.SignProposal(asTendermintSigner(pvm.Signer), chainID, proposal) +} + +func (pvm *privValidatorMemory) SignHeartbeat(chainID string, heartbeat *tm_types.Heartbeat) error { + return pvm.lastSignedInfo.SignHeartbeat(asTendermintSigner(pvm.Signer), chainID, heartbeat) +} + +func asTendermintSigner(signer acm.Signer) tm_types.Signer { + return tendermintSigner{Signer: signer} +} + +type tendermintSigner struct { + acm.Signer +} + +func (tms tendermintSigner) Sign(msg []byte) (crypto.Signature, error) { + sig, err := tms.Signer.Sign(msg) + if err != nil { + return crypto.Signature{}, err + } + return sig.GoCryptoSignature(), nil +} diff --git a/consensus/tendermint/validator/verifier.go b/consensus/tendermint/validator/verifier.go new file mode 100644 index 0000000000000000000000000000000000000000..20d174dd6667aa9671d880f2d5aa784adc26bf55 --- /dev/null +++ b/consensus/tendermint/validator/verifier.go @@ -0,0 +1,143 @@ +package validator + +import ( + "bytes" + "errors" + "fmt" + "sync" + + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire/data" + tm_types "github.com/tendermint/tendermint/types" +) + +const ( + StepError = -1 + StepNone = 0 // Used to distinguish the initial state + StepPropose = 1 + StepPrevote = 2 + StepPrecommit = 3 +) + +func VoteToStep(vote *tm_types.Vote) int8 { + switch vote.Type { + case tm_types.VoteTypePrevote: + return StepPrevote + case tm_types.VoteTypePrecommit: + return StepPrecommit + default: + return StepError + } +} + +type Verifier interface { + SignVote(signer tm_types.Signer, chainID string, vote *tm_types.Vote) error + SignProposal(signer tm_types.Signer, chainID string, proposal *tm_types.Proposal) error + SignHeartbeat(signer tm_types.Signer, chainID string, heartbeat *tm_types.Heartbeat) error +} + +type LastSignedInfo struct { + mtx sync.Mutex + LastHeight int `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` + LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures + LastSignBytes data.Bytes `json:"last_signbytes,omitempty"` // so we dont lose signatures +} + +var _ Verifier = &LastSignedInfo{} + +// SignVote signs a canonical representation of the vote, along with the chainID. +// Implements PrivValidator. +func (lsi *LastSignedInfo) SignVote(signer tm_types.Signer, chainID string, vote *tm_types.Vote) error { + lsi.mtx.Lock() + defer lsi.mtx.Unlock() + signature, err := lsi.signBytesHRS(signer, vote.Height, vote.Round, VoteToStep(vote), tm_types.SignBytes(chainID, vote)) + if err != nil { + return fmt.Errorf("error signing vote: %v", err) + } + vote.Signature = signature + return nil +} + +// SignProposal signs a canonical representation of the proposal, along with the chainID. +// Implements PrivValidator. +func (lsi *LastSignedInfo) SignProposal(signer tm_types.Signer, chainID string, proposal *tm_types.Proposal) error { + lsi.mtx.Lock() + defer lsi.mtx.Unlock() + signature, err := lsi.signBytesHRS(signer, proposal.Height, proposal.Round, StepPropose, tm_types.SignBytes(chainID, proposal)) + if err != nil { + return fmt.Errorf("error signing proposal: %v", err) + } + proposal.Signature = signature + return nil +} + +// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. +// Implements PrivValidator. +func (lsi *LastSignedInfo) SignHeartbeat(signer tm_types.Signer, chainID string, heartbeat *tm_types.Heartbeat) error { + lsi.mtx.Lock() + defer lsi.mtx.Unlock() + var err error + heartbeat.Signature, err = signer.Sign(tm_types.SignBytes(chainID, heartbeat)) + return err +} + +// signBytesHRS signs the given signBytes if the height/round/step (HRS) +// are greater than the latest state. If the HRS are equal, +// it returns the privValidator.LastSignature. +func (lsi *LastSignedInfo) signBytesHRS(signer tm_types.Signer, height, round int, step int8, signBytes []byte) (crypto.Signature, error) { + + sig := crypto.Signature{} + // If height regression, err + if lsi.LastHeight > height { + return sig, errors.New("height regression") + } + // More cases for when the height matches + if lsi.LastHeight == height { + // If round regression, err + if lsi.LastRound > round { + return sig, errors.New("round regression") + } + // If step regression, err + if lsi.LastRound == round { + if lsi.LastStep > step { + return sig, errors.New("step regression") + } else if lsi.LastStep == step { + if lsi.LastSignBytes != nil { + if lsi.LastSignature.Empty() { + return crypto.Signature{}, errors.New("lsi: LastSignature is nil but LastSignBytes is not") + } + // so we dont sign a conflicting vote or proposal + // NOTE: proposals are non-deterministic (include time), + // so we can actually lose them, but will still never sign conflicting ones + if bytes.Equal(lsi.LastSignBytes, signBytes) { + // log.Notice("Using lsi.LastSignature", "sig", lsi.LastSignature) + return lsi.LastSignature, nil + } + } + return sig, errors.New("step regression") + } + } + } + + // Sign + sig, err := signer.Sign(signBytes) + if err != nil { + return sig, err + } + + // Persist height/round/step + lsi.LastHeight = height + lsi.LastRound = round + lsi.LastStep = step + lsi.LastSignature = sig + lsi.LastSignBytes = signBytes + + return sig, nil +} + +func (lsi *LastSignedInfo) String() string { + return fmt.Sprintf("LastSignedInfo{LastHeight:%v, LastRound:%v, LastStep:%v}", + lsi.LastHeight, lsi.LastRound, lsi.LastStep) +} diff --git a/consensus/tendermint/version.go b/consensus/tendermint/version.go deleted file mode 100644 index 58f690db7dbace4b8ebb5852457d7fdb26c867c8..0000000000000000000000000000000000000000 --- a/consensus/tendermint/version.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tendermint - -import ( - "strconv" - - tendermint_version "github.com/tendermint/tendermint/version" - - version "github.com/hyperledger/burrow/version" -) - -const ( - // Client identifier to advertise over the network - tendermintClientIdentifier = "tendermint" -) - -var ( - tendermintVersionMajor uint8 - tendermintVersionMinor uint8 - tendermintVersionPatch uint8 -) - -func init() { - // discard error because we test for this in Continuous Integration tests - tendermintVersionMajor, _ = getTendermintMajorVersionFromSource() - tendermintVersionMinor, _ = getTendermintMinorVersionFromSource() - tendermintVersionPatch, _ = getTendermintPatchVersionFromSource() -} - -func GetTendermintVersion() *version.VersionIdentifier { - return version.New(tendermintClientIdentifier, tendermintVersionMajor, - tendermintVersionMinor, tendermintVersionPatch) -} - -func getTendermintMajorVersionFromSource() (uint8, error) { - majorVersionUint, err := strconv.ParseUint(tendermint_version.Maj, 10, 8) - if err != nil { - return 0, err - } - return uint8(majorVersionUint), nil -} - -func getTendermintMinorVersionFromSource() (uint8, error) { - minorVersionUint, err := strconv.ParseUint(tendermint_version.Min, 10, 8) - if err != nil { - return 0, err - } - return uint8(minorVersionUint), nil -} - -func getTendermintPatchVersionFromSource() (uint8, error) { - patchVersionUint, err := strconv.ParseUint(tendermint_version.Fix, 10, 8) - if err != nil { - return 0, err - } - return uint8(patchVersionUint), nil -} diff --git a/consensus/tendermint/version_test.go b/consensus/tendermint/version_test.go deleted file mode 100644 index 330c77425a6a281581882bda8424620873a84773..0000000000000000000000000000000000000000 --- a/consensus/tendermint/version_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tendermint - -import "testing" - -func TestSemanticVersioningTendermint(t *testing.T) { - // assert that reading the semantic version from Tendermint vendored source - // succeeds without error; at runtime initialisation, on error we default - // to hard-coded semantic version - if _, err := getTendermintMajorVersionFromSource(); err != nil { - t.Errorf("Failed to read Major version from Tendermint source code: %s", err) - t.Fail() - } - if _, err := getTendermintMinorVersionFromSource(); err != nil { - t.Errorf("Failed to read Minor version from Tendermint source code: %s", err) - t.Fail() - } - if _, err := getTendermintPatchVersionFromSource(); err != nil { - t.Errorf("Failed to read Patch version from Tendermint source code: %s", err) - t.Fail() - } -} diff --git a/consensus/types/consensus_engine.go b/consensus/types/consensus_engine.go deleted file mode 100644 index b1677d7a53fe8ef14181738ae17f93ff59748d22..0000000000000000000000000000000000000000 --- a/consensus/types/consensus_engine.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/txs" - abci_types "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-p2p" -) - -type ConsensusEngine interface { - // Peer-2-Peer - IsListening() bool - Listeners() []p2p.Listener - NodeInfo() *p2p.NodeInfo - Peers() []*Peer - - // Private Validator - PublicValidatorKey() crypto.PubKey - - // Memory pool - BroadcastTransaction(transaction []byte, - callback func(*abci_types.Response)) error - - // Events - // For consensus events like NewBlock - Events() event.EventEmitter - - // List pending transactions in the mempool, passing 0 for maxTxs gets an - // unbounded number of transactions - ListUnconfirmedTxs(maxTxs int) ([]txs.Tx, error) - ListValidators() []Validator - ConsensusState() *ConsensusState - // TODO: Consider creating a real type for PeerRoundState, but at the looks - // quite coupled to tendermint - PeerConsensusStates() map[string]string - - // Allow for graceful shutdown of node. Returns whether the node was stopped. - Stop() bool -} diff --git a/consensus/types/consensus_state.go b/consensus/types/consensus_state.go deleted file mode 100644 index 74d8b4b4a2167d5061bc120ac930409ace542570..0000000000000000000000000000000000000000 --- a/consensus/types/consensus_state.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "time" - - tendermint_consensus "github.com/tendermint/tendermint/consensus" - tendermint_types "github.com/tendermint/tendermint/types" -) - -// ConsensusState -type ConsensusState struct { - Height int `json:"height"` - Round int `json:"round"` - Step uint8 `json:"step"` - StartTime time.Time `json:"start_time"` - CommitTime time.Time `json:"commit_time"` - Validators []Validator `json:"validators"` - Proposal *tendermint_types.Proposal `json:"proposal"` -} - -func FromRoundState(rs *tendermint_consensus.RoundState) *ConsensusState { - cs := &ConsensusState{ - StartTime: rs.StartTime, - CommitTime: rs.CommitTime, - Height: rs.Height, - Proposal: rs.Proposal, - Round: rs.Round, - Step: uint8(rs.Step), - Validators: FromTendermintValidators(rs.Validators.Validators), - } - return cs -} diff --git a/consensus/types/validator.go b/consensus/types/validator.go deleted file mode 100644 index 86fcfac1a6d7badbd2792eb5cae07f7156b32038..0000000000000000000000000000000000000000 --- a/consensus/types/validator.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "github.com/tendermint/go-wire" - tendermint_types "github.com/tendermint/tendermint/types" -) - -var _ = wire.RegisterInterface( - struct{ Validator }{}, - wire.ConcreteType{&TendermintValidator{}, byte(0x01)}, -) - -type Validator interface { - AssertIsValidator() - Address() []byte -} - -// Anticipating moving to our own definition of Validator, or at least -// augmenting Tendermint's. -type TendermintValidator struct { - *tendermint_types.Validator `json:"validator"` -} - -var _ Validator = (*TendermintValidator)(nil) - -func (tendermintValidator *TendermintValidator) AssertIsValidator() { - -} - -func (tendermintValidator *TendermintValidator) Address() []byte { - return tendermintValidator.Address() -} - -//------------------------------------------------------------------------------------- -// Helper function for TendermintValidator - -func FromTendermintValidators(tmValidators []*tendermint_types.Validator) []Validator { - validators := make([]Validator, len(tmValidators)) - for i, tmValidator := range tmValidators { - // This embedding could be replaced by a mapping once if we want to describe - // a more general notion of validator - validators[i] = &TendermintValidator{tmValidator} - } - return validators -} diff --git a/core/config.go b/core/config.go deleted file mode 100644 index bddb21b8491f69ed719230ceba412aa2c34aa028..0000000000000000000000000000000000000000 --- a/core/config.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// config.go keeps explicit structures on the runtime configuration of -// burrow and all modules. It loads these from the Viper configuration -// loaded in `definitions.Do` - -package core - -import ( - "fmt" - "os" - "path" - - "github.com/hyperledger/burrow/config" - "github.com/hyperledger/burrow/definitions" - lconfig "github.com/hyperledger/burrow/logging/config" - "github.com/hyperledger/burrow/server" - "github.com/hyperledger/burrow/util" - "github.com/spf13/viper" -) - -// LoadConsensusModuleConfig wraps specifically for the consensus module -func LoadConsensusModuleConfig(do *definitions.Do) (*config.ModuleConfig, error) { - return loadModuleConfigFromDo(do, "consensus") -} - -// LoadApplicationManagerModuleConfig wraps specifically for the application -// manager -func LoadApplicationManagerModuleConfig(do *definitions.Do) (*config.ModuleConfig, error) { - return loadModuleConfigFromDo(do, "manager") -} - -func loadModuleConfigFromDo(do *definitions.Do, module string) (*config.ModuleConfig, error) { - return LoadModuleConfig(do.Config, do.WorkDir, do.DataDir, - do.GenesisFile, do.ChainId, module) -} - -// Generic Module loader for configuration information -func LoadModuleConfig(conf *viper.Viper, rootWorkDir, rootDataDir, - genesisFile, chainId, module string) (*config.ModuleConfig, error) { - moduleName := conf.GetString("chain." + module + ".name") - // set up the directory structure for the module inside the data directory - workDir := path.Join(rootDataDir, conf.GetString("chain."+module+ - ".relative_root")) - if err := util.EnsureDir(workDir, os.ModePerm); err != nil { - return nil, - fmt.Errorf("Failed to create module root directory %s.", workDir) - } - dataDir := path.Join(workDir, "data") - if err := util.EnsureDir(dataDir, os.ModePerm); err != nil { - return nil, - fmt.Errorf("Failed to create module data directory %s.", dataDir) - } - // load configuration subtree for module - if !conf.IsSet(moduleName) { - return nil, fmt.Errorf("Failed to read configuration section for %s", - moduleName) - } - subConfig, err := config.ViperSubConfig(conf, moduleName) - if subConfig == nil { - return nil, fmt.Errorf("Failed to read configuration section for %s: %s", - moduleName, err) - } - - return &config.ModuleConfig{ - Module: module, - Name: moduleName, - WorkDir: workDir, - DataDir: dataDir, - RootDir: rootWorkDir, // burrow's working directory - ChainId: chainId, - GenesisFile: genesisFile, - Config: subConfig, - }, nil -} - -// Load the ServerConfig from commandline Do object -func LoadServerConfigFromDo(do *definitions.Do) (*server.ServerConfig, error) { - // load configuration subtree for servers - return LoadServerConfig(do.ChainId, do.Config) -} - -// Load the ServerConfig from root Viper config, fixing the ChainId -func LoadServerConfig(chainId string, rootConfig *viper.Viper) (*server.ServerConfig, error) { - subConfig, err := config.ViperSubConfig(rootConfig, "servers") - if err != nil { - return nil, err - } - serverConfig, err := server.ReadServerConfig(subConfig) - if err != nil { - return nil, err - } - serverConfig.ChainId = chainId - return serverConfig, err -} - -func LoadLoggingConfigFromDo(do *definitions.Do) (*lconfig.LoggingConfig, error) { - if !do.Config.IsSet("logging") { - return nil, nil - } - loggingConfigMap := do.Config.GetStringMap("logging") - return lconfig.LoggingConfigFromMap(loggingConfigMap) -} - -func LoadLoggingConfigFromClientDo(do *definitions.ClientDo) (*lconfig.LoggingConfig, error) { - loggingConfig := lconfig.DefaultClientLoggingConfig() - return loggingConfig, nil -} diff --git a/core/config_test.go b/core/config_test.go deleted file mode 100644 index ef552d2b0581d48538e6119a2b5615474fe05de4..0000000000000000000000000000000000000000 --- a/core/config_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package core - -import ( - "testing" - - "github.com/hyperledger/burrow/config" - "github.com/hyperledger/burrow/definitions" - lconfig "github.com/hyperledger/burrow/logging/config" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" -) - -func TestLoadLoggingConfigFromDo(t *testing.T) { - do := new(definitions.Do) - do.Config = viper.New() - lc, err := LoadLoggingConfigFromDo(do) - assert.NoError(t, err) - assert.Nil(t, lc, "Should get nil logging config when [logging] not set") - cnf, err := config.ReadViperConfig(([]byte)(lconfig.DefaultNodeLoggingConfig().RootTOMLString())) - assert.NoError(t, err) - do.Config = cnf - lc, err = LoadLoggingConfigFromDo(do) - assert.NoError(t, err) - assert.EqualValues(t, lconfig.DefaultNodeLoggingConfig(), lc) -} diff --git a/core/core.go b/core/core.go deleted file mode 100644 index 3727a823203850afe0881b950ebb41b6a10ec253..0000000000000000000000000000000000000000 --- a/core/core.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package core - -import ( - "fmt" - - // TODO: [ben] swap out go-events with burrow/event (currently unused) - events "github.com/tendermint/go-events" - - "github.com/hyperledger/burrow/config" - "github.com/hyperledger/burrow/consensus" - "github.com/hyperledger/burrow/definitions" - "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/manager" - // rpc_v0 is carried over from burrowv0.11 and before on port 1337 - rpc_v0 "github.com/hyperledger/burrow/rpc/v0" - // rpc_tendermint is carried over from burrowv0.11 and before on port 46657 - - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" - rpc_tendermint "github.com/hyperledger/burrow/rpc/tendermint/core" - "github.com/hyperledger/burrow/server" -) - -// Core is the high-level structure -type Core struct { - chainId string - evsw events.EventSwitch - pipe definitions.Pipe - tendermintPipe definitions.TendermintPipe - logger logging_types.InfoTraceLogger -} - -func NewCore(chainId string, - consensusConfig *config.ModuleConfig, - managerConfig *config.ModuleConfig, - logger logging_types.InfoTraceLogger) (*Core, error) { - // start new event switch, TODO: [ben] replace with burrow/event - evsw := events.NewEventSwitch() - evsw.Start() - logger = logging.WithScope(logger, "Core") - - // start a new application pipe that will load an application manager - pipe, err := manager.NewApplicationPipe(managerConfig, evsw, logger) - if err != nil { - return nil, fmt.Errorf("Failed to load application pipe: %v", err) - } - logging.TraceMsg(logger, "Loaded pipe with application manager") - // pass the consensus engine into the pipe - if e := consensus.LoadConsensusEngineInPipe(consensusConfig, pipe); e != nil { - return nil, fmt.Errorf("Failed to load consensus engine in pipe: %v", e) - } - tendermintPipe, err := pipe.GetTendermintPipe() - if err != nil { - logging.TraceMsg(logger, "Tendermint gateway not supported by manager", - "manager", managerConfig.Name) - } - return &Core{ - chainId: chainId, - evsw: evsw, - pipe: pipe, - tendermintPipe: tendermintPipe, - logger: logger, - }, nil -} - -//------------------------------------------------------------------------------ -// Explicit switch that can later be abstracted into an `Engine` definition -// where the Engine defines the explicit interaction of a specific application -// manager with a consensus engine. -// TODO: [ben] before such Engine abstraction, -// think about many-manager-to-one-consensus - -//------------------------------------------------------------------------------ -// Server functions -// NOTE: [ben] in phase 0 we exactly take over the full server architecture -// from burrow and Tendermint; This is a draft and will be overhauled. - -func (core *Core) NewGatewayV0(config *server.ServerConfig) (*server.ServeProcess, - error) { - codec := &rpc_v0.TCodec{} - eventSubscriptions := event.NewEventSubscriptions(core.pipe.Events()) - // The services. - tmwss := rpc_v0.NewBurrowWsService(codec, core.pipe) - tmjs := rpc_v0.NewBurrowJsonService(codec, core.pipe, eventSubscriptions) - // The servers. - jsonServer := rpc_v0.NewJsonRpcServer(tmjs) - restServer := rpc_v0.NewRestServer(codec, core.pipe, eventSubscriptions) - wsServer := server.NewWebSocketServer(config.WebSocket.MaxWebSocketSessions, - tmwss, core.logger) - // Create a server process. - proc, err := server.NewServeProcess(config, core.logger, jsonServer, restServer, wsServer) - if err != nil { - return nil, fmt.Errorf("Failed to load gateway: %v", err) - } - - return proc, nil -} - -func (core *Core) NewGatewayTendermint(config *server.ServerConfig) ( - *rpc_tendermint.TendermintWebsocketServer, error) { - if core.tendermintPipe == nil { - return nil, fmt.Errorf("No Tendermint pipe has been initialised for Tendermint gateway.") - } - return rpc_tendermint.NewTendermintWebsocketServer(config, - core.tendermintPipe, core.evsw) -} - -// Stop the core allowing for a graceful shutdown of component in order. -func (core *Core) Stop() bool { - return core.pipe.GetConsensusEngine().Stop() -} diff --git a/core/kernel.go b/core/kernel.go new file mode 100644 index 0000000000000000000000000000000000000000..46376d4b3c2c467159ee55bf50c3a2fa8f48ef9e --- /dev/null +++ b/core/kernel.go @@ -0,0 +1,173 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "net" + "os" + "os/signal" + "sync" + "time" + + "fmt" + + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/consensus/tendermint" + "github.com/hyperledger/burrow/consensus/tendermint/query" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc/tm" + "github.com/hyperledger/burrow/txs" + tm_config "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/node" + tm_types "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/events" +) + +const CooldownMilliseconds = 1000 + +// Kernel is the root structure of Burrow +type Kernel struct { + eventSwitch events.EventSwitch + tmNode *node.Node + service rpc.Service + serverLaunchers []ServerLauncher + listeners []net.Listener + logger logging_types.InfoTraceLogger + shutdownNotify chan struct{} + shutdownOnce sync.Once +} + +type ServerLauncher struct { + Name string + Launch func(rpc.Service) (net.Listener, error) +} + +func NewKernel(privValidator tm_types.PrivValidator, genesisDoc *genesis.GenesisDoc, tmConf *tm_config.Config, + rpcConfig *rpc.RPCConfig, logger logging_types.InfoTraceLogger) (*Kernel, error) { + + events.NewEventSwitch().Start() + logger = logging.WithScope(logger, "NewKernel") + + stateDB := dbm.NewDB("burrow_state", dbm.GoLevelDBBackendStr, tmConf.DBDir()) + state := execution.MakeGenesisState(stateDB, genesisDoc) + state.Save() + + blockchain := bcm.NewBlockchain(genesisDoc) + evmEvents := event.NewEmitter(logger) + + tmGenesisDoc := tendermint.DeriveGenesisDoc(genesisDoc) + checker := execution.NewBatchChecker(state, tmGenesisDoc.ChainID, blockchain, logger) + committer := execution.NewBatchCommitter(state, tmGenesisDoc.ChainID, blockchain, evmEvents, logger) + tmNode, err := tendermint.NewNode(tmConf, privValidator, tmGenesisDoc, blockchain, checker, committer, logger) + if err != nil { + return nil, err + } + // Multiplex Tendermint and EVM events + eventEmitter := event.Multiplex(evmEvents, event.WrapEventSwitch(tmNode.EventSwitch(), logger)) + + txCodec := txs.NewGoWireCodec() + nameReg := execution.NewNameReg(state, blockchain) + + transactor := execution.NewTransactor(blockchain, state, eventEmitter, + tendermint.BroadcastTxAsyncFunc(tmNode, txCodec), logger) + + // TODO: consider whether we need to be more explicit about pre-commit (check cache) versus committed (state) values + // Note we pass the checker as the StateIterable to NewService which means the RPC layers will query the check + // cache state. This is in line with previous behaviour of Burrow and chiefly serves to get provide a pre-commit + // view of sequence values on the node that a client is communicating with. + // Since we don't currently execute EVM code in the checker possible conflicts are limited to account creation + // which increments the creator's account Sequence and SendTxs + service := rpc.NewService(state, eventEmitter, nameReg, blockchain, transactor, query.NewNodeView(tmNode, txCodec), + logger) + + servers := []ServerLauncher{ + { + Name: "TM", + Launch: func(service rpc.Service) (net.Listener, error) { + return tm.StartServer(service, "/websocket", rpcConfig.TM.ListenAddress, eventEmitter, logger) + }, + }, + } + + return &Kernel{ + eventSwitch: eventEmitter, + tmNode: tmNode, + service: service, + serverLaunchers: servers, + logger: logger, + shutdownNotify: make(chan struct{}), + }, nil +} + +// Boot the kernel starting Tendermint and RPC layers +func (kern *Kernel) Boot() error { + _, err := kern.tmNode.Start() + if err != nil { + return fmt.Errorf("error starting Tendermint node: %v", err) + } + for _, launcher := range kern.serverLaunchers { + listener, err := launcher.Launch(kern.service) + if err != nil { + return fmt.Errorf("error launching %s server", launcher.Name) + } + + kern.listeners = append(kern.listeners, listener) + } + go kern.supervise() + return nil +} + +// Wait for a graceful shutdown +func (kern *Kernel) WaitForShutdown() { + // Supports multiple goroutines waiting for shutdown since channel is closed + <-kern.shutdownNotify +} + +// Supervise kernel once booted +func (kern *Kernel) supervise() { + // TODO: Consider capturing kernel panics from boot and sending them here via a channel where we could + // perform disaster restarts of the kernel; rejoining the network as if we were a new node. + signals := make(chan os.Signal, 1) + signal.Notify(signals, os.Interrupt, os.Kill) + <-signals + kern.Shutdown() +} + +// Stop the kernel allowing for a graceful shutdown of components in order +func (kern *Kernel) Shutdown() (err error) { + kern.shutdownOnce.Do(func() { + logger := logging.WithScope(kern.logger, "Shutdown") + logging.InfoMsg(logger, "Attempting graceful shutdown...") + logging.InfoMsg(logger, "Shutting down listeners") + for _, listener := range kern.listeners { + err = listener.Close() + } + logging.InfoMsg(logger, "Shutting down Tendermint node") + kern.tmNode.Stop() + logging.InfoMsg(logger, "Shutdown complete") + logging.Sync(kern.logger) + // We don't want to wait for them, but yielding for a cooldown Let other goroutines flush + // potentially interesting final output (e.g. log messages) + time.Sleep(time.Millisecond * CooldownMilliseconds) + close(kern.shutdownNotify) + }) + return +} diff --git a/core/kernel_test.go b/core/kernel_test.go new file mode 100644 index 0000000000000000000000000000000000000000..857d739924681afdbc3a83fe0fd16410f4b305f7 --- /dev/null +++ b/core/kernel_test.go @@ -0,0 +1,33 @@ +package core + +import ( + "os" + "testing" + + "github.com/hyperledger/burrow/consensus/tendermint/validator" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/rpc" + "github.com/stretchr/testify/require" + tm_config "github.com/tendermint/tendermint/config" +) + +const testDir = "./test_scratch/kernel_test" + +func TestBootThenShutdown(t *testing.T) { + + os.RemoveAll(testDir) + os.MkdirAll(testDir, 0777) + os.Chdir(testDir) + tmConf := tm_config.DefaultConfig() + //logger, _ := lifecycle.NewStdErrLogger() + logger := loggers.NewNoopInfoTraceLogger() + genesisDoc, privateAccounts := genesis.NewDeterministicGenesis(123).GenesisDoc(1, true, 1000, 1, true, 1000) + privValidator := validator.NewPrivValidatorMemory(privateAccounts[0], privateAccounts[0]) + kern, err := NewKernel(privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), logger) + require.NoError(t, err) + err = kern.Boot() + require.NoError(t, err) + err = kern.Shutdown() + require.NoError(t, err) +} diff --git a/core/types/types.go b/core/types/types.go index 5b0934c164e3b90cf3245578ab0c70d6c76dbf9b..220b9e3742c547d5446bbee9036c643fc947e1f9 100644 --- a/core/types/types.go +++ b/core/types/types.go @@ -14,45 +14,19 @@ package types -// TODO: [ben] this is poorly constructed but copied over -// from burrow/burrow/pipe/types to make incremental changes and allow -// for a discussion around the proper defintion of the needed types. - import ( - // NodeInfo (drop this!) - "github.com/tendermint/tendermint/types" - - account "github.com/hyperledger/burrow/account" + tm_types "github.com/tendermint/tendermint/types" ) type ( - // *********************************** Address *********************************** - - // Accounts - AccountList struct { - Accounts []*account.Account `json:"accounts"` - } - - // A contract account storage item. - StorageItem struct { - Key []byte `json:"key"` - Value []byte `json:"value"` - } - - // Account storage - Storage struct { - StorageRoot []byte `json:"storage_root"` - StorageItems []StorageItem `json:"storage_items"` - } - // *********************************** Blockchain *********************************** // BlockchainInfo BlockchainInfo struct { - ChainId string `json:"chain_id"` - GenesisHash []byte `json:"genesis_hash"` - LatestBlockHeight int `json:"latest_block_height"` - LatestBlock *types.BlockMeta `json:"latest_block"` + ChainId string `json:"chain_id"` + GenesisHash []byte `json:"genesis_hash"` + LatestBlockHeight uint64 `json:"latest_block_height"` + LatestBlock *tm_types.BlockMeta `json:"latest_block"` } // Genesis hash @@ -62,7 +36,7 @@ type ( // Get the latest LatestBlockHeight struct { - Height int `json:"height"` + Height uint64 `json:"height"` } ChainId struct { @@ -71,78 +45,17 @@ type ( // GetBlocks Blocks struct { - MinHeight int `json:"min_height"` - MaxHeight int `json:"max_height"` - BlockMetas []*types.BlockMeta `json:"block_metas"` + MinHeight uint64 `json:"min_height"` + MaxHeight uint64 `json:"max_height"` + BlockMetas []*tm_types.BlockMeta `json:"block_metas"` } // *********************************** Consensus *********************************** // Validators ValidatorList struct { - BlockHeight int `json:"block_height"` - BondedValidators []*types.Validator `json:"bonded_validators"` - UnbondingValidators []*types.Validator `json:"unbonding_validators"` - } - - // *********************************** Events *********************************** - - // EventSubscribe - EventSub struct { - SubId string `json:"sub_id"` - } - - // EventUnsubscribe - EventUnsub struct { - Result bool `json:"result"` - } - - // EventPoll - PollResponse struct { - Events []interface{} `json:"events"` - } - - // *********************************** Network *********************************** - - ClientVersion struct { - ClientVersion string `json:"client_version"` - } - - Moniker struct { - Moniker string `json:"moniker"` - } - - Listening struct { - Listening bool `json:"listening"` - } - - Listeners struct { - Listeners []string `json:"listeners"` - } - - // *********************************** Transactions *********************************** - - // Call or CallCode - Call struct { - Return string `json:"return"` - GasUsed int64 `json:"gas_used"` - // TODO ... - } -) - -//------------------------------------------------------------------------------ -// copied in from NameReg - -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 - } - - ResultListNames struct { - BlockHeight int `json:"block_height"` - Names []*NameRegEntry `json:"names"` + BlockHeight uint64 `json:"block_height"` + BondedValidators []*tm_types.Validator `json:"bonded_validators"` + UnbondingValidators []*tm_types.Validator `json:"unbonding_validators"` } ) diff --git a/definitions/do.go b/definitions/do.go deleted file mode 100644 index 8e31e596f1cb8c203f6cd0c1b64da3aec218e72f..0000000000000000000000000000000000000000 --- a/definitions/do.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package definitions - -import ( - "os" - "path" - - viper "github.com/spf13/viper" - - util "github.com/hyperledger/burrow/util" -) - -type Do struct { - // Persistent flags not reflected in the configuration files - // only set through command line flags or environment variables - Debug bool // BURROW_DEBUG - Verbose bool // BURROW_VERBOSE - - // Work directory is the root directory for burrow to act in - WorkDir string // BURROW_WORKDIR - // Data directory is defaulted to WorkDir + `/data`. - // If cli maps a data container, DataDir is intended to point - // to that mapped data directory. - DataDir string // BURROW_DATADIR - - // Capital configuration options explicitly extracted from the Viper config - ChainId string // has to be set to non-empty string, - // uniquely identifying the chain. - GenesisFile string - // ChainType string - // CSV string - // AccountTypes []string - // Zip bool - // Tarball bool - DisableRpc bool - Config *viper.Viper - // Accounts []*Account - // Result string -} - -func NewDo() *Do { - do := new(Do) - do.Debug = false - do.Verbose = false - do.WorkDir = "" - do.DataDir = "" - do.ChainId = "" - do.GenesisFile = "" - do.DisableRpc = false - do.Config = viper.New() - return do -} - -// ReadConfig uses Viper to set the configuration file name, file format -// where burrow currently only uses `toml`. -// The search directory is explicitly limited to a single location to -// minimise the chance of loading the wrong configuration file. -func (d *Do) ReadConfig(directory string, name string, configType string) error { - // name of the configuration file without extension - d.Config.SetConfigName(name) - // burrow currently only uses "toml" - d.Config.SetConfigType(configType) - // look for configuration file in the working directory - d.Config.AddConfigPath(directory) - return d.Config.ReadInConfig() -} - -// InitialiseDataDirectory will default to WorkDir/data if DataDir is empty -func (d *Do) InitialiseDataDirectory() error { - if d.DataDir == "" { - d.DataDir = path.Join(d.WorkDir, "data") - } - return util.EnsureDir(d.DataDir, os.ModePerm) -} diff --git a/definitions/pipe.go b/definitions/pipe.go deleted file mode 100644 index 286a0623a000e792e37e32c595f5296cdf02ced2..0000000000000000000000000000000000000000 --- a/definitions/pipe.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package definitions - -// TODO: [ben] This respects the old Pipe interface from burrow. -// This made sense as a wrapper around the old Tendermint, but now -// it strongly reflects the internal details of old Tendermint outwards -// and provides little value as an abstraction. -// The refactor needed here for burrow-0.12.1 is to expose a language -// of transactions, block verification and accounts, grouping -// these interfaces into an Engine, Communicator, NameReg, Permissions (suggestion) - -import ( - account "github.com/hyperledger/burrow/account" - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - consensus_types "github.com/hyperledger/burrow/consensus/types" - core_types "github.com/hyperledger/burrow/core/types" - types "github.com/hyperledger/burrow/core/types" - event "github.com/hyperledger/burrow/event" - logging_types "github.com/hyperledger/burrow/logging/types" - manager_types "github.com/hyperledger/burrow/manager/types" - "github.com/hyperledger/burrow/txs" -) - -type Pipe interface { - Accounts() Accounts - Blockchain() blockchain_types.Blockchain - Events() event.EventEmitter - NameReg() NameReg - Transactor() Transactor - // Hash of Genesis state - GenesisHash() []byte - Logger() logging_types.InfoTraceLogger - // NOTE: [ben] added to Pipe interface on 0.12 refactor - GetApplication() manager_types.Application - SetConsensusEngine(consensusEngine consensus_types.ConsensusEngine) error - GetConsensusEngine() consensus_types.ConsensusEngine - SetBlockchain(blockchain blockchain_types.Blockchain) error - GetBlockchain() blockchain_types.Blockchain - // Support for Tendermint RPC - GetTendermintPipe() (TendermintPipe, error) -} - -type Accounts interface { - GenPrivAccount() (*account.PrivAccount, error) - GenPrivAccountFromKey(privKey []byte) (*account.PrivAccount, error) - Accounts([]*event.FilterData) (*types.AccountList, error) - Account(address []byte) (*account.Account, error) - Storage(address []byte) (*types.Storage, error) - StorageAt(address, key []byte) (*types.StorageItem, error) -} - -type NameReg interface { - Entry(key string) (*core_types.NameRegEntry, error) - Entries([]*event.FilterData) (*types.ResultListNames, error) -} - -type Transactor interface { - Call(fromAddress, toAddress, data []byte) (*types.Call, error) - CallCode(fromAddress, code, data []byte) (*types.Call, error) - // Send(privKey, toAddress []byte, amount int64) (*types.Receipt, error) - // SendAndHold(privKey, toAddress []byte, amount int64) (*types.Receipt, error) - BroadcastTx(tx txs.Tx) (*txs.Receipt, error) - Transact(privKey, address, data []byte, gasLimit, - fee int64) (*txs.Receipt, error) - TransactAndHold(privKey, address, data []byte, gasLimit, - fee int64) (*txs.EventDataCall, error) - Send(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) - SendAndHold(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) - TransactNameReg(privKey []byte, name, data string, amount, - fee int64) (*txs.Receipt, error) - SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) -} diff --git a/definitions/tendermint_pipe.go b/definitions/tendermint_pipe.go deleted file mode 100644 index faf78788a7f74d1efad0ae81df90b3c2cc520617..0000000000000000000000000000000000000000 --- a/definitions/tendermint_pipe.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package definitions - -import ( - "github.com/hyperledger/burrow/account" - rpc_tm_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" -) - -// NOTE: [ben] TendermintPipe is the additional pipe to carry over -// the RPC exposed by old Tendermint on port `46657` (burrow-0.11.4 and before) -// This TendermintPipe interface should be deprecated and work towards a generic -// collection of RPC routes for burrow-1.0.0 - -type TendermintPipe interface { - Pipe - // Events - // Subscribe attempts to subscribe the listener identified by listenerId to - // the event named event. The Event result is written to rpcResponseWriter - // which must be non-blocking - Subscribe(eventId string, - rpcResponseWriter func(result rpc_tm_types.BurrowResult)) (*rpc_tm_types.ResultSubscribe, error) - Unsubscribe(subscriptionId string) (*rpc_tm_types.ResultUnsubscribe, error) - - // Net - Status() (*rpc_tm_types.ResultStatus, error) - NetInfo() (*rpc_tm_types.ResultNetInfo, error) - Genesis() (*rpc_tm_types.ResultGenesis, error) - ChainId() (*rpc_tm_types.ResultChainId, error) - - // Accounts - GetAccount(address []byte) (*rpc_tm_types.ResultGetAccount, error) - ListAccounts() (*rpc_tm_types.ResultListAccounts, error) - GetStorage(address, key []byte) (*rpc_tm_types.ResultGetStorage, error) - DumpStorage(address []byte) (*rpc_tm_types.ResultDumpStorage, error) - - // Call - Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall, error) - CallCode(fromAddress, code, data []byte) (*rpc_tm_types.ResultCall, error) - - // TODO: [ben] deprecate as we should not allow unsafe behaviour - // where a user is allowed to send a private key over the wire, - // especially unencrypted. - SignTransaction(tx txs.Tx, - privAccounts []*account.PrivAccount) (*rpc_tm_types.ResultSignTx, - error) - - // Name registry - GetName(name string) (*rpc_tm_types.ResultGetName, error) - ListNames() (*rpc_tm_types.ResultListNames, error) - - // Memory pool - BroadcastTxAsync(transaction txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) - BroadcastTxSync(transaction txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) - - // Blockchain - BlockchainInfo(minHeight, maxHeight, maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error) - ListUnconfirmedTxs(maxTxs int) (*rpc_tm_types.ResultListUnconfirmedTxs, error) - GetBlock(height int) (*rpc_tm_types.ResultGetBlock, error) - - // Consensus - ListValidators() (*rpc_tm_types.ResultListValidators, error) - DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) -} diff --git a/deployment/README.md b/deployment/README.md deleted file mode 100644 index 61e4a0cc92e46aad95f2120f9bbaebaa5acda6e8..0000000000000000000000000000000000000000 --- a/deployment/README.md +++ /dev/null @@ -1,18 +0,0 @@ -### Deployment -Included in this directory are some template files for running Burrow in a -cluster orchestration environment. [start_in_cluster](start_in_cluster) -is a general purpose script and the files in [kubernetes](kubernetes) are some -example Service and Deployment files that illustrates its possible usage. - -#### start_in_cluster -[start_in_cluster](start_in_cluster) takes its parameters as environment variables. - -You can find the variables used at the top of the file along with their defaults. - -#### Kubernetes -[all_nodes.yml](kubernetes/all_nodes.yaml) is a Kubernetes Service definition -that launches an entire network of nodes based on Deployment definitions like the -example [node000-deploy.yaml](kubernetes/node000-deploy.yaml). Each validating -node should have it's own Deployment defintion like the one found in -[node000-deploy.yaml](kubernetes/node000-deploy.yaml) - diff --git a/deployment/kubernetes/all_nodes.yaml b/deployment/kubernetes/all_nodes.yaml deleted file mode 100644 index 91a73702abf97499c8fe6c342b7e5e46b7fb13f0..0000000000000000000000000000000000000000 --- a/deployment/kubernetes/all_nodes.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# All Nodes - Load balanced API ports -kind: Service -apiVersion: v1 -metadata: - name: your-app-chain-api - labels: - app: your-app - tier: chain - chain_name: your-chain - vendor: monax -spec: - sessionAffinity: ClientIP - selector: - app: your-app - tier: chain - # node_number: "001" - ports: - - protocol: TCP - port: 1337 - targetPort: 1337 - name: your-app-chain-api - -# All Nodes - genesis.json as ConfigMap ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: your-app-ecosystem-chain-genesis - labels: - app: your-app-ecosystem - tier: chain - chain_name: your-app - vendor: monax -data: - chain-genesis: | - {"chain_id":"your-chain","accounts":[{"address":"BD1EA8ABA4B44094A406092922AF7CD92E01E5FE","amount":99999999999999,"name":"your-app_root_000","permissions":{"base":{"perms":16383,"set":16383},"roles":[]}},{"address":"E69B68990FECA85E06421859DBD4B2958C80A0D5","amount":9999999999,"name":"your-app_participant_000","permissions":{"base":{"perms":2118,"set":16383},"roles":[]}},{"address":"15416FC158C2D106B2994C82724B3DBBA47CDF79","amount":9999999999,"name":"your-app_participant_001","permissions":{"base":{"perms":2118,"set":16383},"roles":[]}},{"address":"EA6EBC0569495F98F159D533E8DD7D1D3DCFC80C","amount":9999999999,"name":"your-app_participant_002","permissions":{"base":{"perms":2118,"set":16383},"roles":[]}},{"address":"242075F27576B80F2A2805488E23203CBCBCBDFB","amount":99999999999999,"name":"your-app_validator_000","permissions":{"base":{"perms":16383,"set":16383},"roles":[]}},{"address":"8B84E223A42DEDC8C62A19F99C45C94B807BDFB6","amount":9999999999,"name":"your-app_validator_001","permissions":{"base":{"perms":32,"set":16383},"roles":[]}},{"address":"68AFC7ADB01A8CF8F9B93166D749F0067D250981","amount":9999999999,"name":"your-app_validator_002","permissions":{"base":{"perms":32,"set":16383},"roles":[]}},{"address":"443BAD24961BEE41F052C6B55AF58BDE9A4DB75F","amount":9999999999,"name":"your-app_validator_003","permissions":{"base":{"perms":32,"set":16383},"roles":[]}}],"validators":[{"pub_key":[1,"ED3EAEAAA735EC41A3625BB8AAC754A381A5726269E584B77A594E4197F2B516"],"name":"your-app_validator_000","amount":9999999998,"unbond_to":[{"address":"242075F27576B80F2A2805488E23203CBCBCBDFB","amount":9999999998}]},{"pub_key":[1,"F8F98DE0E65FBF8FBA5CE0813898F4E2FAFC34DD37FDB45B58D73B6D75DCB9AE"],"name":"your-app_validator_001","amount":9999999998,"unbond_to":[{"address":"8B84E223A42DEDC8C62A19F99C45C94B807BDFB6","amount":9999999998}]},{"pub_key":[1,"31D592F81F688AEE06B3124CBAC5AE3E04B5398A34325D4D32A25105B9588385"],"name":"your-app_validator_002","amount":9999999998,"unbond_to":[{"address":"68AFC7ADB01A8CF8F9B93166D749F0067D250981","amount":9999999998}]},{"pub_key":[1,"A1562215F9025DAA180B06C4DD9254D6B92C9F6C19219A359956941EB5924148"],"name":"your-app_validator_003","amount":9999999998,"unbond_to":[{"address":"443BAD24961BEE41F052C6B55AF58BDE9A4DB75F","amount":9999999998}]}]} - diff --git a/deployment/kubernetes/node000-deploy.yaml b/deployment/kubernetes/node000-deploy.yaml deleted file mode 100644 index b1d0dad538175382a2778589fe0237b62324346d..0000000000000000000000000000000000000000 --- a/deployment/kubernetes/node000-deploy.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# Node 000 - Deployment ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: your-app-ecosystem-chain-000 -spec: - replicas: 1 - template: - metadata: - labels: - app: your-app-ecosystem - tier: chain - node_number: "000" - chain_name: your-app - vendor: monax - spec: - containers: - - name: your-app-ecosystem-chain-000 - image: "your-app-ecosystem-chain:latest" - imagePullPolicy: IfNotPresent - env: - - name: CHAIN_NAME - value: your-chain - - name: CHAIN_NODE_NUMBER - value: "000" - - name: CHAIN_SEEDS - value: "your-app-chain-000:46656,your-app-chain-001:46656,your-app-chain-002:46656,your-app-chain-003:46656" - - name: CHAIN_API_PORT - value: "1337" - - name: CHAIN_PEER_PORT - value: "46656" - - name: CHAIN_RPC_PORT - value: "46657" - - name: CHAIN_LOG_LEVEL - value: notice - - name: CHAIN_GENESIS - valueFrom: - configMapKeyRef: - name: your-app-ecosystem-chain-genesis - key: chain-genesis - - name: KEY_ADDRESS - valueFrom: - secretKeyRef: - name: your-app-ecosystem-chain-000-keys - key: address - - name: KEY_PUBLIC - valueFrom: - secretKeyRef: - name: your-app-ecosystem-chain-000-keys - key: public-key - - name: KEY_PRIVATE - valueFrom: - secretKeyRef: - name: your-app-ecosystem-chain-000-keys - key: private-key - - name: ORGANIZATION_NAME - value: allianz - ports: - - containerPort: 46656 - - containerPort: 46657 - - containerPort: 1337 - restartPolicy: Always - diff --git a/deployment/start_in_cluster b/deployment/start_in_cluster deleted file mode 100755 index 964edd9fbb1bb5637a20c3a56bda82f341e23be4..0000000000000000000000000000000000000000 --- a/deployment/start_in_cluster +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env bash -set -e - -# This script is a wrapper for running burrow in a cluster, that is by a cluster -# orchestration environment like Kubernetes or Mesos - -# For why this is necessary see -> -# https://github.com/kubernetes/kubernetes/issues/23404 -CHAIN_NAME=$(echo $CHAIN_NAME | tr -d '\n') -CHAIN_NODE_NUMBER=$(echo $CHAIN_NODE_NUMBER | tr -d '\n') -CHAIN_SEEDS=$(echo $CHAIN_SEEDS | tr -d '\n') -MONAX_PATH=${MONAX_PATH:-$HOME/.monax} -MONAX_PATH=$(echo $MONAX_PATH | tr -d '\n') -KEY_ADDRESS=$(echo $KEY_ADDRESS | tr -d '\n') -KEY_PUBLIC=$(echo $KEY_PUBLIC | tr -d '\n') -KEY_PRIVATE=$(echo $KEY_PRIVATE | tr -d '\n') -ORG_NAME=$(echo $ORG_NAME | tr -d '\n') - -# Normal var setting -CHAIN_DIR=$MONAX_PATH/chains/$CHAIN_NAME -CHAIN_ID=${CHAIN_ID:-$CHAIN_NAME} -CHAIN_API_PORT=${CHAIN_API_PORT:-1337} -CHAIN_PEER_PORT=${CHAIN_PEER_PORT:-46656} -CHAIN_RPC_PORT=${CHAIN_RPC_PORT:-46657} -CHAIN_NODE_NUMBER=${CHAIN_NODE_NUMBER:-"000"} -CHAIN_SEEDS=${CHAIN_SEEDS:-""} -ORG_NAME=${ORG_NAME:-"myGreatOrg"} - -# All known variables should either have defaults set above, or checks here. -check() { - echo -e "\tChecking address of key." - [ -z "$KEY_ADDRESS" ] && { echo "Sad marmot face. Please set KEY_ADDRESS and re-run me."; exit 1; } - echo -e "\tChecking public key." - [ -z "$KEY_PUBLIC" ] && { echo "Sad marmot face. Please set KEY_PUBLIC and re-run me."; exit 1; } - echo -e "\tChecking private key." - [ -z "$KEY_PRIVATE" ] && { echo "Sad marmot face. Please set KEY_PRIVATE and re-run me."; exit 1; } - echo -e "\tChecking chain name." - [ -z "$CHAIN_NAME" ] && { echo "Sad marmot face. Please set CHAIN_NAME and re-run me."; exit 1; } - echo -e "\tChecking genesis." - if [ -z "$CHAIN_GENESIS" ] - then - if [ ! -e "$CHAIN_DIR"/genesis.json ] - then - echo "Sad marmot face. Please set CHAIN_GENESIS and re-run me." - exit 1 - fi - fi - echo -e "\tChecks complete." -} - -setup_dir() { - if [ ! -d "$CHAIN_DIR" ] - then - echo -e "\tChain dir does not exist. Creating." - mkdir -p $CHAIN_DIR - else - echo -e "\tChain dir exists. Not creating." - fi - cd $CHAIN_DIR - echo -e "\tChain dir setup." -} - -write_config() { - cat <<EOF > config.toml -[chain] -assert_chain_id = "$CHAIN_ID" -major_version = 0 -minor_version = 12 -genesis_file = "genesis.json" - [chain.consensus] - name = "tendermint" - major_version = 0 - minor_version = 6 - relative_root = "tendermint" - [chain.manager] - name = "burrowmint" - major_version = 0 - minor_version = 12 - relative_root = "burrowmint" -[servers] - [servers.bind] - address = "" - port = $CHAIN_API_PORT - [servers.tls] - tls = false - cert_path = "" - key_path = "" - [servers.cors] - enable = false - allow_origins = [] - allow_credentials = false - allow_methods = [] - allow_headers = [] - expose_headers = [] - max_age = 0 - [servers.http] - json_rpc_endpoint = "/rpc" - [servers.websocket] - endpoint = "/socketrpc" - max_sessions = 50 - read_buffer_size = 4096 - write_buffer_size = 4096 - [servers.tendermint] - rpc_local_address = "0.0.0.0:$CHAIN_RPC_PORT" - endpoint = "/websocket" -[tendermint] -private_validator_file = "priv_validator.json" - [tendermint.configuration] - moniker = "$CHAIN_NAME-$ORG_NAME-validator-$CHAIN_NODE_NUMBER" - seeds = "$CHAIN_SEEDS" - fast_sync = false - db_backend = "leveldb" - log_level = "$CHAIN_LOG_LEVEL" - node_laddr = "0.0.0.0:$CHAIN_PEER_PORT" - rpc_laddr = "0.0.0.0:$CHAIN_RPC_PORT" - proxy_app = "tcp://127.0.0.1:46658" - [tendermint.configuration.p2p] - dial_timeout_seconds = 3 - handshake_timeout_seconds = 20 - max_num_peers = 20 - authenticated_encryption = true - send_rate = 512000 - recv_rate = 512000 - fuzz_enable = false # use the fuzz wrapped conn - fuzz_active = false # toggle fuzzing - fuzz_mode = "drop" # eg. drop, delay - fuzz_max_delay_milliseconds = 3000 - fuzz_prob_drop_rw = 0.2 - fuzz_prob_drop_conn = 0.00 - fuzz_prob_sleep = 0.00 -[burrowmint] -db_backend = "leveldb" -tendermint_host = "0.0.0.0:$CHAIN_RPC_PORT" -EOF - echo -e "\tConfig file written." -} - -write_key_file() { - cat <<EOF > priv_validator.json -{ - "address": "$KEY_ADDRESS", - "pub_key": [ - 1, - "$KEY_PUBLIC" - ], - "priv_key": [ - 1, - "$KEY_PRIVATE" - ], - "last_height": 0, - "last_round": 0, - "last_step": 0 -} -EOF - echo -e "\tKey file written." -} - -write_genesis_file() { - [ -z "$CHAIN_GENESIS" ] && echo -e "\tUsing preloaded genesis file." && return 0 - echo -e "\tWriting genesis file from environment variables." - echo $CHAIN_GENESIS > genesis.json -} - -main() { - echo "Running pre-boot checks." - check - - echo "Setting up chain directory." - setup_dir - - echo "Writing config file." - write_config - - echo "Writing key file." - write_key_file - - echo "Writing genesis file." - write_genesis_file - - sleep 2 # hack to let the cluster provision echo "Starting burrow" - burrow serve -} - -main $@ \ No newline at end of file diff --git a/docs/specs/api.md b/docs/specs/api.md index 160da78939ea821562a99f014c407eeb40bd7971..5f94b755afe89ea07a2eab9bbcd025b5aaa87fcb 100644 --- a/docs/specs/api.md +++ b/docs/specs/api.md @@ -211,16 +211,6 @@ These are the types of transactions. Note that in DApp programming you would onl } ``` -#### DupeoutTx - -``` -{ - address: <string> - vote_a: <Vote> - vote_b: <Vote> -} -``` - These are the support types that are referenced in the transactions: #### TxInput @@ -434,18 +424,6 @@ Event object: <Tx> ``` -#### Dupeout - -This notifies you when a dupeout event happens. - -Event ID: `Dupeout` - -Event object: - -``` -<Tx> -``` - <a name="namereg"> ### Name-registry @@ -654,8 +632,6 @@ Parameter: ##### Additional info -Sequence is sometimes referred to as the "nonce". - There are two types of objects used to represent accounts, one is public accounts (like the one here), the other is private accounts, which only holds information about an accounts address, public and private key. *** diff --git a/event/cache.go b/event/cache.go new file mode 100644 index 0000000000000000000000000000000000000000..7279c097392579a4f6544501370c84e2adac8196 --- /dev/null +++ b/event/cache.go @@ -0,0 +1,52 @@ +package event + +// When exceeded we will trim the buffer's backing array capacity to avoid excessive +// allocation + +const maximumBufferCapacityToLengthRatio = 2 + +// An Cache buffers events for a Fireable +// All events are cached. Filtering happens on Flush +type Cache struct { + evsw Fireable + events []eventInfo +} + +var _ Fireable = &Cache{} + +// Create a new Cache with an EventSwitch as backend +func NewEventCache(evsw Fireable) *Cache { + return &Cache{ + evsw: evsw, + } +} + +// a cached event +type eventInfo struct { + event string + data AnyEventData +} + +// Cache an event to be fired upon finality. +func (evc *Cache) Fire(event string, eventData interface{}) { + // append to list (go will grow our backing array exponentially) + evc.events = append(evc.events, eventInfo{event: event, data: MapToAnyEventData(eventData)}) +} + +// Fire events by running evsw.Fire on all cached events. Blocks. +// Clears cached events +func (evc *Cache) Flush() { + for _, ei := range evc.events { + evc.evsw.Fire(ei.event, ei.data) + } + // Clear the buffer by re-slicing its length to zero + if cap(evc.events) > len(evc.events)*maximumBufferCapacityToLengthRatio { + // Trim the backing array capacity when it is more than double the length of the slice to avoid tying up memory + // after a spike in the number of events to buffer + evc.events = evc.events[:0:len(evc.events)] + } else { + // Re-slice the length to 0 to clear buffer but hang on to spare capacity in backing array that has been added + // in previous cache round + evc.events = evc.events[:0] + } +} diff --git a/event/cache_test.go b/event/cache_test.go new file mode 100644 index 0000000000000000000000000000000000000000..713b15d68036b0b4473876752872fdef13e32e7c --- /dev/null +++ b/event/cache_test.go @@ -0,0 +1,71 @@ +package event + +import ( + "testing" + + "github.com/hyperledger/burrow/logging/loggers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEventCache_Flush(t *testing.T) { + evts := NewEmitter(loggers.NewNoopInfoTraceLogger()) + evts.Subscribe("nothingness", "", func(data AnyEventData) { + // Check we are not initialising an empty buffer full of zeroed eventInfos in the Cache + require.FailNow(t, "We should never receive a message on this switch since none are fired") + }) + evc := NewEventCache(evts) + evc.Flush() + // Check after reset + evc.Flush() + fail := true + pass := false + evts.Subscribe("somethingness", "something", func(data AnyEventData) { + if fail { + require.FailNow(t, "Shouldn't see a message until flushed") + } + pass = true + }) + evc.Fire("something", AnyEventData{}) + evc.Fire("something", AnyEventData{}) + evc.Fire("something", AnyEventData{}) + fail = false + evc.Flush() + assert.True(t, pass) +} + +func TestEventCacheGrowth(t *testing.T) { + evc := NewEventCache(NewEmitter(loggers.NewNoopInfoTraceLogger())) + + fireNEvents(evc, 100) + c := cap(evc.events) + evc.Flush() + assert.Equal(t, c, cap(evc.events), "cache cap should remain the same after flushing events") + + fireNEvents(evc, c/maximumBufferCapacityToLengthRatio+1) + evc.Flush() + assert.Equal(t, c, cap(evc.events), "cache cap should remain the same after flushing more than half "+ + "the number of events as last time") + + fireNEvents(evc, c/maximumBufferCapacityToLengthRatio-1) + evc.Flush() + assert.True(t, c > cap(evc.events), "cache cap should drop after flushing fewer than half "+ + "the number of events as last time") + + fireNEvents(evc, c*2*maximumBufferCapacityToLengthRatio) + evc.Flush() + assert.True(t, c < cap(evc.events), "cache cap should grow after flushing more events than seen before") + + for numEvents := 100; numEvents >= 0; numEvents-- { + fireNEvents(evc, numEvents) + evc.Flush() + assert.True(t, cap(evc.events) <= maximumBufferCapacityToLengthRatio*numEvents, + "cap (%v) should be at most twice numEvents (%v)", cap(evc.events), numEvents) + } +} + +func fireNEvents(evc *Cache, n int) { + for i := 0; i < n; i++ { + evc.Fire("something", AnyEventData{}) + } +} diff --git a/event/data.go b/event/data.go new file mode 100644 index 0000000000000000000000000000000000000000..199c57439064802c5ac81b88263bb707649cf920 --- /dev/null +++ b/event/data.go @@ -0,0 +1,136 @@ +package event + +import ( + "fmt" + + exe_events "github.com/hyperledger/burrow/execution/events" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/tendermint/go-wire/data" + tm_types "github.com/tendermint/tendermint/types" +) + +// Oh for a real sum type + +// AnyEventData provides a single type for our multiplexed event categories of EVM events and Tendermint events +type AnyEventData struct { + TMEventData *tm_types.TMEventData `json:"tm_event_data,omitempty"` + BurrowEventData *EventData `json:"burrow_event_data,omitempty"` + Err *string `json:"error,omitempty"` +} + +type EventData struct { + EventDataInner `json:"unwrap"` +} + +type EventDataInner interface { +} + +func (ed EventData) Unwrap() EventDataInner { + return ed.EventDataInner +} + +func (ed EventData) MarshalJSON() ([]byte, error) { + return mapper.ToJSON(ed.EventDataInner) +} + +func (ed *EventData) UnmarshalJSON(data []byte) (err error) { + parsed, err := mapper.FromJSON(data) + if err == nil && parsed != nil { + ed.EventDataInner = parsed.(EventDataInner) + } + return err +} + +var mapper = data.NewMapper(EventData{}). + RegisterImplementation(exe_events.EventDataTx{}, "event_data_tx", biota()). + RegisterImplementation(evm_events.EventDataCall{}, "event_data_call", biota()). + RegisterImplementation(evm_events.EventDataLog{}, "event_data_log", biota()) + +// Get whichever element of the AnyEventData sum type that is not nil +func (aed AnyEventData) Get() interface{} { + if aed.TMEventData != nil { + return aed.TMEventData.Unwrap() + } + if aed.BurrowEventData != nil { + return aed.BurrowEventData.Unwrap() + } + if aed.Err != nil { + return *aed.Err + } + return nil +} + +// If this AnyEventData wraps an EventDataNewBlock then return a pointer to that value, else return nil +func (aed AnyEventData) EventDataNewBlock() *tm_types.EventDataNewBlock { + if aed.TMEventData != nil { + eventData, _ := aed.TMEventData.Unwrap().(tm_types.EventDataNewBlock) + return &eventData + } + return nil +} + +// If this AnyEventData wraps an EventDataLog then return a pointer to that value, else return nil +func (aed AnyEventData) EventDataLog() *evm_events.EventDataLog { + if aed.BurrowEventData != nil { + eventData, _ := aed.BurrowEventData.Unwrap().(evm_events.EventDataLog) + return &eventData + } + return nil +} + +// If this AnyEventData wraps an EventDataCall then return a pointer to that value, else return nil +func (aed AnyEventData) EventDataCall() *evm_events.EventDataCall { + if aed.BurrowEventData != nil { + eventData, _ := aed.BurrowEventData.Unwrap().(evm_events.EventDataCall) + return &eventData + } + return nil +} + +// If this AnyEventData wraps an EventDataTx then return a pointer to that value, else return nil +func (aed AnyEventData) EventDataTx() *exe_events.EventDataTx { + if aed.BurrowEventData != nil { + eventData, _ := aed.BurrowEventData.Unwrap().(exe_events.EventDataTx) + return &eventData + } + return nil +} + +func (aed AnyEventData) Error() string { + if aed.Err == nil { + return "" + } + return *aed.Err +} + +// Map any supported event data element to our AnyEventData sum type +func MapToAnyEventData(eventData interface{}) AnyEventData { + switch ed := eventData.(type) { + case AnyEventData: + return ed + + case tm_types.TMEventData: + return AnyEventData{TMEventData: &ed} + + case EventData: + return AnyEventData{BurrowEventData: &ed} + + case EventDataInner: + return AnyEventData{BurrowEventData: &EventData{ + EventDataInner: ed, + }} + + default: + errStr := fmt.Sprintf("could not map event data of type %T to AnyEventData", eventData) + return AnyEventData{Err: &errStr} + } +} + +// Type byte helper +var nextByte byte = 1 + +func biota() (b byte) { + b = nextByte + nextByte++ + return +} diff --git a/event/data_test.go b/event/data_test.go new file mode 100644 index 0000000000000000000000000000000000000000..26187967e9c180c4d5e06b6f13897712451fc50e --- /dev/null +++ b/event/data_test.go @@ -0,0 +1,64 @@ +package event + +import ( + "encoding/json" + "testing" + + acm "github.com/hyperledger/burrow/account" + exe_events "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/assert" + tm_types "github.com/tendermint/tendermint/types" +) + +func TestSerialiseTMEventData(t *testing.T) { + roundTripAnyEventData(t, AnyEventData{ + TMEventData: &tm_types.TMEventData{ + TMEventDataInner: tm_types.EventDataNewBlock{ + Block: &tm_types.Block{ + LastCommit: &tm_types.Commit{}, + Header: &tm_types.Header{ + ChainID: "ChainID-ChainEgo", + }, + Data: &tm_types.Data{}, + }, + }, + }, + }) + +} + +func TestSerialiseEVMEventData(t *testing.T) { + roundTripAnyEventData(t, AnyEventData{ + BurrowEventData: &EventData{ + EventDataInner: exe_events.EventDataTx{ + Tx: &txs.CallTx{ + Address: &acm.Address{1, 2, 2, 3}, + }, + Return: []byte{1, 2, 3}, + Exception: "Exception", + }, + }, + }) +} + +func TestSerialiseError(t *testing.T) { + s := "random error" + roundTripAnyEventData(t, AnyEventData{ + Err: &s, + }) +} + +func roundTripAnyEventData(t *testing.T, aed AnyEventData) { + bs, err := json.Marshal(aed) + assert.NoError(t, err) + + aedOut := new(AnyEventData) + err = json.Unmarshal(bs, aedOut) + assert.NoError(t, err) + + bsOut, err := json.Marshal(aedOut) + assert.NoError(t, err) + assert.Equal(t, string(bs), string(bsOut)) + +} diff --git a/event/emitter.go b/event/emitter.go new file mode 100644 index 0000000000000000000000000000000000000000..c4b8f296ed5c55a0562aecae5cdab381ec1ac0d5 --- /dev/null +++ b/event/emitter.go @@ -0,0 +1,219 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "strings" + + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/tendermint/tmlibs/common" + go_events "github.com/tendermint/tmlibs/events" +) + +type Subscribable interface { + Subscribe(subId, event string, callback func(AnyEventData)) error + Unsubscribe(subId string) error +} + +type Fireable interface { + Fire(event string, data interface{}) +} + +type Emitter interface { + Fireable + go_events.EventSwitch + Subscribable +} + +// The events struct has methods for working with events. +type emitter struct { + // Bah, Service infects everything + *common.BaseService + eventSwitch go_events.EventSwitch + logger logging_types.InfoTraceLogger +} + +var _ Emitter = &emitter{} + +func NewEmitter(logger logging_types.InfoTraceLogger) *emitter { + return WrapEventSwitch(go_events.NewEventSwitch(), logger) +} + +func WrapEventSwitch(eventSwitch go_events.EventSwitch, logger logging_types.InfoTraceLogger) *emitter { + eventSwitch.Start() + return &emitter{ + BaseService: common.NewBaseService(nil, "BurrowEventEmitter", eventSwitch), + eventSwitch: eventSwitch, + logger: logger.With(structure.ComponentKey, "Events"), + } +} + +// Fireable +func (evts *emitter) Fire(event string, eventData interface{}) { + evts.eventSwitch.FireEvent(event, eventData) +} + +func (evts *emitter) FireEvent(event string, data go_events.EventData) { + evts.Fire(event, data) +} + +// EventSwitch +func (evts *emitter) AddListenerForEvent(listenerID, event string, cb go_events.EventCallback) { + evts.eventSwitch.AddListenerForEvent(listenerID, event, cb) +} + +func (evts *emitter) RemoveListenerForEvent(event string, listenerID string) { + evts.eventSwitch.RemoveListenerForEvent(event, listenerID) +} + +func (evts *emitter) RemoveListener(listenerID string) { + evts.eventSwitch.RemoveListener(listenerID) +} + +// Subscribe to an event. +func (evts *emitter) Subscribe(subId, event string, callback func(AnyEventData)) error { + logging.TraceMsg(evts.logger, "Subscribing to event", + structure.ScopeKey, "events.Subscribe", "subId", subId, "event", event) + evts.eventSwitch.AddListenerForEvent(subId, event, func(eventData go_events.EventData) { + if eventData == nil { + logging.TraceMsg(evts.logger, "Sent nil go-events EventData") + return + } + callback(MapToAnyEventData(eventData)) + }) + return nil +} + +// Un-subscribe from an event. +func (evts *emitter) Unsubscribe(subId string) error { + logging.TraceMsg(evts.logger, "Unsubscribing from event", + structure.ScopeKey, "events.Unsubscribe", "subId", subId) + evts.eventSwitch.RemoveListener(subId) + return nil +} + +// Provides an Emitter that wraps many underlying EventEmitters as a +// convenience for Subscribing and Unsubscribing on multiple EventEmitters at +// once +func Multiplex(events ...Emitter) *multiplexedEvents { + return &multiplexedEvents{ + BaseService: common.NewBaseService(nil, "BurrowMultiplexedEventEmitter", nil), + eventEmitters: events, + } +} + +type multiplexedEvents struct { + *common.BaseService + eventEmitters []Emitter +} + +var _ Emitter = &multiplexedEvents{} + +// Subscribe to an event. +func (multiEvents *multiplexedEvents) Subscribe(subId, event string, cb func(AnyEventData)) error { + for _, evts := range multiEvents.eventEmitters { + err := evts.Subscribe(subId, event, cb) + if err != nil { + return err + } + } + return nil +} + +func (multiEvents *multiplexedEvents) Unsubscribe(subId string) error { + for _, evts := range multiEvents.eventEmitters { + err := evts.Unsubscribe(subId) + if err != nil { + return err + } + } + return nil +} + +func (multiEvents *multiplexedEvents) Fire(event string, eventData interface{}) { + for _, evts := range multiEvents.eventEmitters { + evts.Fire(event, eventData) + } +} + +func (multiEvents *multiplexedEvents) FireEvent(event string, eventData go_events.EventData) { + multiEvents.Fire(event, eventData) +} + +// EventSwitch +func (multiEvents *multiplexedEvents) AddListenerForEvent(listenerID, event string, cb go_events.EventCallback) { + for _, evts := range multiEvents.eventEmitters { + evts.AddListenerForEvent(listenerID, event, cb) + } +} + +func (multiEvents *multiplexedEvents) RemoveListenerForEvent(event string, listenerID string) { + for _, evts := range multiEvents.eventEmitters { + evts.RemoveListenerForEvent(event, listenerID) + } +} + +func (multiEvents *multiplexedEvents) RemoveListener(listenerID string) { + for _, evts := range multiEvents.eventEmitters { + evts.RemoveListener(listenerID) + } +} + +type noOpFireable struct { +} + +func (*noOpFireable) Fire(string, interface{}) { + +} + +func NewNoOpFireable() Fireable { + return &noOpFireable{} +} + +// *********************************** Events *********************************** + +// EventSubscribe +type EventSub struct { + SubId string `json:"sub_id"` +} + +// EventUnsubscribe +type EventUnsub struct { + Result bool `json:"result"` +} + +// EventPoll +type PollResponse struct { + Events []interface{} `json:"events"` +} + +// ************************************************************************************** +// Helper function + +func GenerateSubId() (string, error) { + b := make([]byte, 32) + _, err := rand.Read(b) + if err != nil { + return "", fmt.Errorf("could not generate random bytes for a subscription"+ + " id: %v", err) + } + rStr := hex.EncodeToString(b) + return strings.ToUpper(rStr), nil +} diff --git a/event/events.go b/event/events.go deleted file mode 100644 index f93b1b643eb56d75af899995f5b732fa964532d1..0000000000000000000000000000000000000000 --- a/event/events.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package event - -import ( - "crypto/rand" - "encoding/hex" - "strings" - - "fmt" - - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" - "github.com/hyperledger/burrow/txs" - go_events "github.com/tendermint/go-events" - tm_types "github.com/tendermint/tendermint/types" -) - -// TODO: [Silas] this is a compatibility layer between our event types and -// TODO: go-events. Our ultimate plan is to replace go-events with our own pub-sub -// TODO: code that will better allow us to manage and multiplex events from different -// TODO: subsystems - -// Oh for a sum type -// We are using this as a marker interface for the -type anyEventData interface{} - -type EventEmitter interface { - Subscribe(subId, event string, callback func(txs.EventData)) error - Unsubscribe(subId string) error -} - -func NewEvents(eventSwitch go_events.EventSwitch, logger logging_types.InfoTraceLogger) *events { - return &events{eventSwitch: eventSwitch, logger: logging.WithScope(logger, "Events")} -} - -// Provides an EventEmitter that wraps many underlying EventEmitters as a -// convenience for Subscribing and Unsubscribing on multiple EventEmitters at -// once -func Multiplex(events ...EventEmitter) *multiplexedEvents { - return &multiplexedEvents{events} -} - -// The events struct has methods for working with events. -type events struct { - eventSwitch go_events.EventSwitch - logger logging_types.InfoTraceLogger -} - -// Subscribe to an event. -func (evts *events) Subscribe(subId, event string, - callback func(txs.EventData)) error { - cb := func(evt go_events.EventData) { - eventData, err := mapToOurEventData(evt) - if err != nil { - logging.InfoMsg(evts.logger, "Failed to map go-events EventData to our EventData", - "error", err, - "event", event) - } - callback(eventData) - } - evts.eventSwitch.AddListenerForEvent(subId, event, cb) - return nil -} - -// Un-subscribe from an event. -func (evts *events) Unsubscribe(subId string) error { - evts.eventSwitch.RemoveListener(subId) - return nil -} - -type multiplexedEvents struct { - eventEmitters []EventEmitter -} - -// Subscribe to an event. -func (multiEvents *multiplexedEvents) Subscribe(subId, event string, - callback func(txs.EventData)) error { - for _, eventEmitter := range multiEvents.eventEmitters { - err := eventEmitter.Subscribe(subId, event, callback) - if err != nil { - return err - } - } - - return nil -} - -// Un-subscribe from an event. -func (multiEvents *multiplexedEvents) Unsubscribe(subId string) error { - for _, eventEmitter := range multiEvents.eventEmitters { - err := eventEmitter.Unsubscribe(subId) - if err != nil { - return err - } - } - - return nil -} - -// *********************************** Events *********************************** - -// EventSubscribe -type EventSub struct { - SubId string `json:"sub_id"` -} - -// EventUnsubscribe -type EventUnsub struct { - Result bool `json:"result"` -} - -// EventPoll -type PollResponse struct { - Events []interface{} `json:"events"` -} - -// ************************************************************************************** -// Helper function - -func GenerateSubId() (string, error) { - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - return "", fmt.Errorf("Could not generate random bytes for a subscription"+ - " id: %v", err) - } - rStr := hex.EncodeToString(b) - return strings.ToUpper(rStr), nil -} - -func mapToOurEventData(eventData anyEventData) (txs.EventData, error) { - // TODO: [Silas] avoid this with a better event pub-sub system of our own - // TODO: that maybe involves a registry of events - switch eventData := eventData.(type) { - case txs.EventData: - return eventData, nil - case tm_types.EventDataNewBlock: - return txs.EventDataNewBlock{ - Block: eventData.Block, - }, nil - case tm_types.EventDataNewBlockHeader: - return txs.EventDataNewBlockHeader{ - Header: eventData.Header, - }, nil - default: - return nil, fmt.Errorf("EventData not recognised as known EventData: %v", - eventData) - } -} diff --git a/event/events_test.go b/event/events_test.go deleted file mode 100644 index b9305d79e6de5d72ff02f459fcdd88502eda9dec..0000000000000000000000000000000000000000 --- a/event/events_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package event - -import ( - "testing" - - "sync" - "time" - - "github.com/hyperledger/burrow/txs" - "github.com/stretchr/testify/assert" -) - -func TestMultiplexedEvents(t *testing.T) { - emitter1 := newMockEventEmitter() - emitter2 := newMockEventEmitter() - emitter12 := Multiplex(emitter1, emitter2) - - eventData1 := make(map[txs.EventData]int) - eventData2 := make(map[txs.EventData]int) - eventData12 := make(map[txs.EventData]int) - - mutex1 := &sync.Mutex{} - mutex2 := &sync.Mutex{} - mutex12 := &sync.Mutex{} - - emitter12.Subscribe("Sub12", "Event12", func(eventData txs.EventData) { - mutex12.Lock() - eventData12[eventData] = 1 - mutex12.Unlock() - }) - emitter1.Subscribe("Sub1", "Event1", func(eventData txs.EventData) { - mutex1.Lock() - eventData1[eventData] = 1 - mutex1.Unlock() - }) - emitter2.Subscribe("Sub2", "Event2", func(eventData txs.EventData) { - mutex2.Lock() - eventData2[eventData] = 1 - mutex2.Unlock() - }) - - time.Sleep(4 * mockInterval) - - err := emitter12.Unsubscribe("Sub12") - assert.NoError(t, err) - err = emitter1.Unsubscribe("Sub2") - assert.NoError(t, err) - err = emitter2.Unsubscribe("Sub2") - assert.NoError(t, err) - - mutex1.Lock() - defer mutex1.Unlock() - mutex2.Lock() - defer mutex2.Unlock() - mutex12.Lock() - defer mutex12.Unlock() - assert.Equal(t, map[txs.EventData]int{mockEventData{"Sub1", "Event1"}: 1}, - eventData1) - assert.Equal(t, map[txs.EventData]int{mockEventData{"Sub2", "Event2"}: 1}, - eventData2) - assert.Equal(t, map[txs.EventData]int{mockEventData{"Sub12", "Event12"}: 1}, - eventData12) - -} diff --git a/event/filters.go b/event/filters.go index 0a1c732437835d76bf056bd362a213c930a435a7..1cdd153303d8b6f5a428b824ce532b489a4f086f 100644 --- a/event/filters.go +++ b/event/filters.go @@ -141,15 +141,15 @@ func (this *FilterFactory) newSingleFilter(fd *FilterData) (ConfigurableFilter, // Some standard value parsing functions. -func ParseNumberValue(value string) (int64, error) { - var val int64 +func ParseNumberValue(value string) (uint64, error) { + var val uint64 // Check for wildcards. if value == "min" { - val = math.MinInt64 + val = 0 } else if value == "max" { - val = math.MaxInt64 + val = math.MaxUint64 } else { - tv, err := strconv.ParseInt(value, 10, 64) + tv, err := strconv.ParseUint(value, 10, 64) if err != nil { return 0, fmt.Errorf("Wrong value type.") @@ -161,29 +161,29 @@ func ParseNumberValue(value string) (int64, error) { // Some standard filtering functions. -func GetRangeFilter(op, fName string) (func(a, b int64) bool, error) { +func GetRangeFilter(op, fName string) (func(a, b uint64) bool, error) { if op == "==" { - return func(a, b int64) bool { + return func(a, b uint64) bool { return a == b }, nil } else if op == "!=" { - return func(a, b int64) bool { + return func(a, b uint64) bool { return a != b }, nil } else if op == "<=" { - return func(a, b int64) bool { + return func(a, b uint64) bool { return a <= b }, nil } else if op == ">=" { - return func(a, b int64) bool { + return func(a, b uint64) bool { return a >= b }, nil } else if op == "<" { - return func(a, b int64) bool { + return func(a, b uint64) bool { return a < b }, nil } else if op == ">" { - return func(a, b int64) bool { + return func(a, b uint64) bool { return a > b }, nil } else { diff --git a/test/filters/filter_test.go b/event/filters_test.go similarity index 51% rename from test/filters/filter_test.go rename to event/filters_test.go index 35601f4696a5f5df43b50376c49491699c415176..959f4c47f1df7627eb9dc0c4630d75d801ac9fc9 100644 --- a/test/filters/filter_test.go +++ b/event/filters_test.go @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package filters +package event import ( "fmt" "sync" "testing" - event "github.com/hyperledger/burrow/event" "github.com/stretchr/testify/suite" ) @@ -34,16 +33,16 @@ type FilterableObject struct { // Ops: All type IntegerFilter struct { op string - value int64 - match func(int64, int64) bool + value uint64 + match func(uint64, uint64) bool } -func (this *IntegerFilter) Configure(fd *event.FilterData) error { - val, err := event.ParseNumberValue(fd.Value) +func (this *IntegerFilter) Configure(fd *FilterData) error { + val, err := ParseNumberValue(fd.Value) if err != nil { return err } - match, err2 := event.GetRangeFilter(fd.Op, "integer") + match, err2 := GetRangeFilter(fd.Op, "integer") if err2 != nil { return err2 } @@ -58,7 +57,7 @@ func (this *IntegerFilter) Match(v interface{}) bool { if !ok { return false } - return this.match(int64(fo.Integer), this.value) + return this.match(uint64(fo.Integer), this.value) } // Filter for integer value. @@ -69,8 +68,8 @@ type StringFilter struct { match func(string, string) bool } -func (this *StringFilter) Configure(fd *event.FilterData) error { - match, err := event.GetStringFilter(fd.Op, "string") +func (this *StringFilter) Configure(fd *FilterData) error { + match, err := GetStringFilter(fd.Op, "string") if err != nil { return err } @@ -92,17 +91,17 @@ func (this *StringFilter) Match(v interface{}) bool { type FilterSuite struct { suite.Suite objects []FilterableObject - filterFactory *event.FilterFactory + filterFactory *FilterFactory } -func (this *FilterSuite) SetupSuite() { +func (fs *FilterSuite) SetupSuite() { objects := make([]FilterableObject, OBJECTS) for i := 0; i < 100; i++ { objects[i] = FilterableObject{i, fmt.Sprintf("string%d", i)} } - ff := event.NewFilterFactory() + ff := NewFilterFactory() ff.RegisterFilterPool("integer", &sync.Pool{ New: func() interface{} { @@ -116,140 +115,140 @@ func (this *FilterSuite) SetupSuite() { }, }) - this.objects = objects - this.filterFactory = ff + fs.objects = objects + fs.filterFactory = ff } -func (this *FilterSuite) TearDownSuite() { +func (fs *FilterSuite) TearDownSuite() { } // ********************************************* Tests ********************************************* -func (this *FilterSuite) Test_FilterIntegersEquals() { - fd := &event.FilterData{"integer", "==", "5"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersEquals() { + fd := &FilterData{"integer", "==", "5"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) break } } - this.Equal(arr, this.objects[5:6]) + fs.Equal(arr, fs.objects[5:6]) } -func (this *FilterSuite) Test_FilterIntegersLT() { - fd := &event.FilterData{"integer", "<", "5"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersLT() { + fd := &FilterData{"integer", "<", "5"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } - this.Equal(arr, this.objects[:5]) + fs.Equal(arr, fs.objects[:5]) } -func (this *FilterSuite) Test_FilterIntegersLTEQ() { - fd := &event.FilterData{"integer", "<=", "10"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersLTEQ() { + fd := &FilterData{"integer", "<=", "10"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } - this.Equal(arr, this.objects[:11]) + fs.Equal(arr, fs.objects[:11]) } -func (this *FilterSuite) Test_FilterIntegersGT() { - fd := &event.FilterData{"integer", ">", "50"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersGT() { + fd := &FilterData{"integer", ">", "50"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } - this.Equal(arr, this.objects[51:]) + fs.Equal(arr, fs.objects[51:]) } -func (this *FilterSuite) Test_FilterIntegersRange() { - fd0 := &event.FilterData{"integer", ">", "5"} - fd1 := &event.FilterData{"integer", "<", "38"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd0, fd1}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersRange() { + fd0 := &FilterData{"integer", ">", "5"} + fd1 := &FilterData{"integer", "<", "38"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd0, fd1}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } - this.Equal(arr, this.objects[6:38]) + fs.Equal(arr, fs.objects[6:38]) } -func (this *FilterSuite) Test_FilterIntegersGTEQ() { - fd := &event.FilterData{"integer", ">=", "77"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersGTEQ() { + fd := &FilterData{"integer", ">=", "77"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } - this.Equal(arr, this.objects[77:]) + fs.Equal(arr, fs.objects[77:]) } -func (this *FilterSuite) Test_FilterIntegersNEQ() { - fd := &event.FilterData{"integer", "!=", "50"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterIntegersNEQ() { + fd := &FilterData{"integer", "!=", "50"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } res := make([]FilterableObject, OBJECTS) - copy(res, this.objects) + copy(res, fs.objects) res = append(res[:50], res[51:]...) - this.Equal(arr, res) + fs.Equal(arr, res) } -func (this *FilterSuite) Test_FilterStringEquals() { - fd := &event.FilterData{"string", "==", "string7"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterStringEquals() { + fd := &FilterData{"string", "==", "string7"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } - this.Equal(arr, this.objects[7:8]) + fs.Equal(arr, fs.objects[7:8]) } -func (this *FilterSuite) Test_FilterStringNEQ() { - fd := &event.FilterData{"string", "!=", "string50"} - filter, err := this.filterFactory.NewFilter([]*event.FilterData{fd}) - this.NoError(err) +func (fs *FilterSuite) Test_FilterStringNEQ() { + fd := &FilterData{"string", "!=", "string50"} + filter, err := fs.filterFactory.NewFilter([]*FilterData{fd}) + fs.NoError(err) arr := []FilterableObject{} - for _, o := range this.objects { + for _, o := range fs.objects { if filter.Match(o) { arr = append(arr, o) } } res := make([]FilterableObject, OBJECTS) - copy(res, this.objects) + copy(res, fs.objects) res = append(res[:50], res[51:]...) - this.Equal(arr, res) + fs.Equal(arr, res) } // ********************************************* Entrypoint ********************************************* diff --git a/event/event_cache.go b/event/subscriptions.go similarity index 63% rename from event/event_cache.go rename to event/subscriptions.go index 9342564bc51d26c80a10dbf2fa099b81e5eb167c..8c1baecfae89b777db2f2adab2dace2a9f0c9c63 100644 --- a/event/event_cache.go +++ b/event/subscriptions.go @@ -18,8 +18,6 @@ import ( "fmt" "sync" "time" - - "github.com/hyperledger/burrow/txs" ) var ( @@ -27,15 +25,15 @@ var ( reaperThreshold = 10 * time.Second ) -type EventCache struct { +type SubscriptionsCache struct { mtx *sync.Mutex events []interface{} ts time.Time subId string } -func newEventCache() *EventCache { - return &EventCache{ +func newSubscriptionsCache() *SubscriptionsCache { + return &SubscriptionsCache{ &sync.Mutex{}, make([]interface{}, 0), time.Now(), @@ -43,40 +41,40 @@ func newEventCache() *EventCache { } } -func (this *EventCache) poll() []interface{} { - this.mtx.Lock() - defer this.mtx.Unlock() +func (subsCache *SubscriptionsCache) poll() []interface{} { + subsCache.mtx.Lock() + defer subsCache.mtx.Unlock() var evts []interface{} - if len(this.events) > 0 { - evts = this.events - this.events = []interface{}{} + if len(subsCache.events) > 0 { + evts = subsCache.events + subsCache.events = []interface{}{} } else { evts = []interface{}{} } - this.ts = time.Now() + subsCache.ts = time.Now() return evts } // Catches events that callers subscribe to and adds them to an array ready to be polled. -type EventSubscriptions struct { +type Subscriptions struct { mtx *sync.RWMutex - eventEmitter EventEmitter - subs map[string]*EventCache + subscribable Subscribable + subs map[string]*SubscriptionsCache reap bool } -func NewEventSubscriptions(eventEmitter EventEmitter) *EventSubscriptions { - es := &EventSubscriptions{ +func NewSubscriptions(subscribable Subscribable) *Subscriptions { + es := &Subscriptions{ mtx: &sync.RWMutex{}, - eventEmitter: eventEmitter, - subs: make(map[string]*EventCache), + subscribable: subscribable, + subs: make(map[string]*SubscriptionsCache), reap: true, } go reap(es) return es } -func reap(es *EventSubscriptions) { +func reap(es *Subscriptions) { if !es.reap { return } @@ -87,7 +85,7 @@ func reap(es *EventSubscriptions) { if time.Since(sub.ts) > reaperThreshold { // Seems like Go is ok with this.. delete(es.subs, id) - es.eventEmitter.Unsubscribe(id) + es.subscribable.Unsubscribe(id) } } go reap(es) @@ -97,47 +95,47 @@ func reap(es *EventSubscriptions) { // has to call func which involves acquiring a mutex lock, so might be // a delay - though a conflict is practically impossible, and if it does // happen it's for an insignificant amount of time (the time it takes to -// carry out EventCache.poll() ). -func (this *EventSubscriptions) Add(eventId string) (string, error) { +// carry out SubscriptionsCache.poll() ). +func (subs *Subscriptions) Add(eventId string) (string, error) { subId, errSID := GenerateSubId() if errSID != nil { return "", errSID } - cache := newEventCache() - errC := this.eventEmitter.Subscribe(subId, eventId, - func(evt txs.EventData) { + cache := newSubscriptionsCache() + errC := subs.subscribable.Subscribe(subId, eventId, + func(evt AnyEventData) { cache.mtx.Lock() defer cache.mtx.Unlock() cache.events = append(cache.events, evt) }) cache.subId = subId - this.mtx.Lock() - defer this.mtx.Unlock() - this.subs[subId] = cache + subs.mtx.Lock() + defer subs.mtx.Unlock() + subs.subs[subId] = cache if errC != nil { return "", errC } return subId, nil } -func (this *EventSubscriptions) Poll(subId string) ([]interface{}, error) { - this.mtx.RLock() - defer this.mtx.RUnlock() - sub, ok := this.subs[subId] +func (subs *Subscriptions) Poll(subId string) ([]interface{}, error) { + subs.mtx.RLock() + defer subs.mtx.RUnlock() + sub, ok := subs.subs[subId] if !ok { return nil, fmt.Errorf("Subscription not active. ID: " + subId) } return sub.poll(), nil } -func (this *EventSubscriptions) Remove(subId string) error { - this.mtx.Lock() - defer this.mtx.Unlock() +func (subs *Subscriptions) Remove(subId string) error { + subs.mtx.Lock() + defer subs.mtx.Unlock() // TODO Check this. - _, ok := this.subs[subId] + _, ok := subs.subs[subId] if !ok { return fmt.Errorf("Subscription not active. ID: " + subId) } - delete(this.subs, subId) + delete(subs.subs, subId) return nil } diff --git a/event/event_cache_test.go b/event/subscriptions_test.go similarity index 85% rename from event/event_cache_test.go rename to event/subscriptions_test.go index 1d271b9cc6499b11edd45ac9f619486cbc3b8a35..c02bbb709c39af02d0c916db77d2fb1136fa6139 100644 --- a/event/event_cache_test.go +++ b/event/subscriptions_test.go @@ -18,12 +18,10 @@ import ( "encoding/hex" "fmt" "runtime" + "sync" "testing" "time" - "sync" - - "github.com/hyperledger/burrow/txs" "github.com/stretchr/testify/assert" ) @@ -32,7 +30,7 @@ var mockInterval = 20 * time.Millisecond type mockSub struct { subId string eventId string - f func(txs.EventData) + f func(AnyEventData) sdChan chan struct{} } @@ -41,10 +39,10 @@ type mockEventData struct { eventId string } -func (eventData mockEventData) AssertIsEventData() {} +func (eventData mockEventData) AssertIsEVMEventData() {} // A mock event -func newMockSub(subId, eventId string, f func(txs.EventData)) mockSub { +func newMockSub(subId, eventId string, f func(AnyEventData)) mockSub { return mockSub{subId, eventId, f, make(chan struct{})} } @@ -57,35 +55,37 @@ func newMockEventEmitter() *mockEventEmitter { return &mockEventEmitter{make(map[string]mockSub), &sync.Mutex{}} } -func (this *mockEventEmitter) Subscribe(subId, eventId string, callback func(txs.EventData)) error { - if _, ok := this.subs[subId]; ok { +func (mee *mockEventEmitter) Subscribe(subId, eventId string, callback func(AnyEventData)) error { + if _, ok := mee.subs[subId]; ok { return nil } me := newMockSub(subId, eventId, callback) - this.mutex.Lock() - this.subs[subId] = me - this.mutex.Unlock() + mee.mutex.Lock() + mee.subs[subId] = me + mee.mutex.Unlock() go func() { for { select { case <-me.sdChan: - this.mutex.Lock() - delete(this.subs, subId) - this.mutex.Unlock() + mee.mutex.Lock() + delete(mee.subs, subId) + mee.mutex.Unlock() return case <-time.After(mockInterval): - me.f(mockEventData{subId, eventId}) + me.f(AnyEventData{BurrowEventData: &EventData{ + EventDataInner: mockEventData{subId: subId, eventId: eventId}, + }}) } } }() return nil } -func (this *mockEventEmitter) Unsubscribe(subId string) error { - this.mutex.Lock() - sub, ok := this.subs[subId] - this.mutex.Unlock() +func (mee *mockEventEmitter) Unsubscribe(subId string) error { + mee.mutex.Lock() + sub, ok := mee.subs[subId] + mee.mutex.Unlock() if !ok { return nil } @@ -101,7 +101,7 @@ func TestSubReaping(t *testing.T) { reaperTimeout = 100 * time.Millisecond mee := newMockEventEmitter() - eSubs := NewEventSubscriptions(mee) + eSubs := NewSubscriptions(mee) doneChan := make(chan error) go func() { for i := 0; i < NUM_SUBS; i++ { @@ -146,7 +146,7 @@ func TestSubManualClose(t *testing.T) { reaperTimeout = 10000 * time.Millisecond mee := newMockEventEmitter() - eSubs := NewEventSubscriptions(mee) + eSubs := NewSubscriptions(mee) doneChan := make(chan error) go func() { for i := 0; i < NUM_SUBS; i++ { @@ -194,7 +194,7 @@ func TestSubFlooding(t *testing.T) { // Crank it up. Now pressure is 10 times higher on each sub. mockInterval = 1 * time.Millisecond mee := newMockEventEmitter() - eSubs := NewEventSubscriptions(mee) + eSubs := NewSubscriptions(mee) doneChan := make(chan error) go func() { for i := 0; i < NUM_SUBS; i++ { diff --git a/execution/accounts.go b/execution/accounts.go new file mode 100644 index 0000000000000000000000000000000000000000..8a54e2f5a368542211afd38d5f4eeede165b8c59 --- /dev/null +++ b/execution/accounts.go @@ -0,0 +1,252 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package execution + +// Accounts is part of the pipe for BurrowMint and provides the implementation +// for the pipe to call into the BurrowMint application + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" +) + +// The accounts struct has methods for working with accounts. +type accounts struct { + state acm.StateIterable + filterFactory *event.FilterFactory +} + +// Accounts +type AccountList struct { + Accounts []acm.Account `json:"accounts"` +} + +// A contract account storage item. +type StorageItem struct { + Key []byte `json:"key"` + Value []byte `json:"value"` +} + +// Account storage +type Storage struct { + StorageRoot []byte `json:"storage_root"` + StorageItems []StorageItem `json:"storage_items"` +} + +// TODO: [Silas] there are various notes about using mempool (which I guess translates to CheckTx cache). We need +// to understand if this is the right thing to do, since we cannot guarantee stability of the check cache it doesn't +// seem like the right thing to do.... +func newAccounts(state acm.StateIterable) *accounts { + filterFactory := event.NewFilterFactory() + + filterFactory.RegisterFilterPool("code", &sync.Pool{ + New: func() interface{} { + return &AccountCodeFilter{} + }, + }) + + filterFactory.RegisterFilterPool("balance", &sync.Pool{ + New: func() interface{} { + return &AccountBalanceFilter{} + }, + }) + + return &accounts{ + state: state, + filterFactory: filterFactory, + } +} + +// Generate a new Private Key Account. +func (accs *accounts) GenPrivAccount() (*acm.ConcretePrivateAccount, error) { + pa, err := acm.GeneratePrivateAccount() + if err != nil { + return nil, err + } + return acm.AsConcretePrivateAccount(pa), nil +} + +// Generate a new Private Key Account. +func (accs *accounts) GenPrivAccountFromKey(privKey []byte) ( + *acm.ConcretePrivateAccount, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not 64 bytes long.") + } + fmt.Printf("PK BYTES FROM ACCOUNTS: %x\n", privKey) + pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + if err != nil { + return nil, err + } + return acm.AsConcretePrivateAccount(pa), nil +} + +// Get all accounts. +func (accs *accounts) Accounts(fda []*event.FilterData) ( + *AccountList, error) { + accounts := make([]acm.Account, 0) + filter, err := accs.filterFactory.NewFilter(fda) + if err != nil { + return nil, fmt.Errorf("Error in query: " + err.Error()) + } + accs.state.IterateAccounts(func(account acm.Account) bool { + if filter.Match(account) { + accounts = append(accounts, account) + } + return false + }) + return &AccountList{accounts}, nil +} + +// Get an account. +func (accs *accounts) Account(address acm.Address) (acm.Account, error) { + acc, err := accs.state.GetAccount(address) // NOTE: we want to read from mempool! + if err != nil { + return nil, err + } + if acc == nil { + acc = accs.newAcc(address) + } + return acc, nil +} + +// Get the value stored at 'key' in the account with address 'address' +// Both the key and value is returned. +func (accs *accounts) StorageAt(address acm.Address, key []byte) (*StorageItem, + error) { + acc, err := accs.state.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return &StorageItem{key, []byte{}}, nil + } + value, err := accs.state.GetStorage(address, binary.LeftPadWord256(key)) + if err != nil { + return nil, err + } + if value == binary.Zero256 { + return &StorageItem{key, []byte{}}, nil + } + return &StorageItem{key, value.UnpadLeft()}, nil +} + +// Get the storage of the account with address 'address'. +func (accs *accounts) Storage(address acm.Address) (*Storage, error) { + state := accs.state + acc, err := state.GetAccount(address) + if err != nil { + return nil, err + } + storageItems := make([]StorageItem, 0) + if acc == nil { + return &Storage{nil, storageItems}, nil + } + accs.state.IterateStorage(address, func(key, value binary.Word256) bool { + storageItems = append(storageItems, StorageItem{ + Key: key.UnpadLeft(), + Value: value.UnpadLeft(), + }) + return false + }) + return &Storage{acc.StorageRoot(), storageItems}, nil +} + +// Create a new account. +func (accs *accounts) newAcc(address acm.Address) acm.Account { + return (&acm.ConcreteAccount{ + Address: address, + Sequence: 0, + Balance: 0, + Code: nil, + StorageRoot: nil, + }).Account() +} + +// Filter for account code. +// Ops: == or != +// Could be used to match against nil, to see if an account is a contract account. +type AccountCodeFilter struct { + op string + value []byte + match func([]byte, []byte) bool +} + +func (this *AccountCodeFilter) Configure(fd *event.FilterData) error { + op := fd.Op + val, err := hex.DecodeString(fd.Value) + + if err != nil { + return fmt.Errorf("Wrong value type.") + } + if op == "==" { + this.match = func(a, b []byte) bool { + return bytes.Equal(a, b) + } + } else if op == "!=" { + this.match = func(a, b []byte) bool { + return !bytes.Equal(a, b) + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'code' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *AccountCodeFilter) Match(v interface{}) bool { + acc, ok := v.(acm.Account) + if !ok { + return false + } + return this.match(acc.Code(), this.value) +} + +// Filter for account balance. +// Ops: All +type AccountBalanceFilter struct { + op string + value uint64 + match func(uint64, uint64) bool +} + +func (this *AccountBalanceFilter) Configure(fd *event.FilterData) error { + val, err := event.ParseNumberValue(fd.Value) + if err != nil { + return err + } + match, err2 := event.GetRangeFilter(fd.Op, "balance") + if err2 != nil { + return err2 + } + this.match = match + this.op = fd.Op + this.value = uint64(val) + return nil +} + +func (this *AccountBalanceFilter) Match(v interface{}) bool { + acc, ok := v.(acm.Account) + if !ok { + return false + } + return this.match(acc.Balance(), this.value) +} diff --git a/execution/block_cache.go b/execution/block_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..3c83e29ecc30e9e7a862a94e8a0fd6329e6143e6 --- /dev/null +++ b/execution/block_cache.go @@ -0,0 +1,386 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package execution + +import ( + "bytes" + "fmt" + "sort" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + + "sync" + + "github.com/tendermint/merkleeyes/iavl" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/merkle" +) + +func makeStorage(db dbm.DB, root []byte) merkle.Tree { + storage := iavl.NewIAVLTree(1024, db) + storage.Load(root) + return storage +} + +var _ acm.StateWriter = &BlockCache{} + +var _ acm.StateIterable = &BlockCache{} + +// TODO: BlockCache badly needs a rewrite to remove database sharing with State and make it communicate using the +// Account interfaces like a proper person. As well as other oddities of decoupled storage and account state + +// The blockcache helps prevent unnecessary IAVLTree updates and garbage generation. +type BlockCache struct { + // We currently provide the RPC layer with access to read-only access to BlockCache via the StateIterable interface + // on BatchExecutor. However since read-only operations generate writes to the BlockCache in the current design + // we need a mutex here. Otherwise BlockCache ought to be used within a component that is responsible for serialising + // the operations on the BlockCache. + sync.RWMutex + db dbm.DB + backend *State + accounts map[acm.Address]accountInfo + storages map[acm.Address]map[Word256]storageInfo + names map[string]nameInfo +} + +func NewBlockCache(backend *State) *BlockCache { + return &BlockCache{ + // TODO: This is bad and probably the cause of various panics. Accounts themselves are written + // to the State 'backend' but updates to storage just skip that and write directly to the database + db: backend.db, + backend: backend, + accounts: make(map[acm.Address]accountInfo), + storages: make(map[acm.Address]map[Word256]storageInfo), + names: make(map[string]nameInfo), + } +} + +func (cache *BlockCache) State() *State { + return cache.backend +} + +//------------------------------------- +// BlockCache.account + +func (cache *BlockCache) GetAccount(addr acm.Address) (acm.Account, error) { + acc, _, removed, _ := cache.accounts[addr].unpack() + if removed { + return nil, nil + } else if acc != nil { + return acc, nil + } else { + acc, err := cache.backend.GetAccount(addr) + if err != nil { + return nil, err + } + cache.Lock() + defer cache.Unlock() + cache.accounts[addr] = accountInfo{acc, nil, false, false} + return acc, nil + } +} + +func (cache *BlockCache) UpdateAccount(acc acm.Account) error { + cache.Lock() + defer cache.Unlock() + addr := acc.Address() + _, storage, removed, _ := cache.accounts[addr].unpack() + if removed { + return fmt.Errorf("UpdateAccount on a removed account %s", addr) + } + cache.accounts[addr] = accountInfo{acc, storage, false, true} + return nil +} + +func (cache *BlockCache) RemoveAccount(addr acm.Address) error { + cache.Lock() + defer cache.Unlock() + _, _, removed, _ := cache.accounts[addr].unpack() + if removed { + return fmt.Errorf("RemoveAccount on a removed account %s", addr) + } + cache.accounts[addr] = accountInfo{nil, nil, true, false} + return nil +} + +func (cache *BlockCache) IterateAccounts(consumer func(acm.Account) (stop bool)) (bool, error) { + cache.RLock() + defer cache.RUnlock() + for _, info := range cache.accounts { + if consumer(info.account) { + return true, nil + } + } + return cache.backend.IterateAccounts(consumer) +} + +// BlockCache.account +//------------------------------------- +// BlockCache.storage + +func (cache *BlockCache) GetStorage(addr acm.Address, key Word256) (Word256, error) { + // Check cache + cache.RLock() + info, ok := cache.lookupStorage(addr, key) + cache.RUnlock() + if ok { + return info.value, nil + } + // Get or load storage + cache.RLock() + acc, storage, removed, dirty := cache.accounts[addr].unpack() + cache.RUnlock() + if removed { + return Zero256, fmt.Errorf("GetStorage on a removed account %s", addr) + } + cache.Lock() + defer cache.Unlock() + + if acc != nil && storage == nil { + storage = makeStorage(cache.db, acc.StorageRoot()) + cache.accounts[addr] = accountInfo{acc, storage, false, dirty} + } else if acc == nil { + return Zero256, nil + } + // Load and set cache + _, val, _ := storage.Get(key.Bytes()) + value := LeftPadWord256(val) + cache.setStorage(addr, key, storageInfo{value, false}) + return value, nil +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *BlockCache) SetStorage(addr acm.Address, key Word256, value Word256) error { + cache.Lock() + defer cache.Unlock() + _, _, removed, _ := cache.accounts[addr].unpack() + if removed { + return fmt.Errorf("SetStorage on a removed account %s", addr) + } + cache.setStorage(addr, key, storageInfo{value, true}) + return nil +} + +func (cache *BlockCache) IterateStorage(address acm.Address, consumer func(key, value Word256) (stop bool)) (bool, error) { + cache.RLock() + defer cache.RUnlock() + // Try cache first for early exit + for key, info := range cache.storages[address] { + if consumer(key, info.value) { + return true, nil + } + } + + return cache.backend.IterateStorage(address, consumer) +} + +// BlockCache.storage +//------------------------------------- +// BlockCache.names + +func (cache *BlockCache) GetNameRegEntry(name string) *NameRegEntry { + cache.RLock() + entry, removed, _ := cache.names[name].unpack() + cache.RUnlock() + if removed { + return nil + } else if entry != nil { + return entry + } else { + entry = cache.backend.GetNameRegEntry(name) + cache.Lock() + cache.names[name] = nameInfo{entry, false, false} + cache.Unlock() + return entry + } +} + +func (cache *BlockCache) UpdateNameRegEntry(entry *NameRegEntry) { + cache.Lock() + defer cache.Unlock() + cache.names[entry.Name] = nameInfo{entry, false, true} +} + +func (cache *BlockCache) RemoveNameRegEntry(name string) { + cache.Lock() + defer cache.Unlock() + _, removed, _ := cache.names[name].unpack() + if removed { + panic("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() { + cache.Lock() + defer cache.Unlock() + // Determine order for storage updates + // The address comes first so it'll be grouped. + storageKeys := make([]Tuple256, 0, len(cache.storages)) + for address, keyInfoMap := range cache.storages { + for key, _ := range keyInfoMap { + storageKeys = append(storageKeys, Tuple256{First: address.Word256(), Second: key}) + } + } + 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 acm.Address + curAcc acm.Account + curAccRemoved bool + curStorage merkle.Tree + ) + for _, storageKey := range storageKeys { + addrWord256, key := Tuple256Split(storageKey) + addr := acm.AddressFromWord256(addrWord256) + if addr != curAddr || curAcc == nil { + acc, storage, removed, _ := cache.accounts[addr].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[acm.AddressFromWord256(storageKey.First)][storageKey.Second].unpack() + if !dirty { + continue + } + if value.IsZero() { + curStorage.Remove(key.Bytes()) + } else { + curStorage.Set(key.Bytes(), value.Bytes()) + cache.accounts[addr] = accountInfo{curAcc, curStorage, false, true} + } + } + + // Determine order for accounts + addrs := []acm.Address{} + for addr := range cache.accounts { + addrs = append(addrs, addr) + } + sort.Slice(addrs, func(i, j int) bool { + return addrs[i].String() < addrs[j].String() + }) + + // Update or delete accounts. + for _, addr := range addrs { + acc, storage, removed, dirty := cache.accounts[addr].unpack() + if removed { + cache.backend.RemoveAccount(addr) + } else { + if acc == nil { + continue + } + if storage != nil { + newStorageRoot := storage.Save() + if !bytes.Equal(newStorageRoot, acc.StorageRoot()) { + acc = acm.AsMutableAccount(acc).SetStorageRoot(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 { + panic(fmt.Sprintf("Could not remove namereg entry to be removed: %s", nameStr)) + } + } else { + if entry == nil { + continue + } + if dirty { + cache.backend.UpdateNameRegEntry(entry) + } + } + } +} + +func (cache *BlockCache) lookupStorage(address acm.Address, key Word256) (storageInfo, bool) { + keyInfoMap, ok := cache.storages[address] + if !ok { + return storageInfo{}, false + } + info, ok := keyInfoMap[key] + return info, ok +} + +func (cache *BlockCache) setStorage(address acm.Address, key Word256, info storageInfo) { + keyInfoMap, ok := cache.storages[address] + if !ok { + keyInfoMap = make(map[Word256]storageInfo) + cache.storages[address] = keyInfoMap + } + keyInfoMap[key] = info +} + +//----------------------------------------------------------------------------- + +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 *NameRegEntry + removed bool + dirty bool +} + +func (nInfo nameInfo) unpack() (*NameRegEntry, bool, bool) { + return nInfo.name, nInfo.removed, nInfo.dirty +} diff --git a/execution/events/events.go b/execution/events/events.go new file mode 100644 index 0000000000000000000000000000000000000000..266a1860f9570907df72b89f22ba7a0208d269f6 --- /dev/null +++ b/execution/events/events.go @@ -0,0 +1,51 @@ +package events + +import ( + "encoding/json" + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/txs" +) + +func EventStringAccInput(addr acm.Address) string { return fmt.Sprintf("Acc/%s/Input", addr) } +func EventStringAccOutput(addr acm.Address) string { return fmt.Sprintf("Acc/%s/Output", addr) } +func EventStringNameReg(name string) string { return fmt.Sprintf("NameReg/%s", name) } +func EventStringPermissions(name string) string { return fmt.Sprintf("Permissions/%s", name) } +func EventStringBond() string { return "Bond" } +func EventStringUnbond() string { return "Unbond" } +func EventStringRebond() string { return "Rebond" } + +// All txs fire EventDataTx, but only CallTx might have Return or Exception +type EventDataTx struct { + Tx txs.Tx `json:"tx"` + Return []byte `json:"return"` + Exception string `json:"exception"` +} + +type eventDataTx struct { + Tx txs.Wrapper `json:"tx"` + Return []byte `json:"return"` + Exception string `json:"exception"` +} + +func (edTx EventDataTx) MarshalJSON() ([]byte, error) { + model := eventDataTx{ + Tx: txs.Wrap(edTx.Tx), + Exception: edTx.Exception, + Return: edTx.Return, + } + return json.Marshal(model) +} + +func (edTx *EventDataTx) UnmarshalJSON(data []byte) error { + model := new(eventDataTx) + err := json.Unmarshal(data, model) + if err != nil { + return err + } + edTx.Tx = model.Tx.Unwrap() + edTx.Return = model.Return + edTx.Exception = model.Exception + return nil +} diff --git a/manager/burrow-mint/evm/abi/types.go b/execution/evm/abi/types.go similarity index 100% rename from manager/burrow-mint/evm/abi/types.go rename to execution/evm/abi/types.go diff --git a/execution/evm/accounts.go b/execution/evm/accounts.go new file mode 100644 index 0000000000000000000000000000000000000000..19735838ae9c771a58bc4c4ff6d1d2cfe2d5808a --- /dev/null +++ b/execution/evm/accounts.go @@ -0,0 +1,25 @@ +package evm + +import ( + acm "github.com/hyperledger/burrow/account" + ptypes "github.com/hyperledger/burrow/permission/types" +) + +// Create a new account from a parent 'creator' account. The creator account will have its +// sequence number incremented +func DeriveNewAccount(creator acm.MutableAccount, permissions ptypes.AccountPermissions) acm.MutableAccount { + // Generate an address + sequence := creator.Sequence() + creator.IncSequence() + + addr := acm.NewContractAddress(creator.Address(), sequence) + + // Create account from address. + return (&acm.ConcreteAccount{ + Address: addr, + Balance: 0, + Code: nil, + Sequence: 0, + Permissions: permissions, + }).MutableAccount() +} diff --git a/execution/evm/asm/bc/helpers.go b/execution/evm/asm/bc/helpers.go new file mode 100644 index 0000000000000000000000000000000000000000..4d8058d16d517ae845399fe22c6000609279e7b9 --- /dev/null +++ b/execution/evm/asm/bc/helpers.go @@ -0,0 +1,68 @@ +package bc + +import ( + "fmt" + + "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/execution/evm/asm" +) + +// Convenience function to allow us to mix bytes, ints, and OpCodes that +// represent bytes in an EVM assembly code to make assembly more readable. +// Also allows us to splice together assembly +// fragments because any []byte arguments are flattened in the result. +func Splice(bytelikes ...interface{}) []byte { + bytes := make([]byte, len(bytelikes)) + for i, bytelike := range bytelikes { + switch b := bytelike.(type) { + case byte: + bytes[i] = b + case asm.OpCode: + bytes[i] = byte(b) + case int: + bytes[i] = byte(b) + if int(bytes[i]) != b { + panic(fmt.Sprintf("The int %v does not fit inside a byte", b)) + } + case int64: + bytes[i] = byte(b) + if int64(bytes[i]) != b { + panic(fmt.Sprintf("The int64 %v does not fit inside a byte", b)) + } + case uint64: + bytes[i] = byte(b) + if uint64(bytes[i]) != b { + panic(fmt.Sprintf("The uint64 %v does not fit inside a byte", b)) + } + case binary.Word256: + return Concat(bytes[:i], b[:], Splice(bytelikes[i+1:]...)) + case binary.Word160: + return Concat(bytes[:i], b[:], Splice(bytelikes[i+1:]...)) + case account.Address: + return Concat(bytes[:i], b[:], Splice(bytelikes[i+1:]...)) + case []byte: + // splice + return Concat(bytes[:i], b, Splice(bytelikes[i+1:]...)) + default: + panic(fmt.Errorf("could not convert %s to a byte or sequence of bytes", bytelike)) + } + } + return bytes +} + +func Concat(bss ...[]byte) []byte { + offset := 0 + for _, bs := range bss { + offset += len(bs) + } + bytes := make([]byte, offset) + offset = 0 + for _, bs := range bss { + for i, b := range bs { + bytes[offset+i] = b + } + offset += len(bs) + } + return bytes +} diff --git a/manager/burrow-mint/evm/opcodes/opcodes.go b/execution/evm/asm/opcodes.go similarity index 81% rename from manager/burrow-mint/evm/opcodes/opcodes.go rename to execution/evm/asm/opcodes.go index a8139d82f25bde769328ea169b176facd913a75c..bbe5dec981b2da5dc9cc6a03f57afcf7986be925 100644 --- a/manager/burrow-mint/evm/opcodes/opcodes.go +++ b/execution/evm/asm/opcodes.go @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package opcodes +package asm import ( "fmt" - "github.com/hyperledger/burrow/word256" "gopkg.in/fatih/set.v0" ) @@ -341,6 +340,11 @@ var opCodeToString = map[OpCode]string{ SELFDESTRUCT: "SELFDESTRUCT", } +func OpCodeName(op OpCode) (name string, isOpcode bool) { + name, isOpcode = opCodeToString[op] + return name, isOpcode +} + func (o OpCode) String() string { str := opCodeToString[o] if len(str) == 0 { @@ -368,53 +372,3 @@ func AnalyzeJumpDests(code []byte) (dests *set.Set) { } return } - -// Convenience function to allow us to mix bytes, ints, and OpCodes that -// represent bytes in an EVM assembly code to make assembly more readable. -// Also allows us to splice together assembly -// fragments because any []byte arguments are flattened in the result. -func Bytecode(bytelikes ...interface{}) []byte { - bytes := make([]byte, len(bytelikes)) - for i, bytelike := range bytelikes { - switch b := bytelike.(type) { - case byte: - bytes[i] = b - case OpCode: - bytes[i] = byte(b) - case int: - bytes[i] = byte(b) - if int(bytes[i]) != b { - panic(fmt.Sprintf("The int %v does not fit inside a byte", b)) - } - case int64: - bytes[i] = byte(b) - if int64(bytes[i]) != b { - panic(fmt.Sprintf("The int64 %v does not fit inside a byte", b)) - } - case word256.Word256: - return Concat(bytes[:i], b[:], Bytecode(bytelikes[i+1:]...)) - case []byte: - // splice - return Concat(bytes[:i], b, Bytecode(bytelikes[i+1:]...)) - default: - panic("Only byte-like codes (and []byte sequences) can be used to form bytecode") - } - } - return bytes -} - -func Concat(bss ...[]byte) []byte { - offset := 0 - for _, bs := range bss { - offset += len(bs) - } - bytes := make([]byte, offset) - offset = 0 - for _, bs := range bss { - for i, b := range bs { - bytes[offset+i] = b - } - offset += len(bs) - } - return bytes -} diff --git a/manager/burrow-mint/evm/common.go b/execution/evm/common.go similarity index 98% rename from manager/burrow-mint/evm/common.go rename to execution/evm/common.go index fbbd4c51a33263669eaf4bc699ddc730a2ce84ba..e8fb7444c8afcfa69e387323a654c4c2bb5f90ae 100644 --- a/manager/burrow-mint/evm/common.go +++ b/execution/evm/common.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "math/big" diff --git a/execution/evm/events/events.go b/execution/evm/events/events.go new file mode 100644 index 0000000000000000000000000000000000000000..4bc7a32398a976218a557aafbd6514302e875484 --- /dev/null +++ b/execution/evm/events/events.go @@ -0,0 +1,54 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" +) + +// Functions to generate eventId strings + +func EventStringAccCall(addr acm.Address) string { return fmt.Sprintf("Acc/%s/Call", addr) } +func EventStringLogEvent(addr acm.Address) string { return fmt.Sprintf("Log/%s", addr) } + +//---------------------------------------- + +// EventDataCall fires when we call a contract, and when a contract calls another contract +type EventDataCall struct { + CallData *CallData `json:"call_data"` + Origin acm.Address `json:"origin"` + TxID []byte `json:"tx_id"` + Return []byte `json:"return"` + Exception string `json:"exception"` +} + +type CallData struct { + Caller acm.Address `json:"caller"` + Callee acm.Address `json:"callee"` + Data []byte `json:"data"` + Value uint64 `json:"value"` + Gas uint64 `json:"gas"` +} + +// EventDataLog fires when a contract executes the LOG opcode +type EventDataLog struct { + Address acm.Address `json:"address"` + Topics []Word256 `json:"topics"` + Data []byte `json:"data"` + Height uint64 `json:"height"` +} diff --git a/execution/evm/fake_app_state.go b/execution/evm/fake_app_state.go new file mode 100644 index 0000000000000000000000000000000000000000..09a985ca4efedc76265e3b829deb061441586b90 --- /dev/null +++ b/execution/evm/fake_app_state.go @@ -0,0 +1,87 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package evm + +import ( + "fmt" + + "bytes" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" +) + +type FakeAppState struct { + accounts map[acm.Address]acm.Account + storage map[string]Word256 +} + +var _ acm.StateWriter = &FakeAppState{} + +func (fas *FakeAppState) GetAccount(addr acm.Address) (acm.Account, error) { + account := fas.accounts[addr] + return account, nil +} + +func (fas *FakeAppState) UpdateAccount(account acm.Account) error { + fas.accounts[account.Address()] = account + return nil +} + +func (fas *FakeAppState) RemoveAccount(address acm.Address) error { + _, ok := fas.accounts[address] + if !ok { + panic(fmt.Sprintf("Invalid account addr: %s", address)) + } else { + // Remove account + delete(fas.accounts, address) + } + return nil +} + +func (fas *FakeAppState) GetStorage(addr acm.Address, key Word256) (Word256, error) { + _, ok := fas.accounts[addr] + if !ok { + panic(fmt.Sprintf("Invalid account addr: %s", addr)) + } + + value, ok := fas.storage[addr.String()+key.String()] + if ok { + return value, nil + } else { + return Zero256, nil + } +} + +func (fas *FakeAppState) SetStorage(addr acm.Address, key Word256, value Word256) error { + _, ok := fas.accounts[addr] + if !ok { + + fmt.Println("\n\n", fas.accountsDump()) + panic(fmt.Sprintf("Invalid account addr: %s", addr)) + } + + fas.storage[addr.String()+key.String()] = value + return nil +} + +func (fas *FakeAppState) accountsDump() string { + buf := new(bytes.Buffer) + fmt.Fprint(buf, "Dumping accounts...", "\n") + for _, acc := range fas.accounts { + fmt.Fprint(buf, acc.Address().String(), "\n") + } + return buf.String() +} diff --git a/manager/burrow-mint/evm/gas.go b/execution/evm/gas.go similarity index 62% rename from manager/burrow-mint/evm/gas.go rename to execution/evm/gas.go index 9ffc5e56d6d9363aaeabb815d24fdc9dd1b3f602..a764a5a57f6985f20cda9dfc172c83ec27662afd 100644 --- a/manager/burrow-mint/evm/gas.go +++ b/execution/evm/gas.go @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm const ( - GasSha3 int64 = 1 - GasGetAccount int64 = 1 - GasStorageUpdate int64 = 1 + GasSha3 uint64 = 1 + GasGetAccount uint64 = 1 + GasStorageUpdate uint64 = 1 - GasBaseOp int64 = 0 // TODO: make this 1 - GasStackOp int64 = 1 + GasBaseOp uint64 = 0 // TODO: make this 1 + GasStackOp uint64 = 1 - GasEcRecover int64 = 1 - GasSha256Word int64 = 1 - GasSha256Base int64 = 1 - GasRipemd160Word int64 = 1 - GasRipemd160Base int64 = 1 - GasIdentityWord int64 = 1 - GasIdentityBase int64 = 1 + GasEcRecover uint64 = 1 + GasSha256Word uint64 = 1 + GasSha256Base uint64 = 1 + GasRipemd160Word uint64 = 1 + GasRipemd160Base uint64 = 1 + GasIdentityWord uint64 = 1 + GasIdentityBase uint64 = 1 ) diff --git a/manager/burrow-mint/evm/log_event_test.go b/execution/evm/log_event_test.go similarity index 65% rename from manager/burrow-mint/evm/log_event_test.go rename to execution/evm/log_event_test.go index 7dd569a63008a5cf0bbf43a0ef31f7f94e3cd451..76b7dc75607914cac18a4d8a7503dad7498d7341 100644 --- a/manager/burrow-mint/evm/log_event_test.go +++ b/execution/evm/log_event_test.go @@ -12,21 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "bytes" "reflect" "testing" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - "github.com/tendermint/go-events" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + . "github.com/hyperledger/burrow/execution/evm/asm" + "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/logging/loggers" ) var expectedData = []byte{0x10} -var expectedHeight int64 = 0 +var expectedHeight uint64 = 0 var expectedTopics = []Word256{ Int64ToWord256(1), Int64ToWord256(2), @@ -38,28 +40,24 @@ 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 + account1 := acm.ConcreteAccount{ + Address: acm.Address{1, 3, 5, 7, 9}, + }.MutableAccount() + account2 := acm.ConcreteAccount{ + Address: acm.Address{2, 4, 6, 8, 10}, + }.MutableAccount() + st.accounts[account1.Address()] = account1 + st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) - eventSwitch := events.NewEventSwitch() - _, err := eventSwitch.Start() - if err != nil { - t.Errorf("Failed to start eventSwitch: %v", err) - } - eventID := txs.EventStringLogEvent(account2.Address.Postfix(20)) + eventSwitch := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) + eventID := events.EventStringLogEvent(account2.Address()) doneChan := make(chan struct{}, 1) - eventSwitch.AddListenerForEvent("test", eventID, func(event events.EventData) { - logEvent := event.(txs.EventDataLog) + eventSwitch.Subscribe("test", eventID, func(eventData event.AnyEventData) { + logEvent := eventData.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) @@ -75,7 +73,7 @@ func TestLog4(t *testing.T) { ourVm.SetFireable(eventSwitch) - var gas int64 = 100000 + var gas uint64 = 100000 mstore8 := byte(MSTORE8) push1 := byte(PUSH1) @@ -96,7 +94,7 @@ func TestLog4(t *testing.T) { stop, } - _, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + _, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas) <-doneChan if err != nil { t.Fatal(err) diff --git a/manager/burrow-mint/evm/memory.go b/execution/evm/memory.go similarity index 91% rename from manager/burrow-mint/evm/memory.go rename to execution/evm/memory.go index fc24dbd7d85adcfbbb1ca26f0508675b1047e5fb..6eb8456cbbd6e861ba56993a8819622e178bd3da 100644 --- a/manager/burrow-mint/evm/memory.go +++ b/execution/evm/memory.go @@ -1,4 +1,4 @@ -package vm +package evm import ( "fmt" @@ -12,7 +12,7 @@ const ( // Change the length of this zero array to tweak the size of the block of zeros // written to the backing slice at a time when it is grown. A larger number may -// lead to less calls to append to achieve the desired capacity although it is +// lead to fewer calls to append to achieve the desired capacity although it is // unlikely to make a lot of difference. var zeroBlock []byte = make([]byte, 32) @@ -86,9 +86,9 @@ func (mem *dynamicMemory) Capacity() int64 { // memory (will not shrink). func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error { if newCapacity > math.MaxInt32 { - // If we ever did want to then we would need to maintain multiple pages - // of memory - return fmt.Errorf("Cannot address memory beyond a maximum index "+ + // If we ever did want more than an int32 of space then we would need to + // maintain multiple pages of memory + return fmt.Errorf("cannot address memory beyond a maximum index "+ "of Int32 type (%v bytes)", math.MaxInt32) } newCapacityInt := int(newCapacity) @@ -97,7 +97,7 @@ func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error { return nil } if newCapacity > mem.maximumCapacity { - return fmt.Errorf("Cannot grow memory because it would exceed the "+ + return fmt.Errorf("cannot grow memory because it would exceed the "+ "current maximum limit of %v bytes", mem.maximumCapacity) } // Ensure the backing array of slice is big enough diff --git a/manager/burrow-mint/evm/memory_test.go b/execution/evm/memory_test.go similarity index 99% rename from manager/burrow-mint/evm/memory_test.go rename to execution/evm/memory_test.go index 2477b32d8292aa2c0aab36d4af575458322fb26b..923cdda45d6a83c5532f523d3c360e655457ab55 100644 --- a/manager/burrow-mint/evm/memory_test.go +++ b/execution/evm/memory_test.go @@ -1,4 +1,4 @@ -package vm +package evm import ( "testing" diff --git a/manager/burrow-mint/evm/native.go b/execution/evm/native.go similarity index 69% rename from manager/burrow-mint/evm/native.go rename to execution/evm/native.go index 17478055c1ad61d55ec150cbf1f8028c900370f3..198977c4ccd3208c5522bd2d9aeaa8143665b0a0 100644 --- a/manager/burrow-mint/evm/native.go +++ b/execution/evm/native.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "crypto/sha256" - . "github.com/hyperledger/burrow/word256" - + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + logging_types "github.com/hyperledger/burrow/logging/types" "golang.org/x/crypto/ripemd160" ) @@ -52,10 +53,11 @@ func registerNativeContracts() { //----------------------------------------------------------------------------- -type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) +type NativeContract func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) /* Removed due to C dependency -func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func ecrecoverFunc(state State, caller *acm.Account, input []byte, gas *int64) (output []byte, err error) { // Deduct gas gasRequired := GasEcRecover if *gas < gasRequired { @@ -77,9 +79,10 @@ func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) } */ -func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func sha256Func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { // Deduct gas - gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base + gasRequired := uint64((len(input)+31)/32)*GasSha256Word + GasSha256Base if *gas < gasRequired { return nil, ErrInsufficientGas } else { @@ -92,9 +95,10 @@ func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (o return hasher.Sum(nil), nil } -func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func ripemd160Func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { // Deduct gas - gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base + gasRequired := uint64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base if *gas < gasRequired { return nil, ErrInsufficientGas } else { @@ -107,9 +111,10 @@ func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) return LeftPadBytes(hasher.Sum(nil), 32), nil } -func identityFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func identityFunc(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { // Deduct gas - gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase + gasRequired := uint64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase if *gas < gasRequired { return nil, ErrInsufficientGas } else { diff --git a/execution/evm/sha3/LICENSE b/execution/evm/sha3/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6a66aea5eafe0ca6a688840c47219556c552488e --- /dev/null +++ b/execution/evm/sha3/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/execution/evm/sha3/PATENTS b/execution/evm/sha3/PATENTS new file mode 100644 index 0000000000000000000000000000000000000000..733099041f84fa1e58611ab2e11af51c1f26d1d2 --- /dev/null +++ b/execution/evm/sha3/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/manager/burrow-mint/evm/sha3/keccakf.go b/execution/evm/sha3/keccakf.go similarity index 100% rename from manager/burrow-mint/evm/sha3/keccakf.go rename to execution/evm/sha3/keccakf.go diff --git a/manager/burrow-mint/evm/sha3/sha3.go b/execution/evm/sha3/sha3.go similarity index 100% rename from manager/burrow-mint/evm/sha3/sha3.go rename to execution/evm/sha3/sha3.go diff --git a/manager/burrow-mint/evm/snative.go b/execution/evm/snative.go similarity index 62% rename from manager/burrow-mint/evm/snative.go rename to execution/evm/snative.go index 3f628e99d328e1f44ec04c0ad9ff4b19b8f5adc4..e98937ad64dce1b5fdb6fcca02e8116f0257d447 100644 --- a/manager/burrow-mint/evm/snative.go +++ b/execution/evm/snative.go @@ -12,19 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "fmt" - "github.com/hyperledger/burrow/common/sanity" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" - ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/hyperledger/burrow/word256" - "strings" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/abi" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/execution/evm/abi" + "github.com/hyperledger/burrow/execution/evm/sha3" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" ) // @@ -65,7 +68,7 @@ type SNativeFunctionDescription struct { func registerSNativeContracts() { for _, contract := range SNativeContracts() { - registeredNativeContracts[contract.AddressWord256()] = contract.Dispatch + registeredNativeContracts[contract.Address().Word256()] = contract.Dispatch } } @@ -91,7 +94,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - ptypes.AddRole, + permission.AddRole, addRole}, &SNativeFunctionDescription{` @@ -106,7 +109,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - ptypes.RmRole, + permission.RemoveRole, removeRole}, &SNativeFunctionDescription{` @@ -121,7 +124,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - ptypes.HasRole, + permission.HasRole, hasRole}, &SNativeFunctionDescription{` @@ -138,13 +141,13 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_set", abi.BoolTypeName), }, abiReturn("result", permFlagTypeName), - ptypes.SetBase, + permission.SetBase, setBase}, &SNativeFunctionDescription{` * @notice Unsets the permissions flags for an account. Causes permissions being unset to fall through to global permissions. - * @param _account account address - * @param _permission the permissions flags to unset for the account + * @param _account account address + * @param _permission the permissions flags to unset for the account * @return result the effective permissions flags on the account after the call `, "unsetBase", @@ -152,7 +155,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_account", abi.AddressTypeName), abiArg("_permission", permFlagTypeName)}, abiReturn("result", permFlagTypeName), - ptypes.UnsetBase, + permission.UnsetBase, unsetBase}, &SNativeFunctionDescription{` @@ -166,7 +169,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_account", abi.AddressTypeName), abiArg("_permission", permFlagTypeName)}, abiReturn("result", abi.BoolTypeName), - ptypes.HasBase, + permission.HasBase, hasBase}, &SNativeFunctionDescription{` @@ -180,7 +183,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_permission", permFlagTypeName), abiArg("_set", abi.BoolTypeName)}, abiReturn("result", permFlagTypeName), - ptypes.SetGlobal, + permission.SetGlobal, setGlobal}, ), } @@ -190,8 +193,8 @@ func SNativeContracts() map[string]*SNativeContractDescription { if _, ok := contractMap[contract.Name]; ok { // If this happens we have a pseudo compile time error that will be caught // on native.go init() - panic(fmt.Errorf("Duplicate contract with name %s defined. "+ - "Contract names must be unique.", contract.Name)) + panic(fmt.Errorf("duplicate contract with name %s defined. "+ + "Contract names must be unique", contract.Name)) } contractMap[contract.Name] = contract } @@ -208,7 +211,7 @@ func NewSNativeContract(comment, name string, fid := f.ID() otherF, ok := functionsByID[fid] if ok { - panic(fmt.Errorf("Function with ID %x already defined: %s", fid, + panic(fmt.Errorf("function with ID %x already defined: %s", fid, otherF)) } functionsByID[fid] = f @@ -224,8 +227,12 @@ func NewSNativeContract(comment, name string, // This function is designed to be called from the EVM once a SNative contract // has been selected. It is also placed in a registry by registerSNativeContracts // So it can be looked up by SNative address -func (contract *SNativeContractDescription) Dispatch(appState AppState, - caller *Account, args []byte, gas *int64) (output []byte, err error) { +func (contract *SNativeContractDescription) Dispatch(state acm.StateWriter, caller acm.Account, + args []byte, gas *uint64, logger logging_types.InfoTraceLogger) (output []byte, err error) { + + logger = logger.WithPrefix(structure.ComponentKey, "SNatives"). + With(structure.ScopeKey, "Dispatch", "contract_name", contract.Name) + if len(args) < abi.FunctionSelectorLength { return nil, fmt.Errorf("SNatives dispatch requires a 4-byte function "+ "identifier but arguments are only %s bytes long", len(args)) @@ -236,41 +243,31 @@ func (contract *SNativeContractDescription) Dispatch(appState AppState, return nil, err } + logging.TraceMsg(logger, "Dispatching to function", "function_name", function.Name) + remainingArgs := args[abi.FunctionSelectorLength:] // check if we have permission to call this function - if !HasPermission(appState, caller, function.PermFlag) { - return nil, ErrInvalidPermission{caller.Address, function.Name} + if !HasPermission(state, caller, function.PermFlag) { + return nil, ErrLacksSNativePermission{caller.Address(), function.Name} } // ensure there are enough arguments if len(remainingArgs) != function.NArgs()*Word256Length { - return nil, fmt.Errorf("%s() takes %d arguments", function.Name, - function.NArgs()) + return nil, fmt.Errorf("%s() takes %d arguments but got %d (with %d bytes unconsumed - should be 0)", + function.Name, function.NArgs(), len(remainingArgs)/Word256Length, len(remainingArgs)%Word256Length) } // call the function - return function.F(appState, caller, remainingArgs, gas) + return function.F(state, caller, remainingArgs, gas, logger) } // We define the address of an SNative contact as the last 20 bytes of the sha3 // hash of its name -func (contract *SNativeContractDescription) Address() abi.Address { - var address abi.Address +func (contract *SNativeContractDescription) Address() (address acm.Address) { hash := sha3.Sha3([]byte(contract.Name)) copy(address[:], hash[len(hash)-abi.AddressLength:]) - return address -} - -// Get address as a byte slice -func (contract *SNativeContractDescription) AddressBytes() []byte { - address := contract.Address() - return address[:] -} - -// Get address as a left-padded Word256 -func (contract *SNativeContractDescription) AddressWord256() Word256 { - return LeftPadWord256(contract.AddressBytes()) + return } // Get function by calling identifier FunctionSelector @@ -278,7 +275,7 @@ func (contract *SNativeContractDescription) FunctionByID(id abi.FunctionSelector f, ok := contract.functionsByID[id] if !ok { return nil, - fmt.Errorf("Unknown SNative function with ID %x", id) + fmt.Errorf("unknown SNative function with ID %x", id) } return f, nil } @@ -290,7 +287,7 @@ func (contract *SNativeContractDescription) FunctionByName(name string) (*SNativ return f, nil } } - return nil, fmt.Errorf("Unknown SNative function with name %s", name) + return nil, fmt.Errorf("unknown SNative function with name %s", name) } // Get functions in order of declaration @@ -341,142 +338,197 @@ func abiReturn(name string, abiTypeName abi.TypeName) abi.Return { // Permission function defintions // TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?) -func hasBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func hasBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, permNum := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := state.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) // already shifted if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } - permInt := byteFromBool(HasPermission(appState, vmAcc, permN)) - dbg.Printf("snative.hasBasePerm(0x%X, %b) = %v\n", addr.Postfix(20), permN, permInt) + hasPermission := HasPermission(state, acc, permN) + permInt := byteFromBool(hasPermission) + logger.Trace("function", "hasBase", "address", address.String(), + "perm_flag", fmt.Sprintf("%b", permN), "has_permission", hasPermission) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func setBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum, permVal := returnThreeArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func setBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, permNum, permVal := returnThreeArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } permV := !permVal.IsZero() - if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { + if err = acc.MutablePermissions().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 effectivePermBytes(vmAcc.Permissions.Base, globalPerms(appState)), nil + state.UpdateAccount(acc) + logger.Trace("function", "setBase", "address", address.String(), + "permission_flag", fmt.Sprintf("%b", permN), + "permission_value", permV) + return effectivePermBytes(acc.Permissions().Base, globalPerms(state)), nil } -func unsetBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func unsetBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, permNum := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } - if err = vmAcc.Permissions.Base.Unset(permN); err != nil { + if err = acc.MutablePermissions().Base.Unset(permN); err != nil { return nil, err } - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.unsetBasePerm(0x%X, %b)\n", addr.Postfix(20), permN) - return effectivePermBytes(vmAcc.Permissions.Base, globalPerms(appState)), nil + state.UpdateAccount(acc) + logger.Trace("function", "unsetBase", "address", address.String(), + "perm_flag", fmt.Sprintf("%b", permN), + "permission_flag", fmt.Sprintf("%b", permN)) + + return effectivePermBytes(acc.Permissions().Base, globalPerms(state)), nil } -func setGlobal(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +func setGlobal(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + permNum, permVal := returnTwoArgs(args) - vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) - if vmAcc == nil { - sanity.PanicSanity("cant find the global permissions account") + acc, err := acm.GetMutableAccount(state, permission.GlobalPermissionsAddress) + if err != nil { + return nil, err + } + if acc == nil { + panic("cant find the global permissions account") } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } permV := !permVal.IsZero() - if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { + if err = acc.MutablePermissions().Base.Set(permN, permV); err != nil { return nil, err } - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.setGlobalPerm(%b, %v)\n", permN, permV) - return permBytes(vmAcc.Permissions.Base.ResultantPerms()), nil + state.UpdateAccount(acc) + logger.Trace("function", "setGlobal", + "permission_flag", fmt.Sprintf("%b", permN), + "permission_value", permV) + return permBytes(acc.Permissions().Base.ResultantPerms()), nil } -func hasRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, role := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func hasRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, role := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := state.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } roleS := string(role.Bytes()) - permInt := byteFromBool(vmAcc.Permissions.HasRole(roleS)) - dbg.Printf("snative.hasRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + hasRole := acc.Permissions().HasRole(roleS) + permInt := byteFromBool(hasRole) + logger.Trace("function", "hasRole", "address", address.String(), + "role", roleS, + "has_role", hasRole) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func addRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, role := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func addRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, role := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } roleS := string(role.Bytes()) - permInt := byteFromBool(vmAcc.Permissions.AddRole(roleS)) - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.addRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + roleAdded := acc.MutablePermissions().AddRole(roleS) + permInt := byteFromBool(roleAdded) + state.UpdateAccount(acc) + logger.Trace("function", "addRole", "address", address.String(), + "role", roleS, + "role_added", roleAdded) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func removeRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, role := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func removeRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, role := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } roleS := string(role.Bytes()) - permInt := byteFromBool(vmAcc.Permissions.RmRole(roleS)) - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.rmRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + roleRemoved := acc.MutablePermissions().RmRole(roleS) + permInt := byteFromBool(roleRemoved) + state.UpdateAccount(acc) + logger.Trace("function", "removeRole", "address", address.String(), + "role", roleS, + "role_removed", roleRemoved) return LeftPadWord256([]byte{permInt}).Bytes(), nil } //------------------------------------------------------------------------------------------------ // Errors and utility funcs -type ErrInvalidPermission struct { - Address Word256 +type ErrLacksSNativePermission struct { + Address acm.Address SNative string } -func (e ErrInvalidPermission) Error() string { - return fmt.Sprintf("Account %X does not have permission snative.%s", e.Address.Postfix(20), e.SNative) +func (e ErrLacksSNativePermission) Error() string { + return fmt.Sprintf("account %s does not have SNative function call permission: %s", e.Address, e.SNative) } // Checks if a permission flag is valid (a known base chain or snative permission) func ValidPermN(n ptypes.PermFlag) bool { - return n <= ptypes.TopPermFlag + return n <= permission.TopPermFlag } // Get the global BasePermissions -func globalPerms(appState AppState) ptypes.BasePermissions { - vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) - if vmAcc == nil { - sanity.PanicSanity("cant find the global permissions account") - } - return vmAcc.Permissions.Base +func globalPerms(state acm.StateWriter) ptypes.BasePermissions { + return permission.GlobalAccountPermissions(state).Base } -// Compute the effective permissions from an Account's BasePermissions by +// Compute the effective permissions from an acm.Account's BasePermissions by // taking the bitwise or with the global BasePermissions resultant permissions func effectivePermBytes(basePerms ptypes.BasePermissions, globalPerms ptypes.BasePermissions) []byte { diff --git a/manager/burrow-mint/evm/snative_test.go b/execution/evm/snative_test.go similarity index 76% rename from manager/burrow-mint/evm/snative_test.go rename to execution/evm/snative_test.go index 18006ad65cfa2fb0f51acc601291b4743d47d2f4..ab173b97ac404806f53f61a702fdbf85db03e399 100644 --- a/manager/burrow-mint/evm/snative_test.go +++ b/execution/evm/snative_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "encoding/hex" @@ -20,11 +20,13 @@ import ( "strings" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/abi" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/execution/evm/abi" + "github.com/hyperledger/burrow/execution/evm/asm/bc" + "github.com/hyperledger/burrow/execution/evm/sha3" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/hyperledger/burrow/word256" "github.com/stretchr/testify/assert" ) @@ -63,12 +65,12 @@ func TestPermissionsContractSignatures(t *testing.T) { func TestSNativeContractDescription_Dispatch(t *testing.T) { contract := SNativeContracts()["Permissions"] state := newAppState() - caller := &Account{ - Address: addr(1, 1, 1), - } - grantee := &Account{ - Address: addr(2, 2, 2), - } + caller := acm.ConcreteAccount{ + Address: acm.Address{1, 1, 1}, + }.MutableAccount() + grantee := acm.ConcreteAccount{ + Address: acm.Address{2, 2, 2}, + }.MutableAccount() state.UpdateAccount(grantee) function, err := contract.FunctionByName("addRole") @@ -76,20 +78,20 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { t.Fatalf("Could not get function: %s", err) } funcID := function.ID() - gas := int64(1000) + gas := uint64(1000) // Should fail since we have no permissions - retValue, err := contract.Dispatch(state, caller, Bytecode(funcID[:], - grantee.Address, permFlagToWord256(ptypes.CreateAccount)), &gas) - assert.Error(t, err) - if err != nil { - assert.Contains(t, err.Error(), "does not have permission") + retValue, err := contract.Dispatch(state, caller, bc.Splice(funcID[:], + grantee.Address(), permFlagToWord256(permission.CreateAccount)), &gas, logger) + if !assert.Error(t, err, "Should fail due to lack of permissions") { + return } + assert.IsType(t, err, ErrLacksSNativePermission{}) // Grant all permissions and dispatch should success - caller.Permissions = allAccountPermissions() - retValue, err = contract.Dispatch(state, caller, Bytecode(funcID[:], - grantee.Address, permFlagToWord256(ptypes.CreateAccount)), &gas) + caller.SetPermissions(allAccountPermissions()) + retValue, err = contract.Dispatch(state, caller, bc.Splice(funcID[:], + grantee.Address().Word256(), permFlagToWord256(permission.CreateAccount)), &gas, logger) assert.NoError(t, err) assert.Equal(t, retValue, LeftPadBytes([]byte{1}, 32)) } @@ -97,7 +99,7 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { func TestSNativeContractDescription_Address(t *testing.T) { contract := NewSNativeContract("A comment", "CoolButVeryLongNamedContractOfDoom") - assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[12:], contract.AddressBytes()) + assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[12:], contract.Address().Bytes()) } // @@ -105,7 +107,8 @@ func TestSNativeContractDescription_Address(t *testing.T) { // func assertFunctionIDSignature(t *testing.T, contract *SNativeContractDescription, funcIDHex string, expectedSignature string) { - function, err := contract.FunctionByID(funcIDFromHex(t, funcIDHex)) + fromHex := funcIDFromHex(t, funcIDHex) + function, err := contract.FunctionByID(fromHex) assert.NoError(t, err, "Error retrieving SNativeFunctionDescription with ID %s", funcIDHex) if err == nil { @@ -127,15 +130,11 @@ func permFlagToWord256(permFlag ptypes.PermFlag) Word256 { return Uint64ToWord256(uint64(permFlag)) } -func addr(rightBytes ...uint8) Word256 { - return LeftPadWord256(rightBytes) -} - func allAccountPermissions() ptypes.AccountPermissions { return ptypes.AccountPermissions{ Base: ptypes.BasePermissions{ - Perms: ptypes.AllPermFlags, - SetBit: ptypes.AllPermFlags, + Perms: permission.AllPermFlags, + SetBit: permission.AllPermFlags, }, Roles: []string{}, } diff --git a/manager/burrow-mint/evm/stack.go b/execution/evm/stack.go similarity index 84% rename from manager/burrow-mint/evm/stack.go rename to execution/evm/stack.go index f9d48986b2040aa7592f512f5e53aa7b89ca4a8c..0d8a8f3fae56d01205a82656ec9d0670b1719cd5 100644 --- a/manager/burrow-mint/evm/stack.go +++ b/execution/evm/stack.go @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "fmt" - "github.com/hyperledger/burrow/common/math/integral" - "github.com/hyperledger/burrow/common/sanity" - . "github.com/hyperledger/burrow/word256" + . "github.com/hyperledger/burrow/binary" ) // Not goroutine safe @@ -27,11 +25,11 @@ type Stack struct { data []Word256 ptr int - gas *int64 + gas *uint64 err *error } -func NewStack(capacity int, gas *int64, err *error) *Stack { +func NewStack(capacity int, gas *uint64, err *error) *Stack { return &Stack{ data: make([]Word256, capacity), ptr: 0, @@ -40,7 +38,7 @@ func NewStack(capacity int, gas *int64, err *error) *Stack { } } -func (st *Stack) useGas(gasToUse int64) { +func (st *Stack) useGas(gasToUse uint64) { if *st.gas > gasToUse { *st.gas -= gasToUse } else { @@ -64,10 +62,10 @@ func (st *Stack) Push(d Word256) { st.ptr++ } -// currently only called after Sha3 +// currently only called after sha3.Sha3 func (st *Stack) PushBytes(bz []byte) { if len(bz) != 32 { - sanity.PanicSanity("Invalid bytes size: expected 32") + panic("Invalid bytes size: expected 32") } st.Push(LeftPadWord256(bz)) } @@ -76,6 +74,10 @@ func (st *Stack) Push64(i int64) { st.Push(Int64ToWord256(i)) } +func (st *Stack) PushU64(i uint64) { + st.Push(Uint64ToWord256(i)) +} + func (st *Stack) Pop() Word256 { st.useGas(GasStackOp) if st.ptr == 0 { @@ -95,6 +97,11 @@ func (st *Stack) Pop64() int64 { return Int64FromWord256(d) } +func (st *Stack) PopU64() uint64 { + d := st.Pop() + return Uint64FromWord256(d) +} + func (st *Stack) Len() int { return st.ptr } @@ -129,7 +136,10 @@ func (st *Stack) Peek() Word256 { func (st *Stack) Print(n int) { fmt.Println("### stack ###") if st.ptr > 0 { - nn := integral.MinInt(n, st.ptr) + nn := n + if st.ptr < n { + nn = 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 diff --git a/manager/burrow-mint/evm/vm.go b/execution/evm/vm.go similarity index 67% rename from manager/burrow-mint/evm/vm.go rename to execution/evm/vm.go index 60ae0f64e3bc0ffffa65e4f7849f75bb6221dab9..780d30321ea0558e8822be6de0e372839e6c9dca 100644 --- a/manager/burrow-mint/evm/vm.go +++ b/execution/evm/vm.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "bytes" @@ -20,14 +20,17 @@ import ( "fmt" "math/big" - "github.com/hyperledger/burrow/common/sanity" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + . "github.com/hyperledger/burrow/execution/evm/asm" + "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/execution/evm/sha3" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-events" ) var ( @@ -59,46 +62,43 @@ const ( callStackCapacity = 100 // TODO ensure usage. ) -type Debug bool - -var dbg Debug - -func SetDebug(d bool) { - dbg = Debug(d) -} - -func (d Debug) Printf(s string, a ...interface{}) { - if d { - fmt.Printf(s, a...) - } +type Params struct { + BlockHeight uint64 + BlockHash Word256 + BlockTime int64 + GasLimit uint64 } type VM struct { - appState AppState + state acm.StateWriter memoryProvider func() Memory params Params - origin Word256 + origin acm.Address txid []byte - - callDepth int - - evc events.Fireable + callDepth int + evc event.Fireable + logger logging_types.InfoTraceLogger } -func NewVM(appState AppState, memoryProvider func() Memory, params Params, - origin Word256, txid []byte) *VM { +func NewVM(state acm.StateWriter, memoryProvider func() Memory, params Params, origin acm.Address, txid []byte, + logger logging_types.InfoTraceLogger) *VM { return &VM{ - appState: appState, + state: state, memoryProvider: memoryProvider, params: params, origin: origin, callDepth: 0, txid: txid, + logger: logger.WithPrefix(structure.ComponentKey, "EVM"), } } -// satisfies events.Eventable -func (vm *VM) SetFireable(evc events.Fireable) { +func (vm *VM) Debugf(format string, a ...interface{}) { + logging.TraceMsg(vm.logger, fmt.Sprintf(format, a...)) +} + +// satisfies go_events.Eventable +func (vm *VM) SetFireable(evc event.Fireable) { vm.evc = evc } @@ -108,24 +108,25 @@ func (vm *VM) SetFireable(evc events.Fireable) { // on known permissions and panics else) // If the perm is not defined in the acc nor set by default in GlobalPermissions, // this function returns false. -func HasPermission(appState AppState, acc *Account, perm ptypes.PermFlag) bool { - v, err := acc.Permissions.Base.Get(perm) +func HasPermission(state acm.StateWriter, acc acm.Account, perm ptypes.PermFlag) bool { + v, err := acc.Permissions().Base.Get(perm) if _, ok := err.(ptypes.ErrValueNotSet); ok { - if appState == nil { + if state == nil { // In this case the permission is unknown return false } - return HasPermission(nil, appState.GetAccount(ptypes.GlobalPermissionsAddress256), perm) + return HasPermission(nil, permission.GlobalPermissionsAccount(state), perm) } return v } -func (vm *VM) fireCallEvent(exception *string, output *[]byte, caller, callee *Account, input []byte, value int64, gas *int64) { +func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, calleeAddress acm.Address, input []byte, value uint64, gas *uint64) { // fire the post call event (including exception if applicable) if vm.evc != nil { - vm.evc.FireEvent(txs.EventStringAccCall(callee.Address.Postfix(20)), txs.EventDataCall{ - &txs.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas}, - vm.origin.Postfix(20), + stringAccCall := events.EventStringAccCall(calleeAddress) + vm.evc.Fire(stringAccCall, events.EventDataCall{ + &events.CallData{Caller: callerAddress, Callee: calleeAddress, Data: input, Value: value, Gas: *gas}, + vm.origin, vm.txid, *output, *exception, @@ -133,17 +134,17 @@ func (vm *VM) fireCallEvent(exception *string, output *[]byte, caller, callee *A } } -// CONTRACT appState is aware of caller and callee, so we can just mutate them. +// CONTRACT state is aware of caller and callee, so we can just mutate them. // CONTRACT code and input are not mutated. // CONTRACT returned 'ret' is a new compact slice. // value: To be transferred from caller to callee. Refunded upon error. // gas: Available gas. No refunds for gas. // code: May be nil, since the CALL opcode may be used to send value from contracts to accounts -func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { +func (vm *VM) Call(caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { exception := new(string) // fire the post call event (including exception if applicable) - defer vm.fireCallEvent(exception, &output, caller, callee, input, value, gas) + defer vm.fireCallEvent(exception, &output, caller.Address(), callee.Address(), input, value, gas) if err = transfer(caller, callee, value); err != nil { *exception = err.Error() @@ -159,7 +160,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas err := transfer(callee, caller, value) if err != nil { // data has been corrupted in ram - sanity.PanicCrisis("Could not return value to caller") + panic("Could not return value to caller") } } } @@ -171,12 +172,12 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas // The intent of delegate call is to run the code of the callee in the storage context of the caller; // while preserving the original caller to the previous callee. // Different to the normal CALL or CALLCODE, the value does not need to be transferred to the callee. -func (vm *VM) DelegateCall(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { +func (vm *VM) DelegateCall(caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { exception := new(string) // fire the post call event (including exception if applicable) // NOTE: [ben] hotfix for issue 371; - // introduce event EventStringAccDelegateCall Acc/%X/DelegateCall + // introduce event EventStringAccDelegateCall Acc/%s/DelegateCall // defer vm.fireCallEvent(exception, &output, caller, callee, input, value, gas) // DelegateCall does not transfer the value to the callee. @@ -195,7 +196,7 @@ func (vm *VM) DelegateCall(caller, callee *Account, code, input []byte, value in // Try to deduct gasToUse from gasLeft. If ok return false, otherwise // set err and return true. -func useGasNegative(gasLeft *int64, gasToUse int64, err *error) bool { +func useGasNegative(gasLeft *uint64, gasToUse uint64, err *error) bool { if *gasLeft >= gasToUse { *gasLeft -= gasToUse return false @@ -206,8 +207,9 @@ func useGasNegative(gasLeft *int64, gasToUse int64, err *error) bool { } // Just like Call() but does not transfer 'value' or modify the callDepth. -func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { - dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) +func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { + vm.Debugf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address().Bytes()[:4], callee.Address(), + len(callee.Code()), *gas, input) var ( pc int64 = 0 @@ -222,7 +224,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } var op = codeGetOp(code, pc) - dbg.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) + vm.Debugf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) switch op { @@ -233,7 +235,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas sum := new(big.Int).Add(xb, yb) res := LeftPadWord256(U256(sum).Bytes()) stack.Push(res) - dbg.Printf(" %v + %v = %v (%X)\n", xb, yb, sum, res) + vm.Debugf(" %v + %v = %v (%X)\n", xb, yb, sum, res) case MUL: // 0x02 x, y := stack.Pop(), stack.Pop() @@ -242,7 +244,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas prod := new(big.Int).Mul(xb, yb) res := LeftPadWord256(U256(prod).Bytes()) stack.Push(res) - dbg.Printf(" %v * %v = %v (%X)\n", xb, yb, prod, res) + vm.Debugf(" %v * %v = %v (%X)\n", xb, yb, prod, res) case SUB: // 0x03 x, y := stack.Pop(), stack.Pop() @@ -251,69 +253,69 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas diff := new(big.Int).Sub(xb, yb) res := LeftPadWord256(U256(diff).Bytes()) stack.Push(res) - dbg.Printf(" %v - %v = %v (%X)\n", xb, yb, diff, res) + vm.Debugf(" %v - %v = %v (%X)\n", xb, yb, diff, res) case DIV: // 0x04 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %x / %x = %v\n", x, y, 0) + vm.Debugf(" %x / %x = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) div := new(big.Int).Div(xb, yb) res := LeftPadWord256(U256(div).Bytes()) stack.Push(res) - dbg.Printf(" %v / %v = %v (%X)\n", xb, yb, div, res) + vm.Debugf(" %v / %v = %v (%X)\n", xb, yb, div, res) } case SDIV: // 0x05 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %x / %x = %v\n", x, y, 0) + vm.Debugf(" %x / %x = %v\n", x, y, 0) } else { xb := S256(new(big.Int).SetBytes(x[:])) yb := S256(new(big.Int).SetBytes(y[:])) div := new(big.Int).Div(xb, yb) res := LeftPadWord256(U256(div).Bytes()) stack.Push(res) - dbg.Printf(" %v / %v = %v (%X)\n", xb, yb, div, res) + vm.Debugf(" %v / %v = %v (%X)\n", xb, yb, div, res) } case MOD: // 0x06 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) mod := new(big.Int).Mod(xb, yb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) + vm.Debugf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) } case SMOD: // 0x07 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := S256(new(big.Int).SetBytes(x[:])) yb := S256(new(big.Int).SetBytes(y[:])) mod := new(big.Int).Mod(xb, yb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) + vm.Debugf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) } case ADDMOD: // 0x08 x, y, z := stack.Pop(), stack.Pop(), stack.Pop() if z.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) @@ -322,7 +324,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas mod := new(big.Int).Mod(add, zb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v + %v %% %v = %v (%X)\n", + vm.Debugf(" %v + %v %% %v = %v (%X)\n", xb, yb, zb, mod, res) } @@ -330,7 +332,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas x, y, z := stack.Pop(), stack.Pop(), stack.Pop() if z.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) @@ -339,7 +341,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas mod := new(big.Int).Mod(mul, zb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v * %v %% %v = %v (%X)\n", + vm.Debugf(" %v * %v %% %v = %v (%X)\n", xb, yb, zb, mod, res) } @@ -350,7 +352,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas pow := new(big.Int).Exp(xb, yb, big.NewInt(0)) res := LeftPadWord256(U256(pow).Bytes()) stack.Push(res) - dbg.Printf(" %v ** %v = %v (%X)\n", xb, yb, pow, res) + vm.Debugf(" %v ** %v = %v (%X)\n", xb, yb, pow, res) case SIGNEXTEND: // 0x0B back := stack.Pop() @@ -367,7 +369,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas numb.Add(numb, mask) } res := LeftPadWord256(U256(numb).Bytes()) - dbg.Printf(" = %v (%X)", numb, res) + vm.Debugf(" = %v (%X)", numb, res) stack.Push(res) } @@ -377,10 +379,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := new(big.Int).SetBytes(y[:]) if xb.Cmp(yb) < 0 { stack.Push64(1) - dbg.Printf(" %v < %v = %v\n", xb, yb, 1) + vm.Debugf(" %v < %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v < %v = %v\n", xb, yb, 0) + vm.Debugf(" %v < %v = %v\n", xb, yb, 0) } case GT: // 0x11 @@ -389,10 +391,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := new(big.Int).SetBytes(y[:]) if xb.Cmp(yb) > 0 { stack.Push64(1) - dbg.Printf(" %v > %v = %v\n", xb, yb, 1) + vm.Debugf(" %v > %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v > %v = %v\n", xb, yb, 0) + vm.Debugf(" %v > %v = %v\n", xb, yb, 0) } case SLT: // 0x12 @@ -401,10 +403,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := S256(new(big.Int).SetBytes(y[:])) if xb.Cmp(yb) < 0 { stack.Push64(1) - dbg.Printf(" %v < %v = %v\n", xb, yb, 1) + vm.Debugf(" %v < %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v < %v = %v\n", xb, yb, 0) + vm.Debugf(" %v < %v = %v\n", xb, yb, 0) } case SGT: // 0x13 @@ -413,30 +415,30 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := S256(new(big.Int).SetBytes(y[:])) if xb.Cmp(yb) > 0 { stack.Push64(1) - dbg.Printf(" %v > %v = %v\n", xb, yb, 1) + vm.Debugf(" %v > %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v > %v = %v\n", xb, yb, 0) + vm.Debugf(" %v > %v = %v\n", xb, yb, 0) } case EQ: // 0x14 x, y := stack.Pop(), stack.Pop() if bytes.Equal(x[:], y[:]) { stack.Push64(1) - dbg.Printf(" %X == %X = %v\n", x, y, 1) + vm.Debugf(" %X == %X = %v\n", x, y, 1) } else { stack.Push(Zero256) - dbg.Printf(" %X == %X = %v\n", x, y, 0) + vm.Debugf(" %X == %X = %v\n", x, y, 0) } case ISZERO: // 0x15 x := stack.Pop() if x.IsZero() { stack.Push64(1) - dbg.Printf(" %v == 0 = %v\n", x, 1) + vm.Debugf(" %v == 0 = %v\n", x, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v == 0 = %v\n", x, 0) + vm.Debugf(" %v == 0 = %v\n", x, 0) } case AND: // 0x16 @@ -446,7 +448,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = x[i] & y[i] } stack.Push(z) - dbg.Printf(" %X & %X = %X\n", x, y, z) + vm.Debugf(" %X & %X = %X\n", x, y, z) case OR: // 0x17 x, y := stack.Pop(), stack.Pop() @@ -455,7 +457,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = x[i] | y[i] } stack.Push(z) - dbg.Printf(" %X | %X = %X\n", x, y, z) + vm.Debugf(" %X | %X = %X\n", x, y, z) case XOR: // 0x18 x, y := stack.Pop(), stack.Pop() @@ -464,7 +466,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = x[i] ^ y[i] } stack.Push(z) - dbg.Printf(" %X ^ %X = %X\n", x, y, z) + vm.Debugf(" %X ^ %X = %X\n", x, y, z) case NOT: // 0x19 x := stack.Pop() @@ -473,7 +475,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = ^x[i] } stack.Push(z) - dbg.Printf(" !%X = %X\n", x, z) + vm.Debugf(" !%X = %X\n", x, z) case BYTE: // 0x1A idx, val := stack.Pop64(), stack.Pop() @@ -482,7 +484,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas res = val[idx] } stack.Push64(int64(res)) - dbg.Printf(" => 0x%X\n", res) + vm.Debugf(" => 0x%X\n", res) case SHA3: // 0x20 if useGasNegative(gas, GasSha3, &err) { @@ -491,41 +493,44 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas offset, size := stack.Pop64(), stack.Pop64() data, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } data = sha3.Sha3(data) stack.PushBytes(data) - dbg.Printf(" => (%v) %X\n", size, data) + vm.Debugf(" => (%v) %X\n", size, data) case ADDRESS: // 0x30 - stack.Push(callee.Address) - dbg.Printf(" => %X\n", callee.Address) + stack.Push(callee.Address().Word256()) + vm.Debugf(" => %X\n", callee.Address()) case BALANCE: // 0x31 addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - balance := acc.Balance - stack.Push64(balance) - dbg.Printf(" => %v (%X)\n", balance, addr) + balance := acc.Balance() + stack.PushU64(balance) + vm.Debugf(" => %v (%X)\n", balance, addr) case ORIGIN: // 0x32 - stack.Push(vm.origin) - dbg.Printf(" => %X\n", vm.origin) + stack.Push(vm.origin.Word256()) + vm.Debugf(" => %X\n", vm.origin) case CALLER: // 0x33 - stack.Push(caller.Address) - dbg.Printf(" => %X\n", caller.Address) + stack.Push(caller.Address().Word256()) + vm.Debugf(" => %X\n", caller.Address()) case CALLVALUE: // 0x34 - stack.Push64(value) - dbg.Printf(" => %v\n", value) + stack.PushU64(value) + vm.Debugf(" => %v\n", value) case CALLDATALOAD: // 0x35 offset := stack.Pop64() @@ -535,11 +540,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } res := LeftPadWord256(data) stack.Push(res) - dbg.Printf(" => 0x%X\n", res) + vm.Debugf(" => 0x%X\n", res) case CALLDATASIZE: // 0x36 stack.Push64(int64(len(input))) - dbg.Printf(" => %d\n", len(input)) + vm.Debugf(" => %d\n", len(input)) case CALLDATACOPY: // 0x37 memOff := stack.Pop64() @@ -551,15 +556,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } memErr := memory.Write(memOff, data) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) case CODESIZE: // 0x38 l := int64(len(code)) stack.Push64(l) - dbg.Printf(" => %d\n", l) + vm.Debugf(" => %d\n", l) case CODECOPY: // 0x39 memOff := stack.Pop64() @@ -571,47 +576,53 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } memErr := memory.Write(memOff, data) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE_DEPRECATED: // 0x3A stack.Push(Zero256) - dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n") + vm.Debugf(" => %X (GASPRICE IS DEPRECATED)\n") case EXTCODESIZE: // 0x3B addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if acc == nil { if _, ok := registeredNativeContracts[addr]; !ok { return nil, firstErr(err, ErrUnknownAddress) } - dbg.Printf(" => returning code size of 1 to indicated existence of native contract at %X\n", addr) + vm.Debugf(" => returning code size of 1 to indicated existence of native contract at %X\n", addr) stack.Push(One256) } else { - code := acc.Code + code := acc.Code() l := int64(len(code)) stack.Push64(l) - dbg.Printf(" => %d\n", l) + vm.Debugf(" => %d\n", l) } case EXTCODECOPY: // 0x3C addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if acc == nil { if _, ok := registeredNativeContracts[addr]; ok { - dbg.Printf(" => attempted to copy native contract at %X but this is not supported\n", addr) + vm.Debugf(" => attempted to copy native contract at %X but this is not supported\n", addr) return nil, firstErr(err, ErrNativeContractCodeCopy) } return nil, firstErr(err, ErrUnknownAddress) } - code := acc.Code + code := acc.Code() memOff := stack.Pop64() codeOff := stack.Pop64() length := stack.Pop64() @@ -621,81 +632,84 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } memErr := memory.Write(memOff, data) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 stack.Push(Zero256) - dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + vm.Debugf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case COINBASE: // 0x41 stack.Push(Zero256) - dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + vm.Debugf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case TIMESTAMP: // 0x42 time := vm.params.BlockTime stack.Push64(int64(time)) - dbg.Printf(" => 0x%X\n", time) + vm.Debugf(" => 0x%X\n", time) case BLOCKHEIGHT: // 0x43 - number := int64(vm.params.BlockHeight) - stack.Push64(number) - dbg.Printf(" => 0x%X\n", number) + number := vm.params.BlockHeight + stack.PushU64(number) + vm.Debugf(" => 0x%X\n", number) case GASLIMIT: // 0x45 - stack.Push64(vm.params.GasLimit) - dbg.Printf(" => %v\n", vm.params.GasLimit) + stack.PushU64(vm.params.GasLimit) + vm.Debugf(" => %v\n", vm.params.GasLimit) case POP: // 0x50 popped := stack.Pop() - dbg.Printf(" => 0x%X\n", popped) + vm.Debugf(" => 0x%X\n", popped) case MLOAD: // 0x51 offset := stack.Pop64() data, memErr := memory.Read(offset, 32) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } stack.Push(LeftPadWord256(data)) - dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) + vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE: // 0x52 offset, data := stack.Pop64(), stack.Pop() memErr := memory.Write(offset, data.Bytes()) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) + vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE8: // 0x53 offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) memErr := memory.Write(offset, []byte{val}) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v] 0x%X\n", offset, val) + vm.Debugf(" => [%v] 0x%X\n", offset, val) case SLOAD: // 0x54 loc := stack.Pop() - data := vm.appState.GetStorage(callee.Address, loc) + data, errSto := vm.state.GetStorage(callee.Address(), loc) + if errSto != nil { + return nil, firstErr(err, errSto) + } stack.Push(data) - dbg.Printf(" {0x%X : 0x%X}\n", loc, data) + vm.Debugf(" {0x%X : 0x%X}\n", loc, data) case SSTORE: // 0x55 loc, data := stack.Pop(), stack.Pop() if useGasNegative(gas, GasStorageUpdate, &err) { return nil, err } - vm.appState.SetStorage(callee.Address, loc, data) - dbg.Printf(" {0x%X : 0x%X}\n", loc, data) + vm.state.SetStorage(callee.Address(), loc, data) + vm.Debugf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 - if err = jump(code, stack.Pop64(), &pc); err != nil { + if err = vm.jump(code, stack.Pop64(), &pc); err != nil { return nil, err } continue @@ -703,12 +717,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case JUMPI: // 0x57 pos, cond := stack.Pop64(), stack.Pop() if !cond.IsZero() { - if err = jump(code, pos, &pc); err != nil { + if err = vm.jump(code, pos, &pc); err != nil { return nil, err } continue } - dbg.Printf(" ~> false\n") + vm.Debugf(" ~> false\n") case PC: // 0x58 stack.Push64(pc) @@ -719,14 +733,14 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // this offset. capacity := memory.Capacity() stack.Push64(capacity) - dbg.Printf(" => 0x%X\n", capacity) + vm.Debugf(" => 0x%X\n", capacity) case GAS: // 0x5A - stack.Push64(*gas) - dbg.Printf(" => %X\n", *gas) + stack.PushU64(*gas) + vm.Debugf(" => %X\n", *gas) case JUMPDEST: // 0x5B - dbg.Printf("\n") + vm.Debugf("\n") // Do nothing 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: @@ -738,18 +752,18 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas res := LeftPadWord256(codeSegment) stack.Push(res) pc += a - dbg.Printf(" => 0x%X\n", res) + vm.Debugf(" => 0x%X\n", res) //stack.Print(10) case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: n := int(op - DUP1 + 1) stack.Dup(n) - dbg.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) + vm.Debugf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: n := int(op - SWAP1 + 2) stack.Swap(n) - dbg.Printf(" => [%d] %X\n", n, stack.Peek()) + vm.Debugf(" => [%d] %X\n", n, stack.Peek()) //stack.Print(10) case LOG0, LOG1, LOG2, LOG3, LOG4: @@ -761,41 +775,42 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } data, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } if vm.evc != nil { - eventID := txs.EventStringLogEvent(callee.Address.Postfix(20)) + eventID := events.EventStringLogEvent(callee.Address()) fmt.Printf("eventID: %s\n", eventID) - log := txs.EventDataLog{ - callee.Address, - topics, - data, - vm.params.BlockHeight, + log := events.EventDataLog{ + Address: callee.Address(), + Topics: topics, + Data: data, + Height: vm.params.BlockHeight, } - vm.evc.FireEvent(eventID, log) + vm.evc.Fire(eventID, log) } - dbg.Printf(" => T:%X D:%X\n", topics, data) + vm.Debugf(" => T:%X D:%X\n", topics, data) case CREATE: // 0xF0 - if !HasPermission(vm.appState, callee, ptypes.CreateContract) { + if !HasPermission(vm.state, callee, permission.CreateContract) { return nil, ErrPermission{"create_contract"} } - contractValue := stack.Pop64() + contractValue := stack.PopU64() offset, size := stack.Pop64(), stack.Pop64() input, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } // Check balance - if callee.Balance < contractValue { + if callee.Balance() < uint64(contractValue) { return nil, firstErr(err, ErrInsufficientBalance) } // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount := vm.appState.CreateAccount(callee) + newAccount := DeriveNewAccount(callee, permission.GlobalAccountPermissions(vm.state)) + vm.state.UpdateAccount(newAccount) // Run the input to get the contract code. // NOTE: no need to copy 'input' as per Call contract. @@ -803,15 +818,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if err_ != nil { stack.Push(Zero256) } else { - newAccount.Code = ret // Set the code (ret need not be copied as per Call contract) - stack.Push(newAccount.Address) + newAccount.SetCode(ret) // Set the code (ret need not be copied as per Call contract) + stack.Push(newAccount.Address().Word256()) } case CALL, CALLCODE, DELEGATECALL: // 0xF1, 0xF2, 0xF4 - if !HasPermission(vm.appState, callee, ptypes.Call) { + if !HasPermission(vm.state, callee, permission.Call) { return nil, ErrPermission{"call"} } - gasLimit := stack.Pop64() + gasLimit := stack.PopU64() addr := stack.Pop() // NOTE: for DELEGATECALL value is preserved from the original // caller, as such it is not stored on stack as an argument @@ -819,16 +834,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // caller value is used. for CALL and CALLCODE value is stored // on stack and needs to be overwritten from the given value. if op != DELEGATECALL { - value = stack.Pop64() + value = stack.PopU64() } inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs - dbg.Printf(" => %X\n", addr) + vm.Debugf(" => %X\n", addr) // Get the arguments from the memory args, memErr := memory.Read(inOffset, inSize) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } @@ -845,21 +860,24 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas var err error if nativeContract := registeredNativeContracts[addr]; nativeContract != nil { // Native contract - ret, err = nativeContract(vm.appState, callee, args, &gasLimit) + ret, err = nativeContract(vm.state, callee, args, &gasLimit, vm.logger) // for now we fire the Call event. maybe later we'll fire more particulars var exception string if err != nil { exception = err.Error() } - // NOTE: these fire call events and not particular events for eg name reg or permissions - vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, &gasLimit) + // NOTE: these fire call go_events and not particular go_events for eg name reg or permissions + vm.fireCallEvent(&exception, &ret, callee.Address(), acm.AddressFromWord256(addr), args, value, &gasLimit) } else { // EVM contract if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } // since CALL is used also for sending funds, // acc may not exist yet. This is an error for // CALLCODE, but not for CALL, though I don't think @@ -868,29 +886,29 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - ret, err = vm.Call(callee, callee, acc.Code, args, value, &gasLimit) + ret, err = vm.Call(callee, callee, acc.Code(), args, value, &gasLimit) } else if op == DELEGATECALL { if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - ret, err = vm.DelegateCall(caller, callee, acc.Code, args, value, &gasLimit) + ret, err = vm.DelegateCall(caller, callee, acc.Code(), args, value, &gasLimit) } else { // nil account means we're sending funds to a new account if acc == nil { - if !HasPermission(vm.appState, caller, ptypes.CreateAccount) { + if !HasPermission(vm.state, caller, permission.CreateAccount) { return nil, ErrPermission{"create_account"} } - acc = &Account{Address: addr} + acc = (&acm.ConcreteAccount{Address: acm.AddressFromWord256(addr)}).MutableAccount() } // add account to the tx cache - vm.appState.UpdateAccount(acc) - ret, err = vm.Call(callee, acc, acc.Code, args, value, &gasLimit) + vm.state.UpdateAccount(acc) + ret, err = vm.Call(callee, acc, acc.Code(), args, value, &gasLimit) } } // Push result if err != nil { - dbg.Printf("error on call: %s\n", err.Error()) + vm.Debugf("error on call: %s\n", err.Error()) stack.Push(Zero256) } else { stack.Push(One256) @@ -900,7 +918,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // defensively pad or truncate the portion of ret to be returned. memErr := memory.Write(retOffset, RightPadBytes(ret, int(retSize))) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } } @@ -908,16 +926,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // Handle remaining gas. *gas += gasLimit - dbg.Printf("resume %X (%v)\n", callee.Address, gas) + vm.Debugf("resume %s (%v)\n", callee.Address(), gas) case RETURN: // 0xF3 offset, size := stack.Pop64(), stack.Pop64() output, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) + vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, nil case SELFDESTRUCT: // 0xFF @@ -927,22 +945,28 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } // TODO if the receiver is , then make it the fee. (?) // TODO: create account if doesn't exist (no reason not to) - receiver := vm.appState.GetAccount(addr) + receiver, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if receiver == nil { return nil, firstErr(err, ErrUnknownAddress) } - balance := callee.Balance - receiver.Balance += balance - vm.appState.UpdateAccount(receiver) - vm.appState.RemoveAccount(callee) - dbg.Printf(" => (%X) %v\n", addr[:4], balance) + + receiver, errAdd := receiver.AddToBalance(callee.Balance()) + if errAdd != nil { + return nil, firstErr(err, errAdd) + } + vm.state.UpdateAccount(receiver) + vm.state.RemoveAccount(callee.Address()) + vm.Debugf(" => (%X) %v\n", addr[:4], callee.Balance()) fallthrough case STOP: // 0x00 return nil, nil default: - dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) + vm.Debugf("(pc) %-3v Invalid opcode %X\n", pc, op) return nil, fmt.Errorf("Invalid opcode %X", op) } @@ -981,13 +1005,13 @@ func codeGetOp(code []byte, n int64) OpCode { } } -func jump(code []byte, to int64, pc *int64) (err error) { +func (vm *VM) jump(code []byte, to int64, pc *int64) (err error) { dest := codeGetOp(code, to) if dest != JUMPDEST { - dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest) + vm.Debugf(" ~> %v invalid jump dest %v\n", to, dest) return ErrInvalidJumpDest } - dbg.Printf(" ~> %v\n", to) + vm.Debugf(" ~> %v\n", to) *pc = to return nil } @@ -1000,12 +1024,15 @@ func firstErr(errA, errB error) error { } } -func transfer(from, to *Account, amount int64) error { - if from.Balance < amount { +func transfer(from, to acm.MutableAccount, amount uint64) error { + if from.Balance() < amount { return ErrInsufficientBalance } else { - from.Balance -= amount - to.Balance += amount - return nil + from.SubtractFromBalance(amount) + _, err := to.AddToBalance(amount) + if err != nil { + return err + } } + return nil } diff --git a/manager/burrow-mint/evm/vm_test.go b/execution/evm/vm_test.go similarity index 65% rename from manager/burrow-mint/evm/vm_test.go rename to execution/evm/vm_test.go index 0dd356827dbb1fa1d732ddf5cdd321af523dc16d..4e8beb6884ccd45a1ceef7a6cce688ebd655c7e4 100644 --- a/manager/burrow-mint/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -12,39 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( - "crypto/rand" "encoding/hex" "fmt" "strings" "testing" "time" + acm "github.com/hyperledger/burrow/account" + "errors" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + . "github.com/hyperledger/burrow/execution/evm/asm" + . "github.com/hyperledger/burrow/execution/evm/asm/bc" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/logging/lifecycle" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/permission" "github.com/stretchr/testify/assert" - "github.com/tendermint/go-events" + "github.com/stretchr/testify/require" ) -func init() { - SetDebug(true) -} +var logger, _ = lifecycle.NewStdErrLogger() func newAppState() *FakeAppState { fas := &FakeAppState{ - accounts: make(map[string]*Account), + accounts: make(map[acm.Address]acm.Account), storage: make(map[string]Word256), } // For default permissions - fas.accounts[ptypes.GlobalPermissionsAddress256.String()] = &Account{ - Permissions: ptypes.DefaultAccountPermissions, - } + fas.accounts[permission.GlobalPermissionsAddress] = acm.ConcreteAccount{ + Permissions: permission.DefaultAccountPermissions, + }.Account() return fas } @@ -57,25 +61,21 @@ func newParams() Params { } } -func makeBytes(n int) []byte { - b := make([]byte, n) - rand.Read(b) - return b +func newAccount(address ...byte) acm.MutableAccount { + return acm.ConcreteAccount{ + Address: acm.AddressFromWord256(RightPadWord256(address)), + }.MutableAccount() } // Runs a basic loop func TestVM(t *testing.T) { - ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) // Create accounts - account1 := &Account{ - Address: Int64ToWord256(100), - } - account2 := &Account{ - Address: Int64ToWord256(101), - } + account1 := newAccount(1) + account2 := newAccount(1, 0, 1) - var gas int64 = 100000 + var gas uint64 = 100000 N := []byte{0x0f, 0x0f} // Loop N times code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} @@ -91,17 +91,13 @@ func TestVM(t *testing.T) { } func TestJumpErr(t *testing.T) { - ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) // Create accounts - account1 := &Account{ - Address: Int64ToWord256(100), - } - account2 := &Account{ - Address: Int64ToWord256(101), - } + account1 := newAccount(1) + account2 := newAccount(2) - var gas int64 = 100000 + var gas uint64 = 100000 code := []byte{0x60, 0x10, 0x56} // jump to position 16, a clear failure var err error ch := make(chan struct{}) @@ -124,18 +120,14 @@ func TestJumpErr(t *testing.T) { 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 + account1 := newAccount(1, 2, 3) + account2 := newAccount(3, 2, 1) + st.accounts[account1.Address()] = account1 + st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) - var gas int64 = 1000 + var gas uint64 = 1000 code_parts := []string{"620f42403355", "7c0100000000000000000000000000000000000000000000000000000000", "600035046315cf268481141561004657", @@ -143,7 +135,7 @@ func TestSubcurrency(t *testing.T) { "60043560805260243560a052335460c0523360e05260a05160c05112151561008657", "60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"} code, _ := hex.DecodeString(strings.Join(code_parts, "")) - fmt.Printf("Code: %x\n", code) + fmt.Printf("Code: %s\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) @@ -155,21 +147,15 @@ func TestSubcurrency(t *testing.T) { // Test sending tokens from a contract to another account func TestSendCall(t *testing.T) { fakeAppState := newAppState() - ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) // Create accounts - account1 := &Account{ - Address: Int64ToWord256(100), - } - account2 := &Account{ - Address: Int64ToWord256(101), - } - account3 := &Account{ - Address: Int64ToWord256(102), - } + account1 := newAccount(1) + account2 := newAccount(2) + account3 := newAccount(3) // account1 will call account2 which will trigger CALL opcode to account3 - addr := account3.Address.Postfix(20) + addr := account3.Address() contractCode := callContractCode(addr) //---------------------------------------------- @@ -179,14 +165,15 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // give account2 sufficient balance, should pass - account2.Balance = 100000 + account2, err = newAccount(2).AddToBalance(100000) + require.NoError(t, err) _, err = runVMWaitError(ourVm, account1, account2, addr, contractCode, 1000) assert.NoError(t, err, "Should have sufficient balance") //---------------------------------------------- // insufficient gas, should fail - - account2.Balance = 100000 + account2, err = newAccount(2).AddToBalance(100000) + require.NoError(t, err) _, err = runVMWaitError(ourVm, account1, account2, addr, contractCode, 100) assert.Error(t, err, "Expected insufficient gas error") } @@ -197,8 +184,8 @@ func TestSendCall(t *testing.T) { // We first run the DELEGATECALL with _just_ enough gas expecting a simple return, // and then run it with 1 gas unit less, expecting a failure func TestDelegateCallGas(t *testing.T) { - appState := newAppState() - ourVm := NewVM(appState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + state := newAppState() + ourVm := NewVM(state, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) inOff := 0 inSize := 0 // no call data @@ -218,55 +205,55 @@ func TestDelegateCallGas(t *testing.T) { costBetweenGasAndDelegateCall := gasCost + subCost + delegateCallCost + pushCost // Do a simple operation using 1 gas unit - calleeAccount, calleeAddress := makeAccountWithCode(appState, "callee", - Bytecode(PUSH1, calleeReturnValue, return1())) + calleeAccount, calleeAddress := makeAccountWithCode(state, "callee", + Splice(PUSH1, calleeReturnValue, return1())) // Here we split up the caller code so we can make a DELEGATE call with // different amounts of gas. The value we sandwich in the middle is the amount // we subtract from the available gas (that the caller has available), so: - // code := Bytecode(callerCodePrefix, <amount to subtract from GAS> , callerCodeSuffix) + // code := Splice(callerCodePrefix, <amount to subtract from GAS> , callerCodeSuffix) // gives us the code to make the call - callerCodePrefix := Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, + callerCodePrefix := Splice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH20, calleeAddress, PUSH1) - callerCodeSuffix := Bytecode(GAS, SUB, DELEGATECALL, returnWord()) + callerCodeSuffix := Splice(GAS, SUB, DELEGATECALL, returnWord()) // Perform a delegate call - callerAccount, _ := makeAccountWithCode(appState, "caller", - Bytecode(callerCodePrefix, + callerAccount, _ := makeAccountWithCode(state, "caller", + Splice(callerCodePrefix, // Give just enough gas to make the DELEGATECALL costBetweenGasAndDelegateCall, callerCodeSuffix)) // Should pass output, err := runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, - callerAccount.Code, 100) + callerAccount.Code(), 100) assert.NoError(t, err, "Should have sufficient funds for call") assert.Equal(t, Int64ToWord256(calleeReturnValue).Bytes(), output) - callerAccount.Code = Bytecode(callerCodePrefix, + callerAccount.SetCode(Splice(callerCodePrefix, // Shouldn't be enough gas to make call costBetweenGasAndDelegateCall-1, - callerCodeSuffix) + callerCodeSuffix)) // Should fail _, err = runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, - callerAccount.Code, 100) + callerAccount.Code(), 100) assert.Error(t, err, "Should have insufficient funds for call") } func TestMemoryBounds(t *testing.T) { - appState := newAppState() + state := newAppState() memoryProvider := func() Memory { return NewDynamicMemory(1024, 2048) } - ourVm := NewVM(appState, memoryProvider, newParams(), Zero256, nil) - caller, _ := makeAccountWithCode(appState, "caller", nil) - callee, _ := makeAccountWithCode(appState, "callee", nil) - gas := int64(100000) + ourVm := NewVM(state, memoryProvider, newParams(), acm.ZeroAddress, nil, logger) + caller, _ := makeAccountWithCode(state, "caller", nil) + callee, _ := makeAccountWithCode(state, "callee", nil) + gas := uint64(100000) // This attempts to store a value at the memory boundary and return it word := One256 output, err := ourVm.call(caller, callee, - Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + Splice(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.NoError(t, err) assert.Equal(t, word.Bytes(), output) @@ -274,7 +261,7 @@ func TestMemoryBounds(t *testing.T) { // Same with number word = Int64ToWord256(232234234432) output, err = ourVm.call(caller, callee, - Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + Splice(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.NoError(t, err) assert.Equal(t, word.Bytes(), output) @@ -282,9 +269,9 @@ func TestMemoryBounds(t *testing.T) { // Now test a series of boundary stores code := pushWord(word) for i := 0; i < 10; i++ { - code = Bytecode(code, storeAtEnd(), MLOAD) + code = Splice(code, storeAtEnd(), MLOAD) } - output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + output, err = ourVm.call(caller, callee, Splice(code, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.NoError(t, err) assert.Equal(t, word.Bytes(), output) @@ -292,9 +279,9 @@ func TestMemoryBounds(t *testing.T) { // Same as above but we should breach the upper memory limit set in memoryProvider code = pushWord(word) for i := 0; i < 100; i++ { - code = Bytecode(code, storeAtEnd(), MLOAD) + code = Splice(code, storeAtEnd(), MLOAD) } - output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + output, err = ourVm.call(caller, callee, Splice(code, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.Error(t, err, "Should hit memory out of bounds") } @@ -307,49 +294,44 @@ func TestMemoryBounds(t *testing.T) { // stores that value at the current memory boundary func storeAtEnd() []byte { // Pull in MSIZE (to carry forward to MLOAD), swap in value to store, store it at MSIZE - return Bytecode(MSIZE, SWAP1, DUP2, MSTORE) + return Splice(MSIZE, SWAP1, DUP2, MSTORE) } func returnAfterStore() []byte { - return Bytecode(PUSH1, 32, DUP2, RETURN) + return Splice(PUSH1, 32, DUP2, RETURN) } // Store the top element of the stack (which is a 32-byte word) in memory // and return it. Useful for a simple return value. func return1() []byte { - return Bytecode(PUSH1, 0, MSTORE, returnWord()) + return Splice(PUSH1, 0, MSTORE, returnWord()) } func returnWord() []byte { // PUSH1 => return size, PUSH1 => return offset, RETURN - return Bytecode(PUSH1, 32, PUSH1, 0, RETURN) + return Splice(PUSH1, 32, PUSH1, 0, RETURN) } -func makeAccountWithCode(appState AppState, name string, - code []byte) (*Account, []byte) { - account := &Account{ - Address: LeftPadWord256([]byte(name)), - Balance: 9999999, - Code: code, - Nonce: 0, - } - account.Code = code - appState.UpdateAccount(account) - // Sanity check - address := new([20]byte) - for i, b := range account.Address.Postfix(20) { - address[i] = b - } - return account, address[:] +func makeAccountWithCode(state acm.Updater, name string, + code []byte) (acm.MutableAccount, acm.Address) { + address, _ := acm.AddressFromBytes([]byte(name)) + account := acm.ConcreteAccount{ + Address: address, + Balance: 9999999, + Code: code, + Sequence: 0, + }.MutableAccount() + state.UpdateAccount(account) + return account, account.Address() } // Subscribes to an AccCall, runs the vm, returns the output any direct exception -// and then waits for any exceptions transmitted by EventData in the AccCall +// and then waits for any exceptions transmitted by Data in the AccCall // event (in the case of no direct error from call we will block waiting for // at least 1 AccCall event) -func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, - contractCode []byte, gas int64) (output []byte, err error) { - eventCh := make(chan txs.EventData) +func runVMWaitError(ourVm *VM, caller, callee acm.MutableAccount, subscribeAddr acm.Address, + contractCode []byte, gas uint64) (output []byte, err error) { + eventCh := make(chan event.EventData) output, err = runVM(eventCh, ourVm, caller, callee, subscribeAddr, contractCode, gas) if err != nil { @@ -357,10 +339,10 @@ func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, } msg := <-eventCh var errString string - switch ev := msg.(type) { - case txs.EventDataTx: + switch ev := msg.Unwrap().(type) { + case exe_events.EventDataTx: errString = ev.Exception - case txs.EventDataCall: + case evm_events.EventDataCall: errString = ev.Exception } @@ -372,18 +354,17 @@ func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, // Subscribes to an AccCall, runs the vm, returns the output and any direct // exception -func runVM(eventCh chan txs.EventData, ourVm *VM, caller, callee *Account, - subscribeAddr, contractCode []byte, gas int64) ([]byte, error) { +func runVM(eventCh chan event.EventData, ourVm *VM, caller, callee acm.MutableAccount, + subscribeAddr acm.Address, contractCode []byte, gas uint64) ([]byte, error) { // we need to catch the event from the CALL to check for exceptions - evsw := events.NewEventSwitch() - evsw.Start() - fmt.Printf("subscribe to %x\n", subscribeAddr) - evsw.AddListenerForEvent("test", txs.EventStringAccCall(subscribeAddr), - func(msg events.EventData) { - eventCh <- msg.(txs.EventData) + evsw := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) + fmt.Printf("subscribe to %s\n", subscribeAddr) + evsw.Subscribe("test", evm_events.EventStringAccCall(subscribeAddr), + func(msg event.AnyEventData) { + eventCh <- *msg.BurrowEventData }) - evc := events.NewEventCache(evsw) + evc := event.NewEventCache(evsw) ourVm.SetFireable(evc) start := time.Now() output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas) @@ -394,13 +375,13 @@ func runVM(eventCh chan txs.EventData, ourVm *VM, caller, callee *Account, } // this is code to call another contract (hardcoded as addr) -func callContractCode(addr []byte) []byte { +func callContractCode(addr acm.Address) []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) - return Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, + return Splice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20, addr, PUSH2, gas1, gas2, CALL, PUSH1, retSize, PUSH1, retOff, RETURN) } @@ -417,50 +398,50 @@ func pushWord(word Word256) []byte { if word[leadingZeros] == 0 { leadingZeros++ } else { - return Bytecode(byte(PUSH32)-leadingZeros, word[leadingZeros:]) + return Splice(byte(PUSH32)-leadingZeros, word[leadingZeros:]) } } - return Bytecode(PUSH1, 0) + return Splice(PUSH1, 0) } func TestPushWord(t *testing.T) { word := Int64ToWord256(int64(2133213213)) - assert.Equal(t, Bytecode(PUSH4, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) + assert.Equal(t, Splice(PUSH4, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) word[0] = 1 - assert.Equal(t, Bytecode(PUSH32, + assert.Equal(t, Splice(PUSH32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) - assert.Equal(t, Bytecode(PUSH1, 0), pushWord(Word256{})) - assert.Equal(t, Bytecode(PUSH1, 1), pushWord(Int64ToWord256(1))) + assert.Equal(t, Splice(PUSH1, 0), pushWord(Word256{})) + assert.Equal(t, Splice(PUSH1, 1), pushWord(Int64ToWord256(1))) } func TestBytecode(t *testing.T) { assert.Equal(t, - Bytecode(1, 2, 3, 4, 5, 6), - Bytecode(1, 2, 3, Bytecode(4, 5, 6))) + Splice(1, 2, 3, 4, 5, 6), + Splice(1, 2, 3, Splice(4, 5, 6))) assert.Equal(t, - Bytecode(1, 2, 3, 4, 5, 6, 7, 8), - Bytecode(1, 2, 3, Bytecode(4, Bytecode(5), 6), 7, 8)) + Splice(1, 2, 3, 4, 5, 6, 7, 8), + Splice(1, 2, 3, Splice(4, Splice(5), 6), 7, 8)) assert.Equal(t, - Bytecode(PUSH1, 2), - Bytecode(byte(PUSH1), 0x02)) + Splice(PUSH1, 2), + Splice(byte(PUSH1), 0x02)) assert.Equal(t, []byte{}, - Bytecode(Bytecode(Bytecode()))) + Splice(Splice(Splice()))) - contractAccount := &Account{Address: Int64ToWord256(102)} - addr := contractAccount.Address.Postfix(20) + contractAccount := &acm.ConcreteAccount{Address: acm.AddressFromWord256(Int64ToWord256(102))} + addr := contractAccount.Address gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x69) inOff, inSize := byte(0x0), byte(0x0) // no call data retOff, retSize := byte(0x0), byte(0x20) - contractCodeBytecode := Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, + contractCodeBytecode := Splice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20, addr, PUSH2, gas1, gas2, CALL, PUSH1, retSize, PUSH1, retOff, RETURN) contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73} - contractCode = append(contractCode, addr...) + contractCode = append(contractCode, addr[:]...) contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) assert.Equal(t, contractCode, contractCodeBytecode) } diff --git a/manager/burrow-mint/state/execution.go b/execution/execution.go similarity index 52% rename from manager/burrow-mint/state/execution.go rename to execution/execution.go index 4c765ec7f985fa2b92ce500e0be8d2f8b36c3d79..baee18176510a1ad4eca87a46126f5a7b904dfb6 100644 --- a/manager/burrow-mint/state/execution.go +++ b/execution/execution.go @@ -12,341 +12,170 @@ // See the License for the specific language governing permissions and // limitations under the License. -package state +package execution import ( - "bytes" "fmt" + "sync" acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/sanity" - core_types "github.com/hyperledger/burrow/core/types" + "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/evm" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" logging_types "github.com/hyperledger/burrow/logging/types" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - ptypes "github.com/hyperledger/burrow/permission/types" // for GlobalPermissionAddress ... + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/hyperledger/burrow/logging" - "github.com/tendermint/go-events" ) -// ExecBlock stuff is now taken care of by the consensus engine. -// But we leave here for now for reference when we have to do validator updates - -/* - -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling ExecBlock! -func ExecBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { - err := execBlock(s, block, blockPartsHeader) - if err != nil { - return err - } - // State.Hash should match block.StateHash - stateHash := s.Hash() - if !bytes.Equal(stateHash, block.StateHash) { - return errors.New(Fmt("Invalid state hash. Expected %X, got %X", - stateHash, block.StateHash)) - } - return nil +type BatchExecutor interface { + acm.StateIterable + acm.Updater + acm.StorageSetter + // Execute transaction against block cache (i.e. block buffer) + Execute(tx txs.Tx) error + // Reset executor to underlying State + Reset() error } -// executes transactions of a block, does not check block.StateHash -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling execBlock! -func execBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { - // Basic block validation. - err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime) - if err != nil { - return err - } - - // Validate block LastValidation. - if block.Height == 1 { - if len(block.LastValidation.Precommits) != 0 { - return errors.New("Block at height 1 (first block) should have no LastValidation precommits") - } - } else { - if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() { - return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", - s.LastBondedValidators.Size(), len(block.LastValidation.Precommits))) - } - err := s.LastBondedValidators.VerifyValidation( - s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation) - if err != nil { - return err - } - } - - // Update Validator.LastCommitHeight as necessary. - for i, precommit := range block.LastValidation.Precommits { - if precommit == nil { - continue - } - _, val := s.LastBondedValidators.GetByIndex(i) - if val == nil { - PanicCrisis(Fmt("Failed to fetch validator at index %v", i)) - } - if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.BondedValidators.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") - } - } else { - PanicCrisis("Could not find validator") - } - } - - // Remember LastBondedValidators - s.LastBondedValidators = s.BondedValidators.Copy() - - // Create BlockCache to cache changes to state. - blockCache := NewBlockCache(s) - - // Execute each tx - for _, tx := range block.Data.Txs { - err := ExecTx(blockCache, tx, true, s.evc) - if err != nil { - return InvalidTxError{tx, err} - } - } +// Executes transactions +type BatchCommitter interface { + BatchExecutor + // Commit execution results to underlying State and provide opportunity + // to mutate state before it is saved + Commit() (stateHash []byte, err error) +} - // Now sync the BlockCache to the backend. - blockCache.Sync() +type executor struct { + mtx sync.Mutex + chainID string + tip bcm.Tip + runCall bool + state *State + blockCache *BlockCache + fireable event.Fireable + eventCache *event.Cache + logger logging_types.InfoTraceLogger +} - // If any unbonding periods are over, - // reward account with bonded coins. - toRelease := []*txs.Validator{} - s.UnbondingValidators.Iterate(func(index int, val *txs.Validator) bool { - if val.UnbondHeight+unbondingPeriodBlocks < block.Height { - toRelease = append(toRelease, val) - } - return false - }) - for _, val := range toRelease { - s.releaseValidator(val) - } +var _ BatchExecutor = (*executor)(nil) - // If any validators haven't signed in a while, - // unbond them, they have timed out. - toTimeout := []*txs.Validator{} - s.BondedValidators.Iterate(func(index int, val *txs.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) - } +// Wraps a cache of what is variously known as the 'check cache' and 'mempool' +func NewBatchChecker(state *State, + chainID string, + tip bcm.Tip, + logger logging_types.InfoTraceLogger) BatchExecutor { + return newExecutor(false, state, chainID, tip, event.NewNoOpFireable(), + logging.WithScope(logger, "NewBatchExecutor")) +} - // Increment validator AccumPowers - s.BondedValidators.IncrementAccum(1) - s.LastBlockHeight = block.Height - s.LastBlockHash = block.Hash() - s.LastBlockParts = blockPartsHeader - s.LastBlockTime = block.Time - return nil +func NewBatchCommitter(state *State, + chainID string, + tip bcm.Tip, + fireable event.Fireable, + logger logging_types.InfoTraceLogger) BatchCommitter { + return newExecutor(true, state, chainID, tip, fireable, + logging.WithScope(logger, "NewBatchCommitter")) } -*/ -// 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 []*txs.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, txs.ErrTxDuplicateAddress - } - acc := state.GetAccount(in.Address) - if acc == nil { - return nil, txs.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 +func newExecutor(runCall bool, + state *State, + chainID string, + tip bcm.Tip, + eventFireable event.Fireable, + logger logging_types.InfoTraceLogger) *executor { + return &executor{ + chainID: chainID, + tip: tip, + runCall: runCall, + state: state, + blockCache: NewBlockCache(state), + fireable: eventFireable, + eventCache: event.NewEventCache(eventFireable), + logger: logger.With(structure.ComponentKey, "Execution"), } - return accounts, nil } -func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, - outs []*txs.TxOutput, logger logging_types.InfoTraceLogger) (map[string]*acm.Account, error) { - if accounts == nil { - accounts = make(map[string]*acm.Account) - } +// Accounts +func (exe *executor) GetAccount(address acm.Address) (acm.Account, error) { + return exe.blockCache.GetAccount(address) +} - // 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, txs.ErrTxDuplicateAddress - } - acc := state.GetAccount(out.Address) - // output account may be nil (new) - if acc == nil { - if !checkedCreatePerms { - if !hasCreateAccountPermission(state, accounts, logger) { - 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 (exe *executor) UpdateAccount(account acm.Account) error { + return exe.blockCache.UpdateAccount(account) } -// Since all ethereum accounts implicitly exist we sometimes lazily create an Account object to represent them -// only when needed. Sometimes we need to create an unknown Account knowing only its address (which is expected to -// be a deterministic hash of its associated public key) and not its public key. When we eventually receive a -// transaction acting on behalf of that account we will be given a public key that we can check matches the address. -// If it does then we will associate the public key with the stub account already registered in the system once and -// for all time. -func checkInputPubKey(acc *acm.Account, in *txs.TxInput) error { - if acc.PubKey == nil { - if in.PubKey == nil { - return txs.ErrTxUnknownPubKey - } - if !bytes.Equal(in.PubKey.Address(), acc.Address) { - return txs.ErrTxInvalidPubKey - } - acc.PubKey = in.PubKey - } else { - in.PubKey = nil - } - return nil +func (exe *executor) RemoveAccount(address acm.Address) error { + return exe.blockCache.RemoveAccount(address) } -func validateInputs(accounts map[string]*acm.Account, signBytes []byte, ins []*txs.TxInput) (total int64, err error) { - for _, in := range ins { - acc := accounts[string(in.Address)] - if acc == nil { - sanity.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 (exe *executor) IterateAccounts(consumer func(acm.Account) bool) (bool, error) { + return exe.blockCache.IterateAccounts(consumer) } -func validateInput(acc *acm.Account, signBytes []byte, in *txs.TxInput) (err error) { - // Check TxInput basic - if err := in.ValidateBasic(); err != nil { - return err - } - // Check signatures - if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { - return txs.ErrTxInvalidSignature - } - // Check sequences - if acc.Sequence+1 != in.Sequence { - return txs.ErrTxInvalidSequence{ - Got: in.Sequence, - Expected: acc.Sequence + 1, - } - } - // Check amount - if acc.Balance < in.Amount { - return txs.ErrTxInsufficientFunds - } - return nil +// Storage +func (exe *executor) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + return exe.blockCache.GetStorage(address, key) } -func validateOutputs(outs []*txs.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 (exe *executor) SetStorage(address acm.Address, key binary.Word256, value binary.Word256) error { + return exe.blockCache.SetStorage(address, key, value) } -func adjustByInputs(accounts map[string]*acm.Account, ins []*txs.TxInput) { - for _, in := range ins { - acc := accounts[string(in.Address)] - if acc == nil { - sanity.PanicSanity("adjustByInputs() expects account in accounts") - } - if acc.Balance < in.Amount { - sanity.PanicSanity("adjustByInputs() expects sufficient funds") - } - acc.Balance -= in.Amount +func (exe *executor) IterateStorage(address acm.Address, consumer func(key, value binary.Word256) bool) (bool, error) { + return exe.blockCache.IterateStorage(address, consumer) +} - acc.Sequence += 1 - } +func (exe *executor) Commit() ([]byte, error) { + exe.mtx.Lock() + defer exe.mtx.Unlock() + // sync the cache + exe.blockCache.Sync() + // save state to disk + exe.state.Save() + // flush events to listeners (XXX: note issue with blocking) + exe.eventCache.Flush() + return exe.state.Hash(), nil } -func adjustByOutputs(accounts map[string]*acm.Account, outs []*txs.TxOutput) { - for _, out := range outs { - acc := accounts[string(out.Address)] - if acc == nil { - sanity.PanicSanity("adjustByOutputs() expects account in accounts") - } - acc.Balance += out.Amount - } +func (exe *executor) Reset() error { + exe.blockCache = NewBlockCache(exe.state) + exe.eventCache = event.NewEventCache(exe.fireable) + return nil } // If the tx is invalid, an error will be returned. // Unlike ExecBlock(), state will not be altered. -func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable, - logger logging_types.InfoTraceLogger) (err error) { - - logger = logging.WithScope(logger, "ExecTx") +func (exe *executor) Execute(tx txs.Tx) error { + logger := logging.WithScope(exe.logger, "executor.Execute(tx txs.Tx)") // TODO: do something with fees - fees := int64(0) - _s := blockCache.State() // hack to access validators and block height + fees := uint64(0) // Exec tx switch tx := tx.(type) { case *txs.SendTx: - accounts, err := getInputs(blockCache, tx.Inputs) + accounts, err := getInputs(exe.blockCache, tx.Inputs) if err != nil { return err } // ensure all inputs have send permissions - if !hasSendPermission(blockCache, accounts, logger) { - return fmt.Errorf("At least one input lacks permission for SendTx") + if !hasSendPermission(exe.blockCache, accounts, logger) { + 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, logger) + accounts, err = getOrMakeOutputs(exe.blockCache, accounts, tx.Outputs, logger) if err != nil { return err } - signBytes := acm.SignBytes(_s.ChainID, tx) + signBytes := acm.SignBytes(exe.chainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err @@ -362,43 +191,55 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable fees += fee // Good! Adjust accounts - adjustByInputs(accounts, tx.Inputs) - adjustByOutputs(accounts, tx.Outputs) + err = adjustByInputs(accounts, tx.Inputs) + if err != nil { + return err + } + + err = adjustByOutputs(accounts, tx.Outputs) + if err != nil { + return err + } + for _, acc := range accounts { - blockCache.UpdateAccount(acc) + exe.blockCache.UpdateAccount(acc) } - // if the evc is nil, nothing will happen - if evc != nil { + // if the exe.eventCache is nil, nothing will happen + if exe.eventCache != nil { for _, i := range tx.Inputs { - evc.FireEvent(txs.EventStringAccInput(i.Address), txs.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringAccInput(i.Address), events.EventDataTx{tx, nil, ""}) } for _, o := range tx.Outputs { - evc.FireEvent(txs.EventStringAccOutput(o.Address), txs.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringAccOutput(o.Address), events.EventDataTx{tx, nil, ""}) } } return nil case *txs.CallTx: - var inAcc, outAcc *acm.Account + var inAcc acm.MutableAccount + var outAcc acm.Account // Validate input - inAcc = blockCache.GetAccount(tx.Input.Address) + inAcc, err := acm.GetMutableAccount(exe.blockCache, tx.Input.Address) + if err != nil { + return err + } if inAcc == nil { logging.InfoMsg(logger, "Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } - createContract := len(tx.Address) == 0 + createContract := tx.Address == nil if createContract { - if !hasCreateContractPermission(blockCache, inAcc, logger) { - return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address) + if !hasCreateContractPermission(exe.blockCache, inAcc, logger) { + return fmt.Errorf("account %s does not have CreateContract permission", tx.Input.Address) } } else { - if !hasCallPermission(blockCache, inAcc, logger) { - return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) + if !hasCallPermission(exe.blockCache, inAcc, logger) { + return fmt.Errorf("account %s does not have Call permission", tx.Input.Address) } } @@ -408,8 +249,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "tx_input", tx.Input) return err } - signBytes := acm.SignBytes(_s.ChainID, tx) - err := validateInput(inAcc, signBytes, tx.Input) + signBytes := acm.SignBytes(exe.chainID, tx) + err = validateInput(inAcc, signBytes, tx.Input) if err != nil { logging.InfoMsg(logger, "validateInput failed", "tx_input", tx.Input, "error", err) @@ -422,24 +263,22 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } if !createContract { - // Validate output - if len(tx.Address) != 20 { - logging.InfoMsg(logger, "Destination address is not 20 bytes", - "address", tx.Address) - return txs.ErrTxInvalidAddress - } // check if its a native contract - if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) { - return fmt.Errorf("Attempt to call a native contract at %X, "+ + if evm.RegisteredNativeContract(tx.Address.Word256()) { + return fmt.Errorf("attempt to call a native contract at %s, "+ "but native contracts cannot be called using CallTx. Use a "+ "contract that calls the native contract or the appropriate tx "+ - "type (eg. PermissionsTx, NameTx).", tx.Address) + "type (eg. PermissionsTx, NameTx)", tx.Address) } // 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) + // Note: tx.Address == nil iff createContract so dereference is okay + outAcc, err = exe.blockCache.GetAccount(*tx.Address) + if err != nil { + return err + } } logger.Trace("output_account", outAcc) @@ -447,35 +286,43 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! value := tx.Input.Amount - tx.Fee - inAcc.Sequence += 1 - inAcc.Balance -= tx.Fee - blockCache.UpdateAccount(inAcc) + logging.TraceMsg(logger, "Incrementing sequence number", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + + inAcc, err = inAcc.IncSequence().SubtractFromBalance(tx.Fee) + if err != nil { + return err + } + + exe.blockCache.UpdateAccount(inAcc) // The logic in runCall MUST NOT return. - if runCall { + if exe.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(), + gas uint64 = tx.GasLimit + err error = nil + caller acm.MutableAccount = acm.AsMutableAccount(inAcc) + callee acm.MutableAccount = nil // initialized below + code []byte = nil + ret []byte = nil + txCache = NewTxCache(exe.blockCache) + params = evm.Params{ + BlockHeight: exe.tip.LastBlockHeight(), + BlockHash: binary.LeftPadWord256(exe.tip.LastBlockHash()), + BlockTime: exe.tip.LastBlockTime().Unix(), + GasLimit: GasLimit, } ) - if !createContract && (outAcc == nil || len(outAcc.Code) == 0) { + 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) + // block (sequence number 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 @@ -495,28 +342,28 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // get or create callee if createContract { // We already checked for permission - callee = txCache.CreateAccount(caller) + callee = evm.DeriveNewAccount(caller, permission.GlobalAccountPermissions(exe.state)) logging.TraceMsg(logger, "Created new contract", "contract_address", callee.Address, "contract_code", callee.Code) code = tx.Data } else { - callee = toVMAccount(outAcc) + callee = acm.AsMutableAccount(outAcc) logging.TraceMsg(logger, "Calling existing contract", "contract_address", callee.Address, "contract_code", callee.Code) - code = callee.Code + code = callee.Code() } - logger.Trace("callee_") + logger.Trace("callee", callee.Address().String()) - // Run VM call and sync txCache to blockCache. + // Run VM call and sync txCache to exe.blockCache. { // Capture scope for goto. // Write caller/callee to txCache. txCache.UpdateAccount(caller) txCache.UpdateAccount(callee) - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, txs.TxHash(_s.ChainID, tx)) - vmach.SetFireable(evc) + vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), + txs.TxHash(exe.chainID, tx), logger) + vmach.SetFireable(exe.eventCache) // 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 { @@ -528,9 +375,9 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable logging.TraceMsg(logger, "Successful execution") if createContract { - callee.Code = ret + callee.SetCode(ret) } - txCache.Sync() + txCache.Sync(exe.blockCache) } CALL_COMPLETE: // err may or may not be nil. @@ -544,41 +391,55 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Fire Events for sender and receiver // a separate event will be fired from vm for each additional call - if evc != nil { + if exe.eventCache != nil { exception := "" if err != nil { exception = err.Error() } - evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, ret, exception}) - evc.FireEvent(txs.EventStringAccOutput(tx.Address), txs.EventDataTx{tx, ret, exception}) + exe.eventCache.Fire(events.EventStringAccInput(tx.Input.Address), + events.EventDataTx{tx, ret, exception}) + if tx.Address != nil { + exe.eventCache.Fire(events.EventStringAccOutput(*tx.Address), + events.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 + inAcc, err = inAcc.SubtractFromBalance(value) + if err != nil { + return err + } if createContract { - inAcc.Sequence += 1 // XXX ?! + // This is done by DeriveNewAccount when runCall == true + + logging.TraceMsg(logger, "Incrementing sequence number since creates contract", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + inAcc.IncSequence() } - blockCache.UpdateAccount(inAcc) + exe.blockCache.UpdateAccount(inAcc) } return nil case *txs.NameTx: - var inAcc *acm.Account - // Validate input - inAcc = blockCache.GetAccount(tx.Input.Address) + inAcc, err := acm.GetMutableAccount(exe.blockCache, tx.Input.Address) + if err != nil { + return err + } if inAcc == nil { logging.InfoMsg(logger, "Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } // check permission - if !hasNamePermission(blockCache, inAcc, logger) { - return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address) + if !hasNamePermission(exe.blockCache, inAcc, logger) { + return fmt.Errorf("account %s 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 { @@ -586,8 +447,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "tx_input", tx.Input) return err } - signBytes := acm.SignBytes(_s.ChainID, tx) - err := validateInput(inAcc, signBytes, tx.Input) + signBytes := acm.SignBytes(exe.chainID, tx) + err = validateInput(inAcc, signBytes, tx.Input) if err != nil { logging.InfoMsg(logger, "validateInput failed", "tx_input", tx.Input, "error", err) @@ -608,8 +469,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // let's say cost of a name for one block is len(data) + 32 costPerBlock := txs.NameCostPerBlock(txs.NameBaseCost(tx.Name, tx.Data)) - expiresIn := int(value / costPerBlock) - lastBlockHeight := _s.LastBlockHeight + expiresIn := value / uint64(costPerBlock) + lastBlockHeight := exe.tip.LastBlockHeight() logging.TraceMsg(logger, "New NameTx", "value", value, @@ -618,7 +479,7 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "last_block_height", lastBlockHeight) // check if the name exists - entry := blockCache.GetNameRegEntry(tx.Name) + entry := exe.blockCache.GetNameRegEntry(tx.Name) if entry != nil { var expired bool @@ -626,11 +487,9 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // if the entry already exists, and hasn't expired, we must be owner if entry.Expires > lastBlockHeight { // ensure we are owner - if !bytes.Equal(entry.Owner, tx.Input.Address) { - logging.InfoMsg(logger, "Sender is trying to update a name for which they are not an owner", - "sender_address", tx.Input.Address, - "name", tx.Name) - return txs.ErrTxPermissionDenied + if entry.Owner != tx.Input.Address { + return fmt.Errorf("permission denied: sender %s is trying to update a name (%s) for "+ + "which they are not an owner", tx.Input.Address, tx.Name) } } else { expired = true @@ -642,7 +501,7 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // (owners if not expired, anyone if expired) logging.TraceMsg(logger, "Removing NameReg entry (no value and empty data in tx requests this)", "name", entry.Name) - blockCache.RemoveNameRegEntry(entry.Name) + exe.blockCache.RemoveNameRegEntry(entry.Name) } else { // update the entry by bumping the expiry // and changing the data @@ -659,11 +518,11 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } else { // since the size of the data may have changed // we use the total amount of "credit" - oldCredit := int64(entry.Expires-lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data) + oldCredit := (entry.Expires - lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data) credit := oldCredit + value - expiresIn = int(credit / costPerBlock) + expiresIn = uint64(credit / costPerBlock) if expiresIn < txs.MinNameRegistrationPeriod { - return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) + return fmt.Errorf("names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } entry.Expires = lastBlockHeight + expiresIn logging.TraceMsg(logger, "Updated NameReg entry", @@ -674,14 +533,14 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "credit", credit) } entry.Data = tx.Data - blockCache.UpdateNameRegEntry(entry) + exe.blockCache.UpdateNameRegEntry(entry) } } else { if expiresIn < txs.MinNameRegistrationPeriod { return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } // entry does not exist, so create it - entry = &core_types.NameRegEntry{ + entry = &NameRegEntry{ Name: tx.Name, Owner: tx.Input.Address, Data: tx.Data, @@ -690,21 +549,24 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable logging.TraceMsg(logger, "Creating NameReg entry", "name", entry.Name, "expires_in", expiresIn) - blockCache.UpdateNameRegEntry(entry) + exe.blockCache.UpdateNameRegEntry(entry) } // TODO: something with the value sent? // Good! - inAcc.Sequence += 1 - inAcc.Balance -= value - blockCache.UpdateAccount(inAcc) + inAcc.IncSequence() + inAcc, err = inAcc.SubtractFromBalance(value) + if err != nil { + return err + } + exe.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(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, nil, ""}) - evc.FireEvent(txs.EventStringNameReg(tx.Name), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(events.EventStringAccInput(tx.Input.Address), events.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringNameReg(tx.Name), events.EventDataTx{tx, nil, ""}) } return nil @@ -713,14 +575,14 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // TODO! /* case *txs.BondTx: - valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) + valInfo := exe.blockCache.State().GetValidatorInfo(tx.PublicKey().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) + accounts, err := getInputs(exe.blockCache, tx.Inputs) if err != nil { return err } @@ -728,29 +590,29 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // 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) + canCreate := hasCreateAccountPermission(exe.blockCache, accounts) for _, out := range tx.UnbondTo { - acc := blockCache.GetAccount(out.Address) + acc := exe.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) { + bondAcc := exe.blockCache.GetAccount(tx.PublicKey().Address()) + if !hasBondPermission(exe.blockCache, bondAcc) { return fmt.Errorf("The bonder does not have permission to bond") } - if !hasBondOrSendPermission(blockCache, accounts) { + if !hasBondOrSendPermission(exe.blockCache, accounts) { return fmt.Errorf("At least one input lacks permission to bond") } - signBytes := acm.SignBytes(_s.ChainID, tx) + signBytes := acm.SignBytes(exe.chainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err } - if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) { + if !tx.PublicKey().VerifyBytes(signBytes, tx.Signature) { return txs.ErrTxInvalidSignature } outTotal, err := validateOutputs(tx.UnbondTo) @@ -766,30 +628,30 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! Adjust accounts adjustByInputs(accounts, tx.Inputs) for _, acc := range accounts { - blockCache.UpdateAccount(acc) + exe.blockCache.UpdateAccount(acc) } // Add ValidatorInfo _s.SetValidatorInfo(&txs.ValidatorInfo{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, + Address: tx.PublicKey().Address(), + PublicKey: tx.PublicKey(), UnbondTo: tx.UnbondTo, - FirstBondHeight: _s.LastBlockHeight + 1, + FirstBondHeight: _s.lastBlockHeight + 1, FirstBondAmount: outTotal, }) // Add Validator added := _s.BondedValidators.Add(&txs.Validator{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, - BondHeight: _s.LastBlockHeight + 1, + Address: tx.PublicKey().Address(), + PublicKey: tx.PublicKey(), + BondHeight: _s.lastBlockHeight + 1, VotingPower: outTotal, Accum: 0, }) if !added { PanicCrisis("Failed to add validator") } - if evc != nil { + if exe.eventCache != nil { // TODO: fire for all inputs - evc.FireEvent(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""}) } return nil @@ -801,8 +663,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // Verify the signature - signBytes := acm.SignBytes(_s.ChainID, tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + signBytes := acm.SignBytes(exe.chainID, tx) + if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) { return txs.ErrTxInvalidSignature } @@ -813,8 +675,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! _s.unbondValidator(val) - if evc != nil { - evc.FireEvent(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""}) } return nil @@ -826,14 +688,14 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // Verify the signature - signBytes := acm.SignBytes(_s.ChainID, tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + signBytes := acm.SignBytes(exe.chainID, tx) + if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) { return txs.ErrTxInvalidSignature } // tx.Height must be in a suitable range - minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2) - maxRebondHeight := _s.LastBlockHeight + 2 + 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)) @@ -841,66 +703,30 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! _s.rebondValidator(val) - if evc != nil { - evc.FireEvent(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""}) } return nil - case *txs.DupeoutTx: - // Verify the signatures - _, accused := _s.BondedValidators.GetByAddress(tx.Address) - if accused == nil { - _, accused = _s.UnbondingValidators.GetByAddress(tx.Address) - if accused == nil { - return txs.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 txs.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(txs.EventStringDupeout(), txs.EventDataTx{tx, nil, ""}) - } - return nil */ case *txs.PermissionsTx: - var inAcc *acm.Account - // Validate input - inAcc = blockCache.GetAccount(tx.Input.Address) + inAcc, err := acm.GetMutableAccount(exe.blockCache, tx.Input.Address) + if err != nil { + return err + } if inAcc == nil { logging.InfoMsg(logger, "Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } - permFlag := tx.PermArgs.PermFlag() + permFlag := tx.PermArgs.PermFlag // check permission - if !HasPermission(blockCache, inAcc, permFlag, logger) { - return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag) + if !HasPermission(exe.blockCache, inAcc, permFlag, logger) { + return fmt.Errorf("account %s does not have moderator permission %s (%b)", tx.Input.Address, + permission.PermFlagToString(permFlag), permFlag) } // pubKey should be present in either "inAcc" or "tx.Input" @@ -909,8 +735,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "tx_input", tx.Input) return err } - signBytes := acm.SignBytes(_s.ChainID, tx) - err := validateInput(inAcc, signBytes, tx.Input) + signBytes := acm.SignBytes(exe.chainID, tx) + err = validateInput(inAcc, signBytes, tx.Input) if err != nil { logging.InfoMsg(logger, "validateInput failed", "tx_input", tx.Input, @@ -921,47 +747,49 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable value := tx.Input.Amount logging.TraceMsg(logger, "New PermissionsTx", - "perm_flag", ptypes.PermFlagToString(permFlag), + "perm_flag", permission.PermFlagToString(permFlag), "perm_args", tx.PermArgs) - var permAcc *acm.Account - switch args := tx.PermArgs.(type) { - case *ptypes.HasBaseArgs: + var permAcc acm.Account + switch tx.PermArgs.PermFlag { + case permission.HasBase: // 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 { - sanity.PanicSanity("can't find global permissions account") - } - err = permAcc.Permissions.Base.Set(args.Permission, args.Value) - case *ptypes.HasRoleArgs: + case permission.SetBase: + permAcc, err = mutatePermissions(exe.blockCache, tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Set(tx.PermArgs.Permission, tx.PermArgs.Value) + }) + case permission.UnsetBase: + permAcc, err = mutatePermissions(exe.blockCache, tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Unset(tx.PermArgs.Permission) + }) + case permission.SetGlobal: + permAcc, err = mutatePermissions(exe.blockCache, permission.GlobalPermissionsAddress, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Set(tx.PermArgs.Permission, tx.PermArgs.Value) + }) + case permission.HasRole: 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) - } + case permission.AddRole: + permAcc, err = mutatePermissions(exe.blockCache, tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + if !perms.AddRole(tx.PermArgs.Role) { + return fmt.Errorf("role (%s) already exists for account %s", tx.PermArgs.Role, tx.PermArgs.Address) + } + return nil + }) + case permission.RemoveRole: + permAcc, err = mutatePermissions(exe.blockCache, tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + if !perms.RmRole(tx.PermArgs.Role) { + return fmt.Errorf("role (%s) does not exist for account %s", tx.PermArgs.Role, tx.PermArgs.Address) + } + return nil + }) default: - sanity.PanicSanity(fmt.Sprintf("invalid permission function: %s", ptypes.PermFlagToString(permFlag))) + return fmt.Errorf("invalid permission function: %s", permission.PermFlagToString(permFlag)) } // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? @@ -970,34 +798,358 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // Good! - inAcc.Sequence += 1 - inAcc.Balance -= value - blockCache.UpdateAccount(inAcc) + inAcc.IncSequence() + inAcc, err = inAcc.SubtractFromBalance(value) + if err != nil { + return err + } + exe.blockCache.UpdateAccount(inAcc) if permAcc != nil { - blockCache.UpdateAccount(permAcc) + exe.blockCache.UpdateAccount(permAcc) } - if evc != nil { - evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, nil, ""}) - evc.FireEvent(txs.EventStringPermissions(ptypes.PermFlagToString(permFlag)), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(events.EventStringAccInput(tx.Input.Address), + events.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringPermissions(permission.PermFlagToString(permFlag)), + events.EventDataTx{tx, nil, ""}) } return nil default: // binary decoding should not let this happen - sanity.PanicSanity("Unknown Tx type") - return nil + return fmt.Errorf("unknown Tx type: %#v", tx) + } +} + +func mutatePermissions(stateReader acm.StateReader, address acm.Address, + mutator func(*ptypes.AccountPermissions) error) (acm.Account, error) { + + account, err := stateReader.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("could not get account at address %s in order to alter permissions", address) + } + mutableAccount := acm.AsMutableAccount(account) + + return mutableAccount, mutator(mutableAccount.MutablePermissions()) +} + +// ExecBlock stuff is now taken care of by the consensus engine. +// But we leave here for now for reference when we have to do validator updates + +/* + +// NOTE: If an error occurs during block execution, state will be left +// at an invalid state. Copy the state before calling ExecBlock! +func ExecBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { + err := execBlock(s, block, blockPartsHeader) + if err != nil { + return err + } + // State.Hash should match block.StateHash + stateHash := s.Hash() + if !bytes.Equal(stateHash, block.StateHash) { + return errors.New(Fmt("Invalid state hash. Expected %X, got %X", + stateHash, block.StateHash)) } + return nil +} + +// executes transactions of a block, does not check block.StateHash +// NOTE: If an error occurs during block execution, state will be left +// at an invalid state. Copy the state before calling execBlock! +func execBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) error { + // Basic block validation. + err := block.ValidateBasic(s.chainID, s.lastBlockHeight, s.lastBlockAppHash, s.LastBlockParts, s.lastBlockTime) + if err != nil { + return err + } + + // Validate block LastValidation. + if block.Height == 1 { + if len(block.LastValidation.Precommits) != 0 { + return errors.New("Block at height 1 (first block) should have no LastValidation precommits") + } + } else { + if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() { + return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", + s.LastBondedValidators.Size(), len(block.LastValidation.Precommits))) + } + err := s.LastBondedValidators.VerifyValidation( + s.chainID, s.lastBlockAppHash, s.LastBlockParts, block.Height-1, block.LastValidation) + if err != nil { + return err + } + } + + // Update Validator.LastCommitHeight as necessary. + for i, precommit := range block.LastValidation.Precommits { + if precommit == nil { + continue + } + _, val := s.LastBondedValidators.GetByIndex(i) + if val == nil { + PanicCrisis(Fmt("Failed to fetch validator at index %v", i)) + } + if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { + val_.LastCommitHeight = block.Height - 1 + updated := s.BondedValidators.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") + } + } else { + PanicCrisis("Could not find validator") + } + } + + // Remember LastBondedValidators + s.LastBondedValidators = s.BondedValidators.Copy() + + // Create BlockCache to cache changes to state. + blockCache := NewBlockCache(s) + + // Execute each tx + for _, tx := range block.Data.Txs { + err := ExecTx(blockCache, tx, true, s.eventCache) + 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 := []*txs.Validator{} + s.UnbondingValidators.Iterate(func(index int, val *txs.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 := []*txs.Validator{} + s.BondedValidators.Iterate(func(index int, val *txs.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.lastBlockHeight = block.Height + s.lastBlockAppHash = block.Hash() + s.LastBlockParts = blockPartsHeader + s.lastBlockTime = block.Time + return nil +} +*/ + +// The accounts from the TxInputs must either already have +// acm.PublicKey().(type) != nil, (it must be known), +// or it must be specified in the TxInput. If redeclared, +// the TxInput is modified and input.PublicKey() set to nil. +func getInputs(accountGetter acm.Getter, + ins []*txs.TxInput) (map[acm.Address]acm.MutableAccount, error) { + + accounts := map[acm.Address]acm.MutableAccount{} + for _, in := range ins { + // Account shouldn't be duplicated + if _, ok := accounts[in.Address]; ok { + return nil, txs.ErrTxDuplicateAddress + } + acc, err := acm.GetMutableAccount(accountGetter, in.Address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, txs.ErrTxInvalidAddress + } + // PublicKey should be present in either "account" or "in" + if err := checkInputPubKey(acc, in); err != nil { + return nil, err + } + accounts[in.Address] = acc + } + return accounts, nil +} + +func getOrMakeOutputs(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, + outs []*txs.TxOutput, logger logging_types.InfoTraceLogger) (map[acm.Address]acm.MutableAccount, error) { + if accs == nil { + accs = make(map[acm.Address]acm.MutableAccount) + } + + // 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 := accs[out.Address]; ok { + return nil, txs.ErrTxDuplicateAddress + } + acc, err := acm.GetMutableAccount(accountGetter, out.Address) + if err != nil { + return nil, err + } + // output account may be nil (new) + if acc == nil { + if !checkedCreatePerms { + if !hasCreateAccountPermission(accountGetter, accs, logger) { + return nil, fmt.Errorf("at least one input does not have permission to create accounts") + } + checkedCreatePerms = true + } + acc = acm.ConcreteAccount{ + Address: out.Address, + Sequence: 0, + Balance: 0, + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + } + accs[out.Address] = acc + } + return accs, nil +} + +// Since all ethereum accounts implicitly exist we sometimes lazily create an Account object to represent them +// only when needed. Sometimes we need to create an unknown Account knowing only its address (which is expected to +// be a deterministic hash of its associated public key) and not its public key. When we eventually receive a +// transaction acting on behalf of that account we will be given a public key that we can check matches the address. +// If it does then we will associate the public key with the stub account already registered in the system once and +// for all time. +func checkInputPubKey(acc acm.MutableAccount, in *txs.TxInput) error { + if acc.PublicKey().Unwrap() == nil { + if in.PubKey.Unwrap() == nil { + return txs.ErrTxUnknownPubKey + } + addressFromPubKey := in.PubKey.Address() + addressFromAccount := acc.Address() + if addressFromPubKey != addressFromAccount { + return txs.ErrTxInvalidPubKey + } + acc.SetPublicKey(in.PubKey) + } else { + in.PubKey = acm.PublicKey{} + } + return nil +} + +func validateInputs(accs map[acm.Address]acm.MutableAccount, signBytes []byte, ins []*txs.TxInput) (uint64, error) { + total := uint64(0) + for _, in := range ins { + acc := accs[in.Address] + if acc == nil { + return 0, fmt.Errorf("validateInputs() expects account in accounts, but account %s not found", in.Address) + } + err := validateInput(acc, signBytes, in) + if err != nil { + return 0, err + } + // Good. Add amount to total + total += in.Amount + } + return total, nil +} + +func validateInput(acc acm.MutableAccount, signBytes []byte, in *txs.TxInput) error { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check signatures + if !acc.PublicKey().VerifyBytes(signBytes, in.Signature) { + return txs.ErrTxInvalidSignature + } + // Check sequences + if acc.Sequence()+1 != uint64(in.Sequence) { + return txs.ErrTxInvalidSequence{ + Got: in.Sequence, + Expected: acc.Sequence() + uint64(1), + } + } + // Check amount + if acc.Balance() < uint64(in.Amount) { + return txs.ErrTxInsufficientFunds + } + return nil +} + +func validateOutputs(outs []*txs.TxOutput) (uint64, error) { + total := uint64(0) + 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(accs map[acm.Address]acm.MutableAccount, ins []*txs.TxInput) error { + for _, in := range ins { + acc := accs[in.Address] + if acc == nil { + return fmt.Errorf("adjustByInputs() expects account in accounts, but account %s not found", in.Address) + } + if acc.Balance() < in.Amount { + panic("adjustByInputs() expects sufficient funds") + return fmt.Errorf("adjustByInputs() expects sufficient funds but account %s only has balance %v and "+ + "we are deducting %v", in.Address, acc.Balance(), in.Amount) + } + acc, err := acc.SubtractFromBalance(in.Amount) + if err != nil { + return err + } + acc.IncSequence() + } + return nil +} + +func adjustByOutputs(accs map[acm.Address]acm.MutableAccount, outs []*txs.TxOutput) error { + for _, out := range outs { + acc := accs[out.Address] + if acc == nil { + return fmt.Errorf("adjustByOutputs() expects account in accounts, but account %s not found", + out.Address) + } + _, err := acc.AddToBalance(out.Amount) + if err != nil { + return err + } + } + return nil } //--------------------------------------------------------------- // Get permission on an account or fall back to global value -func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, - logger logging_types.InfoTraceLogger) bool { - if perm > ptypes.AllPermFlags { - sanity.PanicSanity("Checking an unknown permission in state should never happen") +func HasPermission(accountGetter acm.Getter, acc acm.Account, perm ptypes.PermFlag, logger logging_types.InfoTraceLogger) bool { + if perm > permission.AllPermFlags { + panic("Checking an unknown permission in state should never happen") } //if acc == nil { @@ -1005,16 +1157,17 @@ func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, // 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) + permString := permission.PermFlagToString(perm) - v, err := acc.Permissions.Base.Get(perm) + v, err := acc.Permissions().Base.Get(perm) if _, ok := err.(ptypes.ErrValueNotSet); ok { - if state == nil { - sanity.PanicSanity("All known global permissions should be set!") + if accountGetter == nil { + panic("All known global permissions should be set!") } logging.TraceMsg(logger, "Permission for account is not set. Querying GlobalPermissionsAddres.", "perm_flag", permString) - return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm, logger) + + return HasPermission(nil, permission.GlobalPermissionsAccount(accountGetter), perm, logger) } else if v { logging.TraceMsg(logger, "Account has permission", "account_address", acc.Address, @@ -1028,51 +1181,51 @@ func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, } // TODO: for debug log the failed accounts -func hasSendPermission(state AccountGetter, accs map[string]*acm.Account, +func hasSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.Send, logger) { + if !HasPermission(accountGetter, acc, permission.Send, logger) { return false } } return true } -func hasNamePermission(state AccountGetter, acc *acm.Account, +func hasNamePermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.Name, logger) + return HasPermission(accountGetter, acc, permission.Name, logger) } -func hasCallPermission(state AccountGetter, acc *acm.Account, +func hasCallPermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.Call, logger) + return HasPermission(accountGetter, acc, permission.Call, logger) } -func hasCreateContractPermission(state AccountGetter, acc *acm.Account, +func hasCreateContractPermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.CreateContract, logger) + return HasPermission(accountGetter, acc, permission.CreateContract, logger) } -func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account, +func hasCreateAccountPermission(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.CreateAccount, logger) { + if !HasPermission(accountGetter, acc, permission.CreateAccount, logger) { return false } } return true } -func hasBondPermission(state AccountGetter, acc *acm.Account, +func hasBondPermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.Bond, logger) + return HasPermission(accountGetter, acc, permission.Bond, logger) } -func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account, +func hasBondOrSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.Account, logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.Bond, logger) { - if !HasPermission(state, acc, ptypes.Send, logger) { + if !HasPermission(accountGetter, acc, permission.Bond, logger) { + if !HasPermission(accountGetter, acc, permission.Send, logger) { return false } } diff --git a/execution/execution_test.go b/execution/execution_test.go new file mode 100644 index 0000000000000000000000000000000000000000..68e4d96bde3cdefff977fa592d3e8e551ced10ae --- /dev/null +++ b/execution/execution_test.go @@ -0,0 +1,1329 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package execution + +import ( + "bytes" + "fmt" + "strconv" + "testing" + "time" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/evm" + . "github.com/hyperledger/burrow/execution/evm/asm" + "github.com/hyperledger/burrow/execution/evm/asm/bc" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/txs" + dbm "github.com/tendermint/tmlibs/db" +) + +var ( + dbBackend = "memdb" + dbDir = "" + permissionsContract = evm.SNativeContracts()["Permissions"] +) + +/* +Permission Tests: + +- SendTx: +x - 1 input, no perm, call perm, create perm +x - 1 input, perm +x - 2 inputs, one with perm one without + +- CallTx, CALL +x - 1 input, no perm, send perm, create perm +x - 1 input, perm +x - contract runs call but doesn't have call perm +x - contract runs call and has call perm +x - contract runs call (with perm), runs contract that runs call (without perm) +x - contract runs call (with perm), runs contract that runs call (with perm) + +- CallTx for Create, CREATE +x - 1 input, no perm, send perm, call perm +x - 1 input, perm +x - contract runs create but doesn't have create perm +x - contract runs create but has perm +x - contract runs call with empty address (has call and create perm) + +- NameTx + - no perm, send perm, call perm + - with perm + +- BondTx +x - 1 input, no perm +x - 1 input, perm +x - 1 bonder with perm, input without send or bond +x - 1 bonder with perm, input with send +x - 1 bonder with perm, input with bond +x - 2 inputs, one with perm one without + +- SendTx for new account +x - 1 input, 1 unknown ouput, input with send, not create (fail) +x - 1 input, 1 unknown ouput, input with send and create (pass) +x - 2 inputs, 1 unknown ouput, both inputs with send, one with create, one without (fail) +x - 2 inputs, 1 known output, 1 unknown ouput, one input with create, one without (fail) +x - 2 inputs, 1 unknown ouput, both inputs with send, both inputs with create (pass ) +x - 2 inputs, 1 known output, 1 unknown ouput, both inputs with create, (pass) + + +- CALL for new account +x - unknown output, without create (fail) +x - unknown output, with create (pass) + + +- SNative (CallTx, CALL): + - for each of CallTx, Call +x - call each snative without permission, fails +x - call each snative with permission, pass + - list: +x - base: has,set,unset +x - globals: set +x - roles: has, add, rm + + +*/ + +// keys +var users = makeUsers(10) +var logger = loggers.NewNoopInfoTraceLogger() + +func makeUsers(n int) []acm.PrivateAccount { + users := make([]acm.PrivateAccount, n) + for i := 0; i < n; i++ { + secret := "mysecret" + strconv.Itoa(i) + users[i] = acm.GeneratePrivateAccountFromSecret(secret) + } + return users +} + +func makeExecutor(state *State) *executor { + return newExecutor(true, state, testChainID, bcm.NewBlockchain(testGenesisDoc), event.NewEmitter(logger), + logger) +} + +func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.GenesisDoc { + genAccounts := []genesis.Account{} + for _, user := range users[:5] { + accountPermCopy := accountPerm // Create new instance for custom overridability. + genAccounts = append(genAccounts, genesis.Account{ + BasicAccount: genesis.BasicAccount{ + Address: user.Address(), + Amount: 1000000, + }, + Permissions: accountPermCopy, + }) + } + + return genesis.GenesisDoc{ + GenesisTime: time.Now(), + ChainName: testGenesisDoc.ChainName, + GlobalPermissions: globalPerm, + Accounts: genAccounts, + Validators: []genesis.Validator{ + { + BasicAccount: genesis.BasicAccount{ + PublicKey: users[0].PublicKey(), + Amount: 10, + }, + UnbondTo: []genesis.BasicAccount{ + { + Address: users[0].Address(), + }, + }, + }, + }, + } +} + +//func getAccount(state acm.Getter, address acm.Address) acm.MutableAccount { +// acc, _ := acm.GetMutableAccount(state, address) +// return acc +//} + +func TestSendFails(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[1].Permissions.Base.Set(permission.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(permission.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(permission.CreateContract, true) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------- + // send txs + + // simple send tx should fail + tx := txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx with call perm should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[4].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx with create perm should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[3].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[4].Address(), 5) + tx.SignInput(testChainID, 0, users[3]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx to unknown account without create_account perm should fail + acc := getAccount(batchCommitter.blockCache, users[3].Address()) + acc.MutablePermissions().Base.Set(permission.Send, true) + batchCommitter.blockCache.UpdateAccount(acc) + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[3].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[6].Address(), 5) + tx.SignInput(testChainID, 0, users[3]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestName(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) + genDoc.Accounts[1].Permissions.Base.Set(permission.Name, true) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------- + // name txs + + // simple name tx without perm should fail + tx, err := txs.NewNameTx(st, users[0].PublicKey(), "somename", "somedata", 10000, 100) + if err != nil { + t.Fatal(err) + } + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple name tx with perm should pass + tx, err = txs.NewNameTx(st, users[1].PublicKey(), "somename", "somedata", 10000, 100) + if err != nil { + t.Fatal(err) + } + tx.Sign(testChainID, users[1]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal(err) + } +} + +func TestCallFails(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[1].Permissions.Base.Set(permission.Send, true) + genDoc.Accounts[2].Permissions.Base.Set(permission.Call, true) + genDoc.Accounts[3].Permissions.Base.Set(permission.CreateContract, true) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------- + // call txs + + address4 := users[4].Address() + // simple call tx should fail + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &address4, nil, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call tx with send permission should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[1].PublicKey(), &address4, nil, 100, 100, 100) + tx.Sign(testChainID, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call tx with create permission should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[3].PublicKey(), &address4, nil, 100, 100, 100) + tx.Sign(testChainID, users[3]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + //------------------- + // create txs + + // simple call create tx should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), nil, nil, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call create tx with send perm should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[1].PublicKey(), nil, nil, 100, 100, 100) + tx.Sign(testChainID, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call create tx with call perm should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[2].PublicKey(), nil, nil, 100, 100, 100) + tx.Sign(testChainID, users[2]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestSendPermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + // A single input, having the permission, should succeed + tx := txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, one with permission, one without, should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[2].Address(), 10) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestCallPermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------------------ + // call to simple contract + fmt.Println("\n##### SIMPLE CONTRACT") + + // create simple contract + simpleContractAddr := acm.NewContractAddress(users[0].Address(), 100) + simpleAcc := acm.ConcreteAccount{ + Address: simpleContractAddr, + Balance: 0, + Code: []byte{0x60}, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + st.UpdateAccount(simpleAcc) + + // A single input, having the permission, should succeed + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &simpleContractAddr, nil, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + + //---------------------------------------------------------- + // call to contract that calls simple contract - without perm + fmt.Println("\n##### CALL TO SIMPLE CONTRACT (FAIL)") + + // create contract that calls the simple contract + contractCode := callContractCode(simpleContractAddr) + caller1ContractAddr := acm.NewContractAddress(users[0].Address(), 101) + caller1Acc := acm.ConcreteAccount{ + Address: caller1ContractAddr, + Balance: 10000, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + batchCommitter.blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // call to contract that calls simple contract - with perm + fmt.Println("\n##### CALL TO SIMPLE CONTRACT (PASS)") + + // A single input, having the permission, and the contract has permission + caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception:", exception) + } + + //---------------------------------------------------------- + // call to contract that calls contract that calls simple contract - without perm + // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. + // caller1Contract does not have call perms, but caller2Contract does. + fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") + + contractCode2 := callContractCode(caller1ContractAddr) + caller2ContractAddr := acm.NewContractAddress(users[0].Address(), 102) + caller2Acc := acm.ConcreteAccount{ + Address: caller2ContractAddr, + Balance: 1000, + Code: contractCode2, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + caller1Acc.MutablePermissions().Base.Set(permission.Call, false) + caller2Acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + batchCommitter.blockCache.UpdateAccount(caller2Acc) + + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // call to contract that calls contract that calls simple contract - without perm + // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. + // both caller1 and caller2 have permission + fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") + + caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } +} + +func TestCreatePermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.CreateContract, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------------------ + // create a simple contract + fmt.Println("\n##### CREATE SIMPLE CONTRACT") + + contractCode := []byte{0x60} + createCode := wrapContractForCreate(contractCode) + + // A single input, having the permission, should succeed + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), nil, createCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + // ensure the contract is there + contractAddr := acm.NewContractAddress(tx.Input.Address, tx.Input.Sequence) + contractAcc := getAccount(batchCommitter.blockCache, contractAddr) + if contractAcc == nil { + t.Fatalf("failed to create contract %s", contractAddr) + } + if !bytes.Equal(contractAcc.Code(), contractCode) { + t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code(), contractCode) + } + + //------------------------------ + // create contract that uses the CREATE op + fmt.Println("\n##### CREATE FACTORY") + + contractCode = []byte{0x60} + createCode = wrapContractForCreate(contractCode) + factoryCode := createContractCode() + createFactoryCode := wrapContractForCreate(factoryCode) + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), nil, createFactoryCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + // ensure the contract is there + contractAddr = acm.NewContractAddress(tx.Input.Address, tx.Input.Sequence) + contractAcc = getAccount(batchCommitter.blockCache, contractAddr) + if contractAcc == nil { + t.Fatalf("failed to create contract %s", contractAddr) + } + if !bytes.Equal(contractAcc.Code(), factoryCode) { + t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code(), factoryCode) + } + + //------------------------------ + // call the contract (should FAIL) + fmt.Println("\n###### CALL THE FACTORY (FAIL)") + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &contractAddr, createCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(contractAddr)) // + if exception == "" { + t.Fatal("expected exception") + } + + //------------------------------ + // call the contract (should PASS) + fmt.Println("\n###### CALL THE FACTORY (PASS)") + + contractAcc.MutablePermissions().Base.Set(permission.CreateContract, true) + batchCommitter.blockCache.UpdateAccount(contractAcc) + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &contractAddr, createCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(contractAddr)) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + + //-------------------------------- + fmt.Println("\n##### CALL to empty address") + code := callContractCode(acm.Address{}) + + contractAddr = acm.NewContractAddress(users[0].Address(), 110) + contractAcc = acm.ConcreteAccount{ + Address: contractAddr, + Balance: 1000, + Code: code, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + contractAcc.MutablePermissions().Base.Set(permission.Call, true) + contractAcc.MutablePermissions().Base.Set(permission.CreateContract, true) + batchCommitter.blockCache.UpdateAccount(contractAcc) + + // this should call the 0 address but not create ... + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &contractAddr, createCode, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(acm.Address{})) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + zeroAcc := getAccount(batchCommitter.blockCache, acm.Address{}) + if len(zeroAcc.Code()) != 0 { + t.Fatal("the zero account was given code from a CALL!") + } +} + +/* TODO +func TestBondPermission(t *testing.T) { + stateDB := dbm.NewDB("state",dbBackend,dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + var bondAcc *acm.Account + + //------------------------------ + // one bonder without permission should fail + tx, _ := txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[1]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + //------------------------------ + // one bonder with permission should pass + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input without send should fail + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input with send should pass + sendAcc := batchCommitter.blockCache.GetAccount(users[2].Address()) + sendAcc.Permissions.Base.Set(permission.Send, true) + batchCommitter.blockCache.UpdateAccount(sendAcc) + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input with bond should pass + sendAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(sendAcc) + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input from that bonder and an input without send or bond should fail + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[1]) + tx.SignInput(testChainID, 1, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } +} +*/ + +func TestCreateAccountPermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) // give the 0 account permission + genDoc.Accounts[1].Permissions.Base.Set(permission.Send, true) // give the 0 account permission + genDoc.Accounts[0].Permissions.Base.Set(permission.CreateAccount, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //---------------------------------------------------------- + // SendTx to unknown account + + // A single input, having the permission, should succeed + tx := txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[6].Address(), 5) + tx.SignInput(testChainID, 0, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, both with send, one with create, one without, should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 10) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, one with create, one without, two ouputs (one known, one unknown) should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 4) + tx.AddOutput(users[4].Address(), 6) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, both with create, should pass + acc := getAccount(batchCommitter.blockCache, users[1].Address()) + acc.MutablePermissions().Base.Set(permission.CreateAccount, true) + batchCommitter.blockCache.UpdateAccount(acc) + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 10) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Unexpected error", err) + } + + // Two inputs, both with send, both with create, two outputs (one known, one unknown) should pass + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 7) + tx.AddOutput(users[4].Address(), 3) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Unexpected error", err) + } + + //---------------------------------------------------------- + // CALL to unknown account + + acc = getAccount(batchCommitter.blockCache, users[0].Address()) + acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(acc) + + // call to contract that calls unknown account - without create_account perm + // create contract that calls the simple contract + contractCode := callContractCode(users[9].Address()) + caller1ContractAddr := acm.NewContractAddress(users[4].Address(), 101) + caller1Acc := acm.ConcreteAccount{ + Address: caller1ContractAddr, + Balance: 0, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + batchCommitter.blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + txCall, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, batchCommitter, txCall, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + // NOTE: for a contract to be able to CreateAccount, it must be able to call + // NOTE: for a users to be able to CreateAccount, it must be able to send! + caller1Acc.MutablePermissions().Base.Set(permission.CreateAccount, true) + caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + // A single input, having the permission, but the contract doesn't have permission + txCall, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, txCall, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + +} + +// holla at my boy +var DougAddress acm.Address + +func init() { + copy(DougAddress[:], ([]byte)("THISISDOUG")) +} + +func TestSNativeCALL(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + genDoc.Accounts[3].Permissions.Base.Set(permission.Bond, true) // some arbitrary permission to play with + genDoc.Accounts[3].Permissions.AddRole("bumble") + genDoc.Accounts[3].Permissions.AddRole("bee") + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //---------------------------------------------------------- + // Test CALL to SNative contracts + + // make the main contract once + doug := acm.ConcreteAccount{ + Address: DougAddress, + Balance: 0, + Code: nil, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + + doug.MutablePermissions().Base.Set(permission.Call, true) + //doug.Permissions.Base.Set(permission.HasBase, true) + batchCommitter.blockCache.UpdateAccount(doug) + + fmt.Println("\n#### HasBase") + // HasBase + snativeAddress, pF, data := snativePermTestInputCALL("hasBase", users[3], permission.Bond, false) + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### SetBase") + // SetBase + snativeAddress, pF, data = snativePermTestInputCALL("setBase", users[3], permission.Bond, false) + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.Bond, false) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + snativeAddress, pF, data = snativePermTestInputCALL("setBase", users[3], permission.CreateContract, true) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.CreateContract, false) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### UnsetBase") + // UnsetBase + snativeAddress, pF, data = snativePermTestInputCALL("unsetBase", users[3], permission.CreateContract, false) + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.CreateContract, false) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### SetGlobal") + // SetGlobalPerm + snativeAddress, pF, data = snativePermTestInputCALL("setGlobal", users[3], permission.CreateContract, true) + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", users[3], permission.CreateContract, false) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### HasRole") + // HasRole + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "bumble") + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### AddRole") + // AddRole + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + snativeAddress, pF, data = snativeRoleTestInputCALL("addRole", users[3], "chuck") + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### RemoveRole") + // RemoveRole + snativeAddress, pF, data = snativeRoleTestInputCALL("removeRole", users[3], "chuck") + testSNativeCALLExpectFail(t, batchCommitter, doug, snativeAddress, data) + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) +} + +func TestSNativeTx(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + genDoc.Accounts[3].Permissions.Base.Set(permission.Bond, true) // some arbitrary permission to play with + genDoc.Accounts[3].Permissions.AddRole("bumble") + genDoc.Accounts[3].Permissions.AddRole("bee") + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //---------------------------------------------------------- + // Test SNativeTx + + fmt.Println("\n#### SetBase") + // SetBase + snativeArgs := snativePermTestInputTx("setBase", users[3], permission.Bond, false) + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.SetBase, snativeArgs) + acc := getAccount(batchCommitter.blockCache, users[3].Address()) + if v, _ := acc.MutablePermissions().Base.Get(permission.Bond); v { + t.Fatal("expected permission to be set false") + } + snativeArgs = snativePermTestInputTx("setBase", users[3], permission.CreateContract, true) + testSNativeTxExpectPass(t, batchCommitter, permission.SetBase, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { + t.Fatal("expected permission to be set true") + } + + fmt.Println("\n#### UnsetBase") + // UnsetBase + snativeArgs = snativePermTestInputTx("unsetBase", users[3], permission.CreateContract, false) + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.UnsetBase, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); v { + t.Fatal("expected permission to be set false") + } + + fmt.Println("\n#### SetGlobal") + // SetGlobalPerm + snativeArgs = snativePermTestInputTx("setGlobal", users[3], permission.CreateContract, true) + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.SetGlobal, snativeArgs) + acc = getAccount(batchCommitter.blockCache, permission.GlobalPermissionsAddress) + if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { + t.Fatal("expected permission to be set true") + } + + fmt.Println("\n#### AddRole") + // AddRole + snativeArgs = snativeRoleTestInputTx("addRole", users[3], "chuck") + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.AddRole, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v := acc.Permissions().HasRole("chuck"); !v { + t.Fatal("expected role to be added") + } + + fmt.Println("\n#### RemoveRole") + // RemoveRole + snativeArgs = snativeRoleTestInputTx("removeRole", users[3], "chuck") + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.RemoveRole, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v := acc.Permissions().HasRole("chuck"); v { + t.Fatal("expected role to be removed") + } +} + +//------------------------------------------------------------------------------------- +// helpers + +var ExceptionTimeOut = "timed out waiting for event" + +// run ExecTx and wait for the Call event on given addr +// returns the msg data and an error/exception +func execTxWaitEvent(t *testing.T, batchCommitter *executor, tx txs.Tx, eventid string) (interface{}, string) { + evsw := event.NewEmitter(logger) + ch := make(chan event.AnyEventData) + evsw.Subscribe("test", eventid, func(msg event.AnyEventData) { + ch <- msg + }) + evc := event.NewEventCache(evsw) + batchCommitter.eventCache = evc + go func() { + if err := batchCommitter.Execute(tx); err != nil { + errStr := err.Error() + ch <- event.AnyEventData{Err: &errStr} + } + evc.Flush() + }() + ticker := time.NewTicker(5 * time.Second) + var msg event.AnyEventData + select { + case msg = <-ch: + case <-ticker.C: + return nil, ExceptionTimeOut + } + + switch ev := msg.Get().(type) { + case exe_events.EventDataTx: + return ev, ev.Exception + case evm_events.EventDataCall: + return ev, ev.Exception + case string: + return nil, ev + default: + return ev, "" + } +} + +// give a contract perms for an snative, call it, it calls the snative, but shouldn't have permission +func testSNativeCALLExpectFail(t *testing.T, batchCommitter *executor, doug acm.MutableAccount, + snativeAddress acm.Address, data []byte) { + testSNativeCALL(t, false, batchCommitter, doug, 0, snativeAddress, data, nil) +} + +// give a contract perms for an snative, call it, it calls the snative, ensure the check funciton (f) succeeds +func testSNativeCALLExpectPass(t *testing.T, batchCommitter *executor, doug acm.MutableAccount, snativePerm ptypes.PermFlag, + snativeAddress acm.Address, data []byte, f func([]byte) error) { + testSNativeCALL(t, true, batchCommitter, doug, snativePerm, snativeAddress, data, f) +} + +func testSNativeCALL(t *testing.T, expectPass bool, batchCommitter *executor, doug acm.MutableAccount, + snativePerm ptypes.PermFlag, snativeAddress acm.Address, data []byte, f func([]byte) error) { + if expectPass { + doug.MutablePermissions().Base.Set(snativePerm, true) + } + + doug.SetCode(callContractCode(snativeAddress)) + dougAddress := doug.Address() + + batchCommitter.blockCache.UpdateAccount(doug) + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &dougAddress, data, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + fmt.Println("subscribing to", evm_events.EventStringAccCall(snativeAddress)) + ev, exception := execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(snativeAddress)) + if exception == ExceptionTimeOut { + t.Fatal("Timed out waiting for event") + } + if expectPass { + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + evv := ev.(evm_events.EventDataCall) + ret := evv.Return + if err := f(ret); err != nil { + t.Fatal(err) + } + } else { + if exception == "" { + t.Fatal("Expected exception") + } + } +} + +func testSNativeTxExpectFail(t *testing.T, batchCommitter *executor, snativeArgs permission.PermArgs) { + testSNativeTx(t, false, batchCommitter, 0, snativeArgs) +} + +func testSNativeTxExpectPass(t *testing.T, batchCommitter *executor, perm ptypes.PermFlag, snativeArgs permission.PermArgs) { + testSNativeTx(t, true, batchCommitter, perm, snativeArgs) +} + +func testSNativeTx(t *testing.T, expectPass bool, batchCommitter *executor, perm ptypes.PermFlag, snativeArgs permission.PermArgs) { + if expectPass { + acc := getAccount(batchCommitter.blockCache, users[0].Address()) + acc.MutablePermissions().Base.Set(perm, true) + batchCommitter.blockCache.UpdateAccount(acc) + } + tx, _ := txs.NewPermissionsTx(batchCommitter.blockCache, users[0].PublicKey(), &snativeArgs) + tx.Sign(testChainID, users[0]) + err := batchCommitter.Execute(tx) + if expectPass { + if err != nil { + t.Fatal("Unexpected exception", err) + } + } else { + if err == nil { + t.Fatal("Expected exception") + } + } +} + +func boolToWord256(v bool) Word256 { + var vint byte + if v { + vint = 0x1 + } else { + vint = 0x0 + } + return LeftPadWord256([]byte{vint}) +} + +func permNameToFuncID(name string) []byte { + function, err := permissionsContract.FunctionByName(name) + if err != nil { + panic("didn't find snative function signature!") + } + id := function.ID() + return id[:] +} + +func snativePermTestInputCALL(name string, user acm.PrivateAccount, perm ptypes.PermFlag, + val bool) (addr acm.Address, pF ptypes.PermFlag, data []byte) { + addr = permissionsContract.Address() + switch name { + case "hasBase", "unsetBase": + data = user.Address().Word256().Bytes() + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + case "setBase": + data = user.Address().Word256().Bytes() + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + data = append(data, boolToWord256(val).Bytes()...) + case "setGlobal": + data = Uint64ToWord256(uint64(perm)).Bytes() + data = append(data, boolToWord256(val).Bytes()...) + } + data = append(permNameToFuncID(name), data...) + var err error + if pF, err = permission.PermStringToFlag(name); err != nil { + panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) + } + return +} + +func snativePermTestInputTx(name string, user acm.PrivateAccount, perm ptypes.PermFlag, val bool) (snativeArgs permission.PermArgs) { + switch name { + case "hasBase": + snativeArgs = *permission.HasBaseArgs(user.Address(), perm) + case "unsetBase": + snativeArgs = *permission.UnsetBaseArgs(user.Address(), perm) + case "setBase": + snativeArgs = *permission.SetBaseArgs(user.Address(), perm, val) + case "setGlobal": + snativeArgs = *permission.SetGlobalArgs(perm, val) + } + return +} + +func snativeRoleTestInputCALL(name string, user acm.PrivateAccount, + role string) (addr acm.Address, pF ptypes.PermFlag, data []byte) { + addr = permissionsContract.Address() + data = user.Address().Word256().Bytes() + data = append(data, RightPadBytes([]byte(role), 32)...) + data = append(permNameToFuncID(name), data...) + + var err error + if pF, err = permission.PermStringToFlag(name); err != nil { + panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) + } + return +} + +func snativeRoleTestInputTx(name string, user acm.PrivateAccount, role string) (snativeArgs permission.PermArgs) { + switch name { + case "hasRole": + snativeArgs = *permission.HasRoleArgs(user.Address(), role) + case "addRole": + snativeArgs = *permission.AddRoleArgs(user.Address(), role) + case "removeRole": + snativeArgs = *permission.RemoveRoleArgs(user.Address(), role) + } + return +} + +// convenience function for contract that calls a given address +func callContractCode(contractAddr acm.Address) []byte { + // calldatacopy into mem and use as input to call + memOff, inputOff := byte(0x0), byte(0x0) + value := byte(0x1) + inOff := byte(0x0) + retOff, retSize := byte(0x0), byte(0x20) + + // this is the code we want to run (call a contract and return) + return bc.Splice(CALLDATASIZE, PUSH1, inputOff, PUSH1, memOff, + CALLDATACOPY, PUSH1, retSize, PUSH1, retOff, CALLDATASIZE, PUSH1, inOff, + PUSH1, value, PUSH20, contractAddr, + // Zeno loves us - call with half of the available gas each time we CALL + PUSH1, 2, GAS, DIV, CALL, + PUSH1, 32, PUSH1, 0, RETURN) +} + +// convenience function for contract that is a factory for the code that comes as call data +func createContractCode() []byte { + // TODO: gas ... + + // calldatacopy the calldatasize + memOff, inputOff := byte(0x0), byte(0x0) + contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} + + // create + value := byte(0x1) + contractCode = append(contractCode, []byte{0x60, value, 0x36, 0x60, memOff, 0xf0}...) + return contractCode +} + +// wrap a contract in create code +func wrapContractForCreate(contractCode []byte) []byte { + // 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{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(lenCode), 0x60, 0x0, 0xf3}...) + // return init code, contract code, expected return + return code +} diff --git a/manager/burrow-mint/namereg.go b/execution/namereg.go similarity index 63% rename from manager/burrow-mint/namereg.go rename to execution/namereg.go index 3deedd44e1dc0acdec88af4555810ef611e04e69..5e71f0fc6c4f8e87eaf33213262134cad26c0adf 100644 --- a/manager/burrow-mint/namereg.go +++ b/execution/namereg.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package burrowmint +package execution import ( "bytes" @@ -20,82 +20,108 @@ import ( "fmt" "sync" - sm "github.com/hyperledger/burrow/manager/burrow-mint/state" - - core_types "github.com/hyperledger/burrow/core/types" - event "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" ) // NameReg is part of the pipe for BurrowMint and provides the implementation // for the pipe to call into the BurrowMint application +type NameRegGetter interface { + GetNameRegEntry(name string) *NameRegEntry +} + +type NameRegIterable interface { + NameRegGetter + IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) (stopped bool) +} + type namereg struct { - burrowMint *BurrowMint + state *State + blockchain blockchain.Blockchain filterFactory *event.FilterFactory } -func newNameReg(burrowMint *BurrowMint) *namereg { +var _ NameRegIterable = &namereg{} + +type NameRegEntry struct { + Name string `json:"name"` // registered name for the entry + Owner account.Address `json:"owner"` // address that created the entry + Data string `json:"data"` // data to store under this name + Expires uint64 `json:"expires"` // block at which this entry expires +} - ff := event.NewFilterFactory() +func NewNameReg(state *State, blockchain blockchain.Blockchain) *namereg { + filterFactory := event.NewFilterFactory() - ff.RegisterFilterPool("name", &sync.Pool{ + filterFactory.RegisterFilterPool("name", &sync.Pool{ New: func() interface{} { return &NameRegNameFilter{} }, }) - ff.RegisterFilterPool("owner", &sync.Pool{ + filterFactory.RegisterFilterPool("owner", &sync.Pool{ New: func() interface{} { return &NameRegOwnerFilter{} }, }) - ff.RegisterFilterPool("data", &sync.Pool{ + filterFactory.RegisterFilterPool("data", &sync.Pool{ New: func() interface{} { return &NameRegDataFilter{} }, }) - ff.RegisterFilterPool("expires", &sync.Pool{ + filterFactory.RegisterFilterPool("expires", &sync.Pool{ New: func() interface{} { return &NameRegExpiresFilter{} }, }) - return &namereg{burrowMint, ff} + return &namereg{ + state: state, + blockchain: blockchain, + filterFactory: filterFactory, + } +} + +func (nr *namereg) GetNameRegEntry(name string) *NameRegEntry { + return nr.state.GetNameRegEntry(name) +} + +func (nr *namereg) IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) bool { + return nr.state.IterateNameRegEntries(consumer) } -func (this *namereg) Entry(key string) (*core_types.NameRegEntry, error) { - st := this.burrowMint.GetState() // performs a copy - entry := st.GetNameRegEntry(key) +func (nr *namereg) Entry(key string) (*NameRegEntry, error) { + entry := nr.state.GetNameRegEntry(key) if entry == nil { - return nil, fmt.Errorf("Entry %s not found", key) + return nil, fmt.Errorf("entry %s not found", key) } return entry, nil } -func (this *namereg) Entries(filters []*event.FilterData) (*core_types.ResultListNames, error) { - var blockHeight int - var names []*core_types.NameRegEntry - state := this.burrowMint.GetState() - blockHeight = state.LastBlockHeight - filter, err := this.filterFactory.NewFilter(filters) +func (nr *namereg) Entries(filters []*event.FilterData) (*ResultListNames, error) { + var names []*NameRegEntry + blockHeight := nr.blockchain.Tip().LastBlockHeight() + filter, err := nr.filterFactory.NewFilter(filters) if err != nil { return nil, fmt.Errorf("Error in query: " + err.Error()) } - state.GetNames().Iterate(func(key, value []byte) bool { - nre := sm.DecodeNameRegEntry(value) + nr.state.GetNames().Iterate(func(key, value []byte) bool { + nre := DecodeNameRegEntry(value) if filter.Match(nre) { names = append(names, nre) } return false }) - return &core_types.ResultListNames{blockHeight, names}, nil + return &ResultListNames{blockHeight, names}, nil } type ResultListNames struct { - BlockHeight int `json:"block_height"` - Names []*core_types.NameRegEntry `json:"names"` + BlockHeight uint64 `json:"block_height"` + Names []*NameRegEntry `json:"names"` } // Filter for namereg name. This should not be used to get individual entries by name. @@ -127,7 +153,7 @@ func (this *NameRegNameFilter) Configure(fd *event.FilterData) error { } func (this *NameRegNameFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } @@ -147,7 +173,7 @@ func (this *NameRegOwnerFilter) Configure(fd *event.FilterData) error { val, err := hex.DecodeString(fd.Value) if err != nil { - return fmt.Errorf("Wrong value type.") + return fmt.Errorf("wrong value type.") } if op == "==" { this.match = func(a, b []byte) bool { @@ -166,11 +192,11 @@ func (this *NameRegOwnerFilter) Configure(fd *event.FilterData) error { } func (this *NameRegOwnerFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } - return this.match(nre.Owner, this.value) + return this.match(nre.Owner.Bytes(), this.value) } // Filter for namereg data. Useful for example if you store an ipfs hash and know the hash but need the key. @@ -202,7 +228,7 @@ func (this *NameRegDataFilter) Configure(fd *event.FilterData) error { } func (this *NameRegDataFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } @@ -213,8 +239,8 @@ func (this *NameRegDataFilter) Match(v interface{}) bool { // Ops: All type NameRegExpiresFilter struct { op string - value int64 - match func(int64, int64) bool + value uint64 + match func(uint64, uint64) bool } func (this *NameRegExpiresFilter) Configure(fd *event.FilterData) error { @@ -233,9 +259,9 @@ func (this *NameRegExpiresFilter) Configure(fd *event.FilterData) error { } func (this *NameRegExpiresFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } - return this.match(int64(nre.Expires), this.value) + return this.match(nre.Expires, this.value) } diff --git a/manager/burrow-mint/state/state.go b/execution/state.go similarity index 54% rename from manager/burrow-mint/state/state.go rename to execution/state.go index ccf68bf34841fe4765fc6d785b6da2d6c0d4891e..74a1b1eb414bbaf37e326bac274cefb6f3c2f31d 100644 --- a/manager/burrow-mint/state/state.go +++ b/execution/state.go @@ -12,28 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package state +package execution import ( "bytes" "fmt" "io" - "io/ioutil" + "sync" "time" acm "github.com/hyperledger/burrow/account" - genesis "github.com/hyperledger/burrow/genesis" - ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/txs" - - dbm "github.com/tendermint/go-db" - "github.com/tendermint/go-events" - "github.com/tendermint/go-merkle" - "github.com/tendermint/go-wire" - - core_types "github.com/hyperledger/burrow/core/types" "github.com/hyperledger/burrow/util" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/go-wire" + "github.com/tendermint/merkleeyes/iavl" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/merkle" ) var ( @@ -45,189 +43,240 @@ var ( maxLoadStateElementSize = 0 // no max ) +// TODO +const GasLimit = uint64(1000000) + //----------------------------------------------------------------------------- // NOTE: not goroutine-safe. type State struct { - DB dbm.DB - ChainID string - LastBlockHeight int - LastBlockHash []byte - LastBlockParts types.PartSetHeader - LastBlockTime time.Time + sync.RWMutex + db dbm.DB // 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. +} + +// Implements account and blockchain state +var _ acm.Updater = &State{} + +var _ acm.StateIterable = &State{} + +var _ acm.StateWriter = &State{} + +func MakeGenesisState(db dbm.DB, genDoc *genesis.GenesisDoc) *State { + if len(genDoc.Validators) == 0 { + util.Fatalf("The genesis file has no validators") + } + + if genDoc.GenesisTime.IsZero() { + // NOTE: [ben] change GenesisTime to requirement on v0.17 + // GenesisTime needs to be deterministic across the chain + // and should be required in the genesis file; + // the requirement is not yet enforced when lacking set + // time to 11/18/2016 @ 4:09am (UTC) + genDoc.GenesisTime = time.Unix(1479442162, 0) + } + + // Make accounts state tree + accounts := iavl.NewIAVLTree(defaultAccountsCacheCapacity, db) + for _, genAcc := range genDoc.Accounts { + perm := genAcc.Permissions + acc := &acm.ConcreteAccount{ + Address: genAcc.Address, + Balance: genAcc.Amount, + Permissions: perm, + } + accounts.Set(acc.Address.Bytes(), acc.Encode()) + } + + // global permissions are saved as the 0 address + // so they are included in the accounts tree + globalPerms := ptypes.DefaultAccountPermissions + globalPerms = genDoc.GlobalPermissions + // XXX: make sure the set bits are all true + // Without it the HasPermission() functions will fail + globalPerms.Base.SetBit = ptypes.AllPermFlags + + permsAcc := &acm.ConcreteAccount{ + Address: permission.GlobalPermissionsAddress, + Balance: 1337, + Permissions: globalPerms, + } + accounts.Set(permsAcc.Address.Bytes(), permsAcc.Encode()) + + // Make validatorInfos state tree && validators slice + /* + validatorInfos := merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) + validators := make([]*types.Validator, len(genDoc.Validators)) + for i, val := range genDoc.Validators { + pubKey := val.PublicKey + address := pubKey.Address() - evc events.Fireable // typically an events.EventCache + // Make ValidatorInfo + valInfo := &types.ValidatorInfo{ + Address: address, + PublicKey: 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, + PublicKey: pubKey, + VotingPower: val.Amount, + } + } + */ + + // Make namereg tree + nameReg := iavl.NewIAVLTree(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, + //BondedValidators: types.NewValidatorSet(validators), + //LastBondedValidators: types.NewValidatorSet(nil), + //UnbondingValidators: types.NewValidatorSet(nil), + accounts: accounts, + //validatorInfos: validatorInfos, + nameReg: nameReg, + } } -func LoadState(db dbm.DB) *State { - s := &State{DB: db} +func LoadState(db dbm.DB) (*State, error) { + s := &State{db: db} buf := db.Get(stateKey) if len(buf) == 0 { - return nil + return nil, nil } else { r, n, err := bytes.NewReader(buf), new(int), new(error) - s.ChainID = wire.ReadString(r, maxLoadStateElementSize, n, err) - s.LastBlockHeight = wire.ReadVarint(r, n, err) - s.LastBlockHash = wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - s.LastBlockParts = wire.ReadBinary(types.PartSetHeader{}, r, maxLoadStateElementSize, n, err).(types.PartSetHeader) - s.LastBlockTime = wire.ReadTime(r, n, err) - // s.BondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) - // s.LastBondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) - // s.UnbondingValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) - accountsHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - s.accounts = merkle.NewIAVLTree(defaultAccountsCacheCapacity, db) - s.accounts.Load(accountsHash) - //validatorInfosHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - //s.validatorInfos = merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) - //s.validatorInfos.Load(validatorInfosHash) - nameRegHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - s.nameReg = merkle.NewIAVLTree(0, db) - s.nameReg.Load(nameRegHash) + wire.ReadBinaryPtr(&s, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED - util.Fatalf("Data has been corrupted or its spec has changed: %v\n", *err) + return nil, fmt.Errorf("data has been corrupted or its spec has changed: %v", *err) } - // TODO: ensure that buf is completely read. } - return s + return s, nil } func (s *State) Save() { + s.Lock() + defer s.Unlock() s.accounts.Save() //s.validatorInfos.Save() s.nameReg.Save() - buf, n, err := new(bytes.Buffer), new(int), 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) - if *err != nil { - // TODO: [Silas] Do something better than this, really serialising ought to - // be error-free - util.Fatalf("Could not serialise state in order to save the state, "+ - "cannot continue, error: %s", *err) - } - s.DB.Set(stateKey, buf.Bytes()) + s.db.SetSync(stateKey, wire.BinaryBytes(s)) } // CONTRACT: // Copy() is a cheap way to take a snapshot, // as if State were copied by value. +// TODO [Silas]: Kill this with fire it is totally broken - there is no safe way to copy IAVLTree while sharing database 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, + db: s.db, // 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, } } +//func (s *State) Copy() *State { +// stateCopy := &State{ +// db: dbm.NewMemDB(), +// chainID: s.chainID, +// lastBlockHeight: s.lastBlockHeight, +// lastBlockAppHash: s.lastBlockAppHash, +// 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: copyTree(s.accounts), +// //validatorInfos: s.validatorInfos.Copy(), +// nameReg: copyTree(s.nameReg), +// evc: nil, +// } +// stateCopy.Save() +// return stateCopy +//} + // Returns a hash that represents the state data, excluding Last* func (s *State) Hash() []byte { + s.RLock() + defer s.RUnlock() return merkle.SimpleHashFromMap(map[string]interface{}{ //"BondedValidators": s.BondedValidators, //"UnbondingValidators": s.UnbondingValidators, - "Accounts": s.accounts, + "Accounts": s.accounts.Hash(), //"ValidatorInfos": s.validatorInfos, "NameRegistry": s.nameReg, }) } -/* //XXX Done by tendermint core -// Mutates the block in place and updates it with new state hash. -func (s *State) ComputeBlockStateHash(block *types.Block) error { - sCopy := s.Copy() - // sCopy has no event cache in it, so this won't fire events - err := execBlock(sCopy, block, types.PartSetHeader{}) - if err != nil { - return err - } - // Set block.StateHash - block.StateHash = sCopy.Hash() - return nil -} -*/ - -func (s *State) GetGenesisDoc() (*genesis.GenesisDoc, error) { - var genesisDoc *genesis.GenesisDoc - loadedGenesisDocBytes := s.DB.Get(genesis.GenDocKey) - err := new(error) - wire.ReadJSONPtr(&genesisDoc, loadedGenesisDocBytes, err) - if *err != nil { - return nil, fmt.Errorf("Unable to read genesisDoc from db on Get: %v", err) - } - return genesisDoc, nil -} - -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. -// Implements Statelike -func (s *State) GetAccount(address []byte) *acm.Account { - _, accBytes, _ := s.accounts.Get(address) +func (s *State) GetAccount(address acm.Address) (acm.Account, error) { + s.RLock() + defer s.RUnlock() + _, accBytes, _ := s.accounts.Get(address.Bytes()) if accBytes == nil { - return nil + return nil, nil } - return acm.DecodeAccount(accBytes) + return acm.Decode(accBytes) } -// 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, acm.EncodeAccount(account)) +func (s *State) UpdateAccount(account acm.Account) error { + s.Lock() + defer s.Unlock() + s.accounts.Set(account.Address().Bytes(), account.Encode()) + return nil } -// Implements Statelike -func (s *State) RemoveAccount(address []byte) bool { - _, removed := s.accounts.Remove(address) - return removed +func (s *State) RemoveAccount(address acm.Address) error { + s.Lock() + defer s.Unlock() + s.accounts.Remove(address.Bytes()) + return nil } -// The returned Account is a copy, so mutating it -// has no side effects. +// This does not give a true independent copy since the underlying database is shared and any save calls all copies +// to become invalid and using them may cause panics func (s *State) GetAccounts() merkle.Tree { return s.accounts.Copy() } -// Set the accounts tree -func (s *State) SetAccounts(accounts merkle.Tree) { - s.accounts = accounts +func (s *State) IterateAccounts(consumer func(acm.Account) (stop bool)) (stopped bool, err error) { + s.RLock() + defer s.RUnlock() + stopped = s.accounts.Iterate(func(key, value []byte) bool { + var account acm.Account + account, err = acm.Decode(value) + if err != nil { + return true + } + return consumer(account) + }) + return } // State.accounts @@ -265,7 +314,7 @@ func (s *State) unbondValidator(val *types.Validator) { if !removed { PanicCrisis("Couldn't remove validator for unbonding") } - val.UnbondHeight = s.LastBlockHeight + 1 + val.UnbondHeight = s.lastBlockHeight + 1 added := s.UnbondingValidators.Add(val) if !added { PanicCrisis("Couldn't add validator for unbonding") @@ -278,7 +327,7 @@ func (s *State) rebondValidator(val *types.Validator) { if !removed { PanicCrisis("Couldn't remove validator for rebonding") } - val.BondHeight = s.LastBlockHeight + 1 + val.BondHeight = s.lastBlockHeight + 1 added := s.BondedValidators.Add(val) if !added { PanicCrisis("Couldn't add validator for rebonding") @@ -291,7 +340,7 @@ func (s *State) releaseValidator(val *types.Validator) { if valInfo == nil { PanicSanity("Couldn't find validatorInfo for release") } - valInfo.ReleasedHeight = s.LastBlockHeight + 1 + valInfo.ReleasedHeight = s.lastBlockHeight + 1 s.SetValidatorInfo(valInfo) // Send coins back to UnbondTo outputs @@ -317,7 +366,7 @@ func (s *State) destroyValidator(val *types.Validator) { if valInfo == nil { PanicSanity("Couldn't find validatorInfo for release") } - valInfo.DestroyedHeight = s.LastBlockHeight + 1 + valInfo.DestroyedHeight = s.lastBlockHeight + 1 valInfo.DestroyedAmount = val.VotingPower s.SetValidatorInfo(valInfo) @@ -343,17 +392,81 @@ func (s *State) SetValidatorInfos(validatorInfos merkle.Tree) { //------------------------------------- // State.storage -func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { - storage = merkle.NewIAVLTree(1024, s.DB) +func (s *State) accountStorage(address acm.Address) (merkle.Tree, error) { + account, err := s.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("could not find account %s to access its storage", address) + } + return s.LoadStorage(account.StorageRoot()), nil +} + +func (s *State) LoadStorage(hash []byte) merkle.Tree { + s.RLock() + defer s.RUnlock() + storage := iavl.NewIAVLTree(1024, s.db) storage.Load(hash) return storage } +func (s *State) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + s.RLock() + defer s.RUnlock() + storageTree, err := s.accountStorage(address) + if err != nil { + return binary.Zero256, err + } + _, value, _ := storageTree.Get(key.Bytes()) + return binary.LeftPadWord256(value), nil +} + +func (s *State) SetStorage(address acm.Address, key, value binary.Word256) error { + s.Lock() + defer s.Unlock() + storageTree, err := s.accountStorage(address) + if err != nil { + return err + } + if storageTree != nil { + storageTree.Set(key.Bytes(), value.Bytes()) + } + return nil +} + +func (s *State) IterateStorage(address acm.Address, + consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) { + + var storageTree merkle.Tree + storageTree, err = s.accountStorage(address) + if err != nil { + return + } + stopped = storageTree.Iterate(func(key []byte, value []byte) (stop bool) { + // Note: no left padding should occur unless there is a bug and non-words have been writte to this storage tree + if len(key) != binary.Word256Length { + err = fmt.Errorf("key '%X' stored for account %s is not a %v-byte word", + key, address, binary.Word256Length) + return true + } + if len(value) != binary.Word256Length { + err = fmt.Errorf("value '%X' stored for account %s is not a %v-byte word", + key, address, binary.Word256Length) + return true + } + return consumer(binary.LeftPadWord256(key), binary.LeftPadWord256(value)) + }) + return +} + // State.storage //------------------------------------- // State.nameReg -func (s *State) GetNameRegEntry(name string) *core_types.NameRegEntry { +var _ NameRegIterable = &State{} + +func (s *State) GetNameRegEntry(name string) *NameRegEntry { _, valueBytes, _ := s.nameReg.Get([]byte(name)) if valueBytes == nil { return nil @@ -362,18 +475,24 @@ func (s *State) GetNameRegEntry(name string) *core_types.NameRegEntry { return DecodeNameRegEntry(valueBytes) } -func DecodeNameRegEntry(entryBytes []byte) *core_types.NameRegEntry { +func (s *State) IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) (stopped bool) { + return s.nameReg.Iterate(func(key []byte, value []byte) (stop bool) { + return consumer(DecodeNameRegEntry(value)) + }) +} + +func DecodeNameRegEntry(entryBytes []byte) *NameRegEntry { var n int var err error - value := NameRegCodec.Decode(bytes.NewBuffer(entryBytes), &n, &err) - return value.(*core_types.NameRegEntry) + value := NameRegDecode(bytes.NewBuffer(entryBytes), &n, &err) + return value.(*NameRegEntry) } -func (s *State) UpdateNameRegEntry(entry *core_types.NameRegEntry) bool { +func (s *State) UpdateNameRegEntry(entry *NameRegEntry) bool { w := new(bytes.Buffer) var n int var err error - NameRegCodec.Encode(entry, w, &n, &err) + NameRegEncode(entry, w, &n, &err) return s.nameReg.Set([]byte(entry.Name), w.Bytes()) } @@ -390,144 +509,10 @@ func (s *State) GetNames() merkle.Tree { func (s *State) SetNameReg(nameReg merkle.Tree) { s.nameReg = nameReg } - -func NameRegEncoder(o interface{}, w io.Writer, n *int, err *error) { - wire.WriteBinary(o.(*core_types.NameRegEntry), w, n, err) -} - -func NameRegDecoder(r io.Reader, n *int, err *error) interface{} { - return wire.ReadBinary(&core_types.NameRegEntry{}, r, txs.MaxDataLength, n, err) +func NameRegEncode(o interface{}, w io.Writer, n *int, err *error) { + wire.WriteBinary(o.(*NameRegEntry), w, 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 -} - -//----------------------------------------------------------------------------- -// Genesis - -func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*genesis.GenesisDoc, *State) { - jsonBlob, err := ioutil.ReadFile(genDocFile) - if err != nil { - util.Fatalf("Couldn't read GenesisDoc file: %v", err) - } - genDoc := genesis.GenesisDocFromJSON(jsonBlob) - return genDoc, MakeGenesisState(db, genDoc) -} - -func MakeGenesisState(db dbm.DB, genDoc *genesis.GenesisDoc) *State { - if len(genDoc.Validators) == 0 { - util.Fatalf("The genesis file has no validators") - } - - if genDoc.GenesisTime.IsZero() { - // NOTE: [ben] change GenesisTime to requirement on v0.17 - // GenesisTime needs to be deterministic across the chain - // and should be required in the genesis file; - // the requirement is not yet enforced when lacking set - // time to 11/18/2016 @ 4:09am (UTC) - genDoc.GenesisTime = time.Unix(1479442162, 0) - } - - // Make accounts state tree - accounts := merkle.NewIAVLTree(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, acm.EncodeAccount(acc)) - } - - // 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, acm.EncodeAccount(permsAcc)) - - // Make validatorInfos state tree && validators slice - /* - validatorInfos := merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) - 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, - PubKey: pubKey, - VotingPower: val.Amount, - } - } - */ - - // Make namereg tree - nameReg := merkle.NewIAVLTree(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, - } +func NameRegDecode(r io.Reader, n *int, err *error) interface{} { + return wire.ReadBinary(&NameRegEntry{}, r, txs.MaxDataLength, n, err) } diff --git a/manager/burrow-mint/state/state_test.go b/execution/state_test.go similarity index 51% rename from manager/burrow-mint/state/state_test.go rename to execution/state_test.go index f9f91d9b76561772a5f654f35ea71b1da468e766..8ffb5ff23ac92f19f1cf11b8fd56c61a92a46809 100644 --- a/manager/burrow-mint/state/state_test.go +++ b/execution/state_test.go @@ -12,48 +12,88 @@ // See the License for the specific language governing permissions and // limitations under the License. -package state +package execution import ( "bytes" "encoding/hex" "testing" - core_types "github.com/hyperledger/burrow/core/types" - evm "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" + "fmt" + + "github.com/hyperledger/burrow/execution/evm/sha3" - "github.com/tendermint/tendermint/config/tendermint_test" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/assert" + dbm "github.com/tendermint/tmlibs/db" ) -func init() { - tendermint_test.ResetConfig("state_test") - evm.SetDebug(true) -} +var deterministicGenesis = genesis.NewDeterministicGenesis(34059836243380576) +var testGenesisDoc, testPrivAccounts = deterministicGenesis. + GenesisDoc(3, true, 1000, 1, true, 1000) +var testChainID = testGenesisDoc.ChainID() -func execTxWithState(state *State, tx txs.Tx, runCall bool) error { - cache := NewBlockCache(state) - if err := ExecTx(cache, tx, runCall, nil, logger); err != nil { +func execTxWithStateAndBlockchain(state *State, tip bcm.Tip, tx txs.Tx) error { + exe := newExecutor(true, state, testChainID, tip, event.NewNoOpFireable(), logger) + if err := exe.Execute(tx); err != nil { return err } else { - cache.Sync() + exe.blockCache.Sync() return nil } } -func execTxWithStateNewBlock(state *State, tx txs.Tx, runCall bool) error { - if err := execTxWithState(state, tx, runCall); err != nil { +func execTxWithState(state *State, tx txs.Tx) error { + return execTxWithStateAndBlockchain(state, bcm.NewBlockchain(testGenesisDoc), tx) +} + +func commitNewBlock(state *State, blockchain bcm.MutableBlockchain) { + blockchain.CommitBlock(blockchain.LastBlockTime().Add(time.Second), sha3.Sha3(blockchain.LastBlockHash()), + state.Hash()) +} + +func execTxWithStateNewBlock(state *State, blockchain bcm.MutableBlockchain, tx txs.Tx) error { + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { return err } - - state.LastBlockHeight += 1 + commitNewBlock(state, blockchain) return nil } +func makeGenesisState(numAccounts int, randBalance bool, minBalance uint64, numValidators int, randBonded bool, + minBonded int64) (*State, []acm.PrivateAccount) { + testGenesisDoc, privAccounts := deterministicGenesis.GenesisDoc(numAccounts, randBalance, minBalance, + numValidators, randBonded, minBonded) + s0 := MakeGenesisState(dbm.NewMemDB(), testGenesisDoc) + s0.Save() + return s0, privAccounts +} + +func getAccount(state acm.Getter, address acm.Address) acm.MutableAccount { + acc, _ := acm.GetMutableAccount(state, address) + return acc +} + +func addressPtr(account acm.Account) *acm.Address { + if account == nil { + return nil + } + accountAddresss := account.Address() + return &accountAddresss +} + +// Tests + func TestCopyState(t *testing.T) { // Generate a random state - s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000) + s0, privAccounts := makeGenesisState(10, true, 1000, 5, true, 1000) s0Hash := s0.Hash() if len(s0Hash) == 0 { t.Error("Expected state hash") @@ -61,23 +101,22 @@ func TestCopyState(t *testing.T) { // Check hash of copy s0Copy := s0.Copy() - if !bytes.Equal(s0Hash, s0Copy.Hash()) { - t.Error("Expected state copy hash to be the same") - } + assert.Equal(t, s0Hash, s0Copy.Hash(), "Expected state copy hash to be the same") + assert.Equal(t, s0Copy.Copy().Hash(), s0Copy.Hash(), "Expected COPY COPY COPY the same") // Mutate the original; hash should change. - acc0Address := privAccounts[0].PubKey.Address() - acc := s0.GetAccount(acc0Address) - acc.Balance += 1 + acc0Address := privAccounts[0].Address() + acc := getAccount(s0, acc0Address) + acc.AddToBalance(1) // The account balance shouldn't have changed yet. - if s0.GetAccount(acc0Address).Balance == acc.Balance { + if getAccount(s0, 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 { + if getAccount(s0, acc0Address).Balance() != acc.Balance() { t.Error("Account balance wasn't set") } @@ -99,11 +138,11 @@ func makeBlock(t *testing.T, state *State, validation *tmtypes.Commit, txs []txs } block := &tmtypes.Block{ Header: &tmtypes.Header{ - ChainID: state.ChainID, - Height: state.LastBlockHeight + 1, - Time: state.LastBlockTime.Add(time.Minute), + testChainID: testChainID, + Height: blockchain.LastBlockHeight() + 1, + Time: state.lastBlockTime.Add(time.Minute), NumTxs: len(txs), - LastBlockHash: state.LastBlockHash, + lastBlockAppHash: state.lastBlockAppHash, LastBlockParts: state.LastBlockParts, AppHash: nil, }, @@ -129,7 +168,7 @@ func makeBlock(t *testing.T, state *State, validation *tmtypes.Commit, txs []txs func TestGenesisSaveLoad(t *testing.T) { // Generate a state, save & load it. - s0, _, _ := RandGenesisState(10, true, 1000, 5, true, 1000) + s0, _, _ := makeGenesisState(10, true, 1000, 5, true, 1000) // Make complete block and blockParts block := makeBlock(t, s0, nil, nil) @@ -145,23 +184,23 @@ func TestGenesisSaveLoad(t *testing.T) { s0.Save() // Sanity check s0 - //s0.DB.(*dbm.MemDB).Print() + //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) + if s0.lastBlockHeight != 1 { + t.Error("s0 lastBlockHeight should be 1, got", s0.lastBlockHeight) } // Load s1 - s1 := LoadState(s0.DB) + s1 := LoadState(s0.db) // Compare height & blockHash - if s0.LastBlockHeight != s1.LastBlockHeight { - t.Error("LastBlockHeight mismatch") + if s0.lastBlockHeight != s1.lastBlockHeight { + t.Error("lastBlockHeight mismatch") } - if !bytes.Equal(s0.LastBlockHash, s1.LastBlockHash) { - t.Error("LastBlockHash mismatch") + if !bytes.Equal(s0.lastBlockAppHash, s1.lastBlockAppHash) { + t.Error("lastBlockAppHash mismatch") } // Compare state merkle trees @@ -194,64 +233,68 @@ func TestGenesisSaveLoad(t *testing.T) { 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()) + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].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 + for i := uint64(0); i < 3; i++ { + sequence := acc0.Sequence() + i tx := txs.NewSendTx() - tx.AddInputWithNonce(acc0PubKey, 1, sequence) - tx.AddOutput(acc1.Address, 1) - tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) + tx.AddInputWithSequence(acc0PubKey, 1, sequence) + tx.AddOutput(acc1.Address(), 1) + tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) stateCopy := state.Copy() - err := execTxWithState(stateCopy, tx, true) + err := execTxWithState(stateCopy, tx) 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 { + // Check acc.Sequence(). + newAcc0 := getAccount(stateCopy, acc0.Address()) + if newAcc0.Sequence() != sequence { t.Errorf("Expected account sequence to change to %v, got %v", - sequence, newAcc0.Sequence) + 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 { + // Check acc.Sequence(). (shouldn't have changed) + newAcc0 := getAccount(stateCopy, acc0.Address()) + if newAcc0.Sequence() != acc0.Sequence() { t.Errorf("Expected account sequence to not change from %v, got %v", - acc0.Sequence, newAcc0.Sequence) + acc0.Sequence(), newAcc0.Sequence()) } } } } func TestNameTxs(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state := MakeGenesisState(dbm.NewMemDB(), testGenesisDoc) + state.Save() txs.MinNameRegistrationPeriod = 5 - startingBlock := state.LastBlockHeight + blockchain := bcm.NewBlockchain(testGenesisDoc) + startingBlock := blockchain.LastBlockHeight() // try some bad names. these should all fail - names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyessurprised", "no spaces please"} + 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 + fee := uint64(1000) + numDesiredBlocks := uint64(5) for _, name := range names { - amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier* + txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) - if err := execTxWithState(state, tx, true); err == nil { + if err := execTxWithState(state, tx); err == nil { t.Fatalf("Expected invalid name error from %s", name) } } @@ -260,23 +303,23 @@ func TestNameTxs(t *testing.T) { 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)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier* + txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) - if err := execTxWithState(state, tx, true); err == nil { + if err := execTxWithState(state, tx); err == nil { t.Fatalf("Expected invalid data error from %s", data) } } - validateEntry := func(t *testing.T, entry *core_types.NameRegEntry, name, - data string, addr []byte, expires int) { + validateEntry := func(t *testing.T, entry *NameRegEntry, name, data string, addr acm.Address, expires uint64) { if entry == nil { t.Fatalf("Could not find name %s", name) } - if !bytes.Equal(entry.Owner, addr) { - t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr) + if entry.Owner != addr { + t.Fatalf("Wrong owner. Got %s expected %s", entry.Owner, addr) } if data != entry.Data { t.Fatalf("Wrong data. Got %s expected %s", entry.Data, data) @@ -292,78 +335,81 @@ func TestNameTxs(t *testing.T) { // 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)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithState(state, tx, true); err != nil { + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithState(state, tx); err != nil { t.Fatal(err) } entry := state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks) + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks) // fail to update it as non-owner, in same block - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err == nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithState(state, tx); 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, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*2) + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks*2) // update it as owner, just to increase expiry, in next block - tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3) + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks*3) // fail to update it as non-owner - state.LastBlockHeight = entry.Expires - 1 - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err == nil { + // Fast forward + for blockchain.Tip().LastBlockHeight() < entry.Expires-1 { + commitNewBlock(state, blockchain) + } + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err == nil { t.Fatal("Expected error") } + commitNewBlock(state, blockchain) // once expires, non-owner succeeds - state.LastBlockHeight = entry.Expires - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) + validateEntry(t, entry, name, data, testPrivAccounts[1].Address(), blockchain.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)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - oldCredit) - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err != nil { + amt = fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - oldCredit + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) + validateEntry(t, entry, name, data, testPrivAccounts[1].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) // test removal amt = fee data = "" - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) @@ -375,21 +421,24 @@ func TestNameTxs(t *testing.T) { // test removal by key1 after expiry name = "looking_good/karaoke_bar" data = "some data" - amt = fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithState(state, tx, true); err != nil { + amt = fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ = txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, state.LastBlockHeight+numDesiredBlocks) - state.LastBlockHeight = entry.Expires + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) + // Fast forward + for blockchain.Tip().LastBlockHeight() < entry.Expires { + commitNewBlock(state, blockchain) + } amt = fee data = "" - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) @@ -424,71 +473,71 @@ var createData, _ = hex.DecodeString("9ed93318") func TestCreates(t *testing.T) { //evm.SetDebug(true) - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state, privAccounts := makeGenesisState(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()) - acc2 := state.GetAccount(privAccounts[2].PubKey.Address()) + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + acc2 := getAccount(state, privAccounts[2].Address()) state = state.Copy() - newAcc1 := state.GetAccount(acc1.Address) - newAcc1.Code = preFactoryCode - newAcc2 := state.GetAccount(acc2.Address) - newAcc2.Code = factoryCode + newAcc1 := getAccount(state, acc1.Address()) + newAcc1.SetCode(preFactoryCode) + newAcc2 := getAccount(state, acc2.Address()) + newAcc2.SetCode(factoryCode) state.UpdateAccount(newAcc1) state.UpdateAccount(newAcc2) - createData = append(createData, word256.LeftPadBytes(acc2.Address, 32)...) + createData = append(createData, acc2.Address().Word256().Bytes()...) // call the pre-factory, triggering the factory to run a create tx := &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 10000, Data: createData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - acc1 = state.GetAccount(acc1.Address) - storage := state.LoadStorage(acc1.StorageRoot) - _, firstCreatedAddress, _ := storage.Get(word256.LeftPadBytes([]byte{0}, 32)) + acc1 = getAccount(state, acc1.Address()) + storage := state.LoadStorage(acc1.StorageRoot()) + _, firstCreatedAddress, _ := storage.Get(binary.LeftPadBytes([]byte{0}, 32)) - acc0 = state.GetAccount(acc0.Address) + acc0 = getAccount(state, acc0.Address()) // call the pre-factory, triggering the factory to run a create tx = &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 100000, Data: createData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err = execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err = execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - acc1 = state.GetAccount(acc1.Address) - storage = state.LoadStorage(acc1.StorageRoot) - _, secondCreatedAddress, _ := storage.Get(word256.LeftPadBytes([]byte{0}, 32)) + acc1 = getAccount(state, acc1.Address()) + storage = state.LoadStorage(acc1.StorageRoot()) + _, secondCreatedAddress, _ := storage.Get(binary.LeftPadBytes([]byte{0}, 32)) if bytes.Equal(firstCreatedAddress, secondCreatedAddress) { t.Errorf("Multiple contracts created with the same address!") @@ -506,129 +555,196 @@ var callerCode, _ = hex.DecodeString("60606040526000357c010000000000000000000000 var sendData, _ = hex.DecodeString("3e58c58c") func TestContractSend(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state, privAccounts := makeGenesisState(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()) - acc2 := state.GetAccount(privAccounts[2].PubKey.Address()) + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + acc2 := getAccount(state, privAccounts[2].Address()) state = state.Copy() - newAcc1 := state.GetAccount(acc1.Address) - newAcc1.Code = callerCode + newAcc1 := getAccount(state, acc1.Address()) + newAcc1.SetCode(callerCode) state.UpdateAccount(newAcc1) - sendData = append(sendData, word256.LeftPadBytes(acc2.Address, 32)...) - sendAmt := int64(10) - acc2Balance := acc2.Balance + sendData = append(sendData, acc2.Address().Word256().Bytes()...) + sendAmt := uint64(10) + acc2Balance := acc2.Balance() // call the contract, triggering the send tx := &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: sendAmt, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 1000, Data: sendData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - acc2 = state.GetAccount(acc2.Address) - if acc2.Balance != sendAmt+acc2Balance { - t.Errorf("Value transfer from contract failed! Got %d, expected %d", acc2.Balance, sendAmt+acc2Balance) + acc2 = getAccount(state, acc2.Address()) + if acc2.Balance() != sendAmt+acc2Balance { + t.Errorf("Value transfer from contract failed! Got %d, expected %d", acc2.Balance(), sendAmt+acc2Balance) + } +} +func TestMerklePanic(t *testing.T) { + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, + 1000) + + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + + state.Save() + // SendTx. + { + stateSendTx := state.Copy() + tx := &txs.SendTx{ + Inputs: []*txs.TxInput{ + { + Address: acc0.Address(), + Amount: 1, + Sequence: acc0.Sequence() + 1, + PubKey: acc0PubKey, + }, + }, + Outputs: []*txs.TxOutput{ + { + Address: acc1.Address(), + Amount: 1, + }, + }, + } + + tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateSendTx, tx) + if err != nil { + t.Errorf("Got error in executing send transaction, %v", err) + } + // uncomment for panic fun! + //stateSendTx.Save() + } + + // CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more + { + stateCallTx := state.Copy() + newAcc1 := getAccount(stateCallTx, acc1.Address()) + newAcc1.SetCode([]byte{0x60}) + stateCallTx.UpdateAccount(newAcc1) + tx := &txs.CallTx{ + Input: &txs.TxInput{ + Address: acc0.Address(), + Amount: 1, + Sequence: acc0.Sequence() + 1, + PubKey: acc0PubKey, + }, + Address: addressPtr(acc1), + GasLimit: 10, + } + + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateCallTx, tx) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } } + state.Save() + trygetacc0 := getAccount(state, privAccounts[0].Address()) + fmt.Println(trygetacc0.Address()) } // TODO: test overflows. // TODO: test for unbonding validators. func TestTxs(t *testing.T) { + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) - 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()) + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) // SendTx. { - state := state.Copy() + stateSendTx := state.Copy() tx := &txs.SendTx{ Inputs: []*txs.TxInput{ - &txs.TxInput{ - Address: acc0.Address, + { + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, }, Outputs: []*txs.TxOutput{ - &txs.TxOutput{ - Address: acc1.Address, + { + Address: acc1.Address(), Amount: 1, }, }, } - tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateSendTx, tx) if err != nil { t.Errorf("Got error in executing send transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if acc0.Balance-1 != newAcc0.Balance { + newAcc0 := getAccount(stateSendTx, acc0.Address()) + if acc0.Balance()-1 != newAcc0.Balance() { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-1, newAcc0.Balance) + acc0.Balance()-1, newAcc0.Balance()) } - newAcc1 := state.GetAccount(acc1.Address) - if acc1.Balance+1 != newAcc1.Balance { + newAcc1 := getAccount(stateSendTx, acc1.Address()) + if acc1.Balance()+1 != newAcc1.Balance() { t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", - acc1.Balance+1, newAcc1.Balance) + 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) + stateCallTx := state.Copy() + newAcc1 := getAccount(stateCallTx, acc1.Address()) + newAcc1.SetCode([]byte{0x60}) + stateCallTx.UpdateAccount(newAcc1) tx := &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 10, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateCallTx, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if acc0.Balance-1 != newAcc0.Balance { + newAcc0 := getAccount(stateCallTx, acc0.Address()) + if acc0.Balance()-1 != newAcc0.Balance() { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-1, newAcc0.Balance) + acc0.Balance()-1, newAcc0.Balance()) } - newAcc1 = state.GetAccount(acc1.Address) - if acc1.Balance+1 != newAcc1.Balance { + newAcc1 = getAccount(stateCallTx, acc1.Address()) + if acc1.Balance()+1 != newAcc1.Balance() { t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", - acc1.Balance+1, newAcc1.Balance) + acc1.Balance()+1, newAcc1.Balance()) } } + trygetacc0 := getAccount(state, privAccounts[0].Address()) + fmt.Println(trygetacc0.Address()) // NameTx. { @@ -648,31 +764,32 @@ attack the network, they'll generate the longest chain and outpace attackers. 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) + entryAmount := uint64(10000) - state := state.Copy() + stateNameTx := state.Copy() tx := &txs.NameTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: entryAmount, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, Name: entryName, Data: entryData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + + err := execTxWithState(stateNameTx, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if acc0.Balance-entryAmount != newAcc0.Balance { + newAcc0 := getAccount(stateNameTx, acc0.Address()) + if acc0.Balance()-entryAmount != newAcc0.Balance() { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-entryAmount, newAcc0.Balance) + acc0.Balance()-entryAmount, newAcc0.Balance()) } - entry := state.GetNameRegEntry(entryName) + entry := stateNameTx.GetNameRegEntry(entryName) if entry == nil { t.Errorf("Expected an entry but got nil") } @@ -683,8 +800,8 @@ proof-of-work chain as proof of what happened while they were gone ` // 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) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err = execTxWithState(stateNameTx, tx) if _, ok := err.(txs.ErrTxInvalidString); !ok { t.Errorf("Expected invalid string error. Got: %s", err.Error()) } @@ -695,44 +812,44 @@ proof-of-work chain as proof of what happened while they were gone ` { state := state.Copy() tx := &txs.BondTx{ - PubKey: acc0PubKey.(crypto.PubKeyEd25519), + PublicKey: acc0PubKey.(acm.PublicKeyEd25519), Inputs: []*txs.TxInput{ &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, - PubKey: acc0PubKey, + Sequence: acc0.Sequence() + 1, + PublicKey: acc0PubKey, }, }, UnbondTo: []*txs.TxOutput{ &txs.TxOutput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, }, }, } - tx.Signature = privAccounts[0].Sign(state.ChainID, tx).(crypto.SignatureEd25519) - tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Signature = privAccounts[0] acm.ChainSign(testChainID, tx).(crypto.SignatureEd25519) + tx.Inputs[0].Signature = privAccounts[0] acm.ChainSign(testChainID, tx) + err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing bond transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if newAcc0.Balance != acc0.Balance-1 { + newAcc0 := getAccount(state, acc0.Address()) + if newAcc0.Balance() != acc0.Balance()-1 { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-1, newAcc0.Balance) + acc0.Balance()-1, newAcc0.Balance()) } - _, acc0Val := state.BondedValidators.GetByAddress(acc0.Address) + _, acc0Val := state.BondedValidators.GetByAddress(acc0.Address()) if acc0Val == nil { t.Errorf("acc0Val not present") } - if acc0Val.BondHeight != state.LastBlockHeight+1 { + if acc0Val.BondHeight != blockchain.LastBlockHeight()+1 { t.Errorf("Unexpected bond height. Expected %v, got %v", - state.LastBlockHeight, acc0Val.BondHeight) + blockchain.LastBlockHeight(), acc0Val.BondHeight) } if acc0Val.VotingPower != 1 { t.Errorf("Unexpected voting power. Expected %v, got %v", - acc0Val.VotingPower, acc0.Balance) + acc0Val.VotingPower, acc0.Balance()) } if acc0Val.Accum != 0 { t.Errorf("Unexpected accum. Expected 0, got %v", @@ -746,52 +863,52 @@ proof-of-work chain as proof of what happened while they were gone ` func TestSelfDestruct(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state, privAccounts := makeGenesisState(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 + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + acc2 := getAccount(state, privAccounts[2].Address()) + sendingAmount, refundedBalance, oldBalance := uint64(1), acc1.Balance(), acc2.Balance() - newAcc1 := state.GetAccount(acc1.Address) + newAcc1 := getAccount(state, acc1.Address()) // store 0x1 at 0x1, push an address, then self-destruct:) contractCode := []byte{0x60, 0x01, 0x60, 0x01, 0x55, 0x73} - contractCode = append(contractCode, acc2.Address...) + contractCode = append(contractCode, acc2.Address().Bytes()...) contractCode = append(contractCode, 0xff) - newAcc1.Code = contractCode + newAcc1.SetCode(contractCode) state.UpdateAccount(newAcc1) // send call tx with no data, cause self-destruct - tx := txs.NewCallTxWithNonce(acc0PubKey, acc1.Address, nil, sendingAmount, 1000, 0, acc0.Sequence+1) - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + tx := txs.NewCallTxWithSequence(acc0PubKey, addressPtr(acc1), nil, sendingAmount, 1000, 0, acc0.Sequence()+1) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) // we use cache instead of execTxWithState so we can run the tx twice - cache := NewBlockCache(state) - if err := ExecTx(cache, tx, true, nil, logger); err != nil { + exe := NewBatchCommitter(state, testChainID, bcm.NewBlockchain(testGenesisDoc), event.NewNoOpFireable(), logger) + if err := exe.Execute(tx); 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 self-destruct // 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, logger); err != nil { + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + if err := exe.Execute(tx); err != nil { t.Errorf("Got error in executing call transaction, %v", err) } // commit the block - cache.Sync() + exe.Commit() // acc2 should receive the sent funds and the contracts balance - newAcc2 := state.GetAccount(acc2.Address) + newAcc2 := getAccount(state, acc2.Address()) newBalance := sendingAmount + refundedBalance + oldBalance - if newAcc2.Balance != newBalance { + if newAcc2.Balance() != newBalance { t.Errorf("Unexpected newAcc2 balance. Expected %v, got %v", - newAcc2.Balance, newBalance) + newAcc2.Balance(), newBalance) } - newAcc1 = state.GetAccount(acc1.Address) + newAcc1 = getAccount(state, acc1.Address()) if newAcc1 != nil { t.Errorf("Expected account to be removed") } @@ -802,29 +919,29 @@ func TestSelfDestruct(t *testing.T) { func TestAddValidator(t *testing.T) { // Generate a state, save & load it. - s0, privAccounts, privValidators := RandGenesisState(10, false, 1000, 1, false, 1000) + s0, privAccounts, privValidators := makeGenesisState(10, false, 1000, 1, false, 1000) // The first privAccount will become a validator acc0 := privAccounts[0] bondTx := &txs.BondTx{ - PubKey: acc0.PubKey.(account.PubKeyEd25519), + PublicKey: acc0.PublicKey.(account.PubKeyEd25519), Inputs: []*txs.TxInput{ &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1000, Sequence: 1, - PubKey: acc0.PubKey, + PublicKey: acc0.PublicKey, }, }, UnbondTo: []*txs.TxOutput{ &txs.TxOutput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1000, }, }, } - bondTx.Signature = acc0.Sign(s0.ChainID, bondTx).(account.SignatureEd25519) - bondTx.Inputs[0].Signature = acc0.Sign(s0.ChainID, bondTx) + bondTx.Signature = acc0 acm.ChainSign(testChainID, bondTx).(account.SignatureEd25519) + bondTx.Inputs[0].Signature = acc0 acm.ChainSign(testChainID, bondTx) // Make complete block and blockParts block0 := makeBlock(t, s0, nil, []txs.Tx{bondTx}) @@ -858,7 +975,7 @@ func TestAddValidator(t *testing.T) { BlockHash: block0.Hash(), BlockPartsHeader: block0Parts.Header(), } - privValidators[0].SignVote(s0.ChainID, precommit0) + privValidators[0].SignVote(testChainID, precommit0) block1 := makeBlock(t, s0, &txs.Validation{ diff --git a/execution/transactor.go b/execution/transactor.go new file mode 100644 index 0000000000000000000000000000000000000000..90df6728900fd3cbfb963916ad4b072eda3a7376 --- /dev/null +++ b/execution/transactor.go @@ -0,0 +1,469 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package execution + +import ( + "bytes" + "fmt" + "sync" + "time" + + "reflect" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/evm" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/txs" + abci_types "github.com/tendermint/abci/types" + "github.com/tendermint/go-wire" +) + +type Call struct { + Return []byte `json:"return"` + GasUsed uint64 `json:"gas_used"` +} + +type Transactor interface { + Call(fromAddress, toAddress acm.Address, data []byte) (*Call, error) + CallCode(fromAddress acm.Address, code, data []byte) (*Call, error) + BroadcastTx(tx txs.Tx) (*txs.Receipt, error) + BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error + Transact(privKey []byte, address acm.Address, data []byte, gasLimit, fee uint64) (*txs.Receipt, error) + TransactAndHold(privKey []byte, address acm.Address, data []byte, gasLimit, fee uint64) (*evm_events.EventDataCall, error) + Send(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) + SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) + TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) + SignTx(tx txs.Tx, privAccounts []acm.PrivateAccount) (txs.Tx, error) +} + +// Transactor is the controller/middleware for the v0 RPC +type transactor struct { + txMtx *sync.Mutex + blockchain blockchain.Blockchain + state acm.StateReader + eventEmitter event.Emitter + broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error + logger logging_types.InfoTraceLogger +} + +var _ Transactor = &transactor{} + +func NewTransactor(blockchain blockchain.Blockchain, state acm.StateReader, eventEmitter event.Emitter, + broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error, + logger logging_types.InfoTraceLogger) *transactor { + + return &transactor{ + blockchain: blockchain, + state: state, + eventEmitter: eventEmitter, + broadcastTxAsync: broadcastTxAsync, + logger: logger.With(structure.ComponentKey, "Transactor"), + } +} + +// Run a contract's code on an isolated and unpersisted state +// Cannot be used to create new contracts +func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) (*Call, error) { + if evm.RegisteredNativeContract(toAddress.Word256()) { + return nil, fmt.Errorf("attempt to call native contract at address "+ + "%X, but native contracts can not be called directly. Use a deployed "+ + "contract that calls the native function instead", toAddress) + } + // This was being run against CheckTx cache, need to understand the reasoning + callee, err := acm.GetMutableAccount(trans.state, toAddress) + if err != nil { + return nil, err + } + if callee == nil { + return nil, fmt.Errorf("account %s does not exist", toAddress) + } + caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() + txCache := NewTxCache(trans.state) + params := vmParams(trans.blockchain) + + vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, + logging.WithScope(trans.logger, "Call")) + vmach.SetFireable(trans.eventEmitter) + + gas := params.GasLimit + ret, err := vmach.Call(caller, callee, callee.Code(), data, 0, &gas) + if err != nil { + return nil, err + } + gasUsed := params.GasLimit - gas + return &Call{Return: ret, GasUsed: gasUsed}, nil +} + +// Run the given code on an isolated and unpersisted state +// Cannot be used to create new contracts. +func (trans *transactor) CallCode(fromAddress acm.Address, code, data []byte) (*Call, error) { + // This was being run against CheckTx cache, need to understand the reasoning + callee := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() + caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() + txCache := NewTxCache(trans.state) + params := vmParams(trans.blockchain) + + vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, + logging.WithScope(trans.logger, "CallCode")) + gas := params.GasLimit + ret, err := vmach.Call(caller, callee, code, data, 0, &gas) + if err != nil { + return nil, err + } + gasUsed := params.GasLimit - gas + return &Call{Return: ret, GasUsed: gasUsed}, nil +} + +func (trans *transactor) BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error { + return trans.broadcastTxAsync(tx, callback) +} + +// Broadcast a transaction. +func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { + responseCh := make(chan *abci_types.Response, 1) + err := trans.BroadcastTxAsync(tx, func(res *abci_types.Response) { + responseCh <- res + }) + + if err != nil { + return nil, err + } + response := <-responseCh + checkTxResponse := response.GetCheckTx() + if checkTxResponse == nil { + return nil, fmt.Errorf("application did not return CheckTx response") + } + + switch checkTxResponse.Code { + case abci_types.CodeType_OK: + receipt := new(txs.Receipt) + err := wire.ReadBinaryBytes(checkTxResponse.Data, receipt) + if err != nil { + return nil, fmt.Errorf("could not deserialise transaction receipt: %s", err) + } + return receipt, nil + default: + return nil, fmt.Errorf("error returned by Tendermint in BroadcastTxSync "+ + "ABCI code: %v, ABCI log: %v", checkTxResponse.Code, checkTxResponse.Log) + } +} + +// Orders calls to BroadcastTx using lock (waits for response from core before releasing) +func (trans *transactor) Transact(privKey []byte, address acm.Address, data []byte, gasLimit, + fee uint64) (*txs.Receipt, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) + } + trans.txMtx.Lock() + defer trans.txMtx.Unlock() + pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + if err != nil { + return nil, err + } + // [Silas] This is puzzling, if the account doesn't exist the CallTx will fail, so what's the point in this? + acc, err := trans.state.GetAccount(pa.Address()) + if err != nil { + return nil, err + } + sequence := uint64(1) + if acc != nil { + sequence = acc.Sequence() + uint64(1) + } + // TODO: [Silas] we should consider revising this method and removing fee, or + // possibly adding an amount parameter. It is non-sensical to just be able to + // set the fee. Our support of fees in general is questionable since at the + // moment all we do is deduct the fee effectively leaking token. It is possible + // someone may be using the sending of native token to payable functions but + // they can be served by broadcasting a token. + + // We hard-code the amount to be equal to the fee which means the CallTx we + // generate transfers 0 value, which is the most sensible default since in + // recent solidity compilers the EVM generated will throw an error if value + // is transferred to a non-payable function. + txInput := &txs.TxInput{ + Address: pa.Address(), + Amount: fee, + Sequence: sequence, + PubKey: pa.PublicKey(), + } + tx := &txs.CallTx{ + Input: txInput, + Address: &address, + GasLimit: gasLimit, + Fee: fee, + Data: data, + } + + // Got ourselves a tx. + txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) + if errS != nil { + return nil, errS + } + return trans.BroadcastTx(txS) +} + +func (trans *transactor) TransactAndHold(privKey []byte, address acm.Address, data []byte, gasLimit, + fee uint64) (*evm_events.EventDataCall, error) { + rec, tErr := trans.Transact(privKey, address, data, gasLimit, fee) + if tErr != nil { + return nil, tErr + } + var addr acm.Address + if rec.CreatesContract { + addr = rec.ContractAddr + } else { + addr = address + } + // We want non-blocking on the first event received (but buffer the value), + // after which we want to block (and then discard the value - see below) + wc := make(chan *evm_events.EventDataCall, 1) + subId := fmt.Sprintf("%X", rec.TxHash) + trans.eventEmitter.Subscribe(subId, evm_events.EventStringAccCall(addr), + func(eventData event.AnyEventData) { + eventDataCall := eventData.EventDataCall() + if eventDataCall == nil { + trans.logger.Info("error", "cold not be convert event data to EventDataCall", + structure.ScopeKey, "TransactAndHold", + "sub_id", subId, + "event_data_type", reflect.TypeOf(eventData.Get()).String()) + return + } + if bytes.Equal(eventDataCall.TxID, rec.TxHash) { + // Beware the contract of go-events subscribe is that we must not be + // blocking in an event callback when we try to unsubscribe! + // We work around this by using a non-blocking send. + select { + // This is a non-blocking send, but since we are using a buffered + // channel of size 1 we will always grab our first event even if we + // haven't read from the channel at the time we receive the first event. + case wc <- eventDataCall: + default: + } + } + }) + + timer := time.NewTimer(300 * time.Second) + toChan := timer.C + + var ret *evm_events.EventDataCall + var rErr error + + select { + case <-toChan: + rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) + case e := <-wc: + timer.Stop() + if e.Exception != "" { + rErr = fmt.Errorf("error when transacting: " + e.Exception) + } else { + ret = e + } + } + trans.eventEmitter.Unsubscribe(subId) + return ret, rErr +} + +func (trans *transactor) Send(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", + len(privKey)) + } + + pk := &[64]byte{} + copy(pk[:], privKey) + trans.txMtx.Lock() + defer trans.txMtx.Unlock() + pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + if err != nil { + return nil, err + } + cache := trans.state + acc, err := cache.GetAccount(pa.Address()) + if err != nil { + return nil, err + } + sequence := uint64(1) + if acc != nil { + sequence = acc.Sequence() + uint64(1) + } + + tx := txs.NewSendTx() + + txInput := &txs.TxInput{ + Address: pa.Address(), + Amount: amount, + Sequence: sequence, + PubKey: pa.PublicKey(), + } + + tx.Inputs = append(tx.Inputs, txInput) + + txOutput := &txs.TxOutput{Address: toAddress, Amount: amount} + + tx.Outputs = append(tx.Outputs, txOutput) + + // Got ourselves a tx. + txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) + if errS != nil { + return nil, errS + } + return trans.BroadcastTx(txS) +} + +func (trans *transactor) SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { + rec, tErr := trans.Send(privKey, toAddress, amount) + if tErr != nil { + return nil, tErr + } + + wc := make(chan *txs.SendTx) + subId := fmt.Sprintf("%X", rec.TxHash) + + trans.eventEmitter.Subscribe(subId, exe_events.EventStringAccOutput(toAddress), + func(eventData event.AnyEventData) { + eventDataTx, ok := eventData.Get().(exe_events.EventDataTx) + if !ok { + trans.logger.Info("error", "cold not be convert event data to EventDataCall", + structure.ScopeKey, "SendAndHold", + "tx_hash", subId, + "event_data_type", reflect.TypeOf(eventData.Get()).String()) + return + } + tx, ok := eventDataTx.Tx.(*txs.SendTx) + if !ok { + trans.logger.Info("error", "EventDataTx was expected to contain SendTx", + structure.ScopeKey, "SendAndHold", + "sub_id", subId, + "tx_type", reflect.TypeOf(eventDataTx.Tx).String()) + return + } + + wc <- tx + }) + + timer := time.NewTimer(300 * time.Second) + toChan := timer.C + + var rErr error + + pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + if tErr != nil { + return nil, err + } + + select { + case <-toChan: + rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) + case e := <-wc: + if e.Inputs[0].Address == pa.Address() && e.Inputs[0].Amount == amount { + timer.Stop() + trans.eventEmitter.Unsubscribe(subId) + return rec, rErr + } + } + return nil, rErr +} + +func (trans *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) { + + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) + } + trans.txMtx.Lock() + defer trans.txMtx.Unlock() + pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + if err != nil { + return nil, err + } + cache := trans.state // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) + acc, err := cache.GetAccount(pa.Address()) + if err != nil { + return nil, err + } + sequence := uint64(1) + if acc == nil { + sequence = acc.Sequence() + uint64(1) + } + tx := txs.NewNameTxWithSequence(pa.PublicKey(), name, data, amount, fee, sequence) + // Got ourselves a tx. + txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) + if errS != nil { + return nil, errS + } + return trans.BroadcastTx(txS) +} + +// Sign a transaction +func (trans *transactor) SignTx(tx txs.Tx, privAccounts []acm.PrivateAccount) (txs.Tx, error) { + // more checks? + + for i, privAccount := range privAccounts { + if privAccount == nil || privAccount.PrivateKey().Unwrap() == nil { + return nil, fmt.Errorf("invalid (empty) privAccount @%v", i) + } + } + chainID := trans.blockchain.ChainID() + switch tx.(type) { + case *txs.NameTx: + nameTx := tx.(*txs.NameTx) + nameTx.Input.PubKey = privAccounts[0].PublicKey() + nameTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, nameTx) + case *txs.SendTx: + sendTx := tx.(*txs.SendTx) + for i, input := range sendTx.Inputs { + input.PubKey = privAccounts[i].PublicKey() + input.Signature = acm.ChainSign(privAccounts[i], chainID, sendTx) + } + case *txs.CallTx: + callTx := tx.(*txs.CallTx) + callTx.Input.PubKey = privAccounts[0].PublicKey() + callTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, callTx) + case *txs.BondTx: + bondTx := tx.(*txs.BondTx) + // the first privaccount corresponds to the BondTx pub key. + // the rest to the inputs + bondTx.Signature = acm.ChainSign(privAccounts[0], chainID, bondTx) + for i, input := range bondTx.Inputs { + input.PubKey = privAccounts[i+1].PublicKey() + input.Signature = acm.ChainSign(privAccounts[i+1], chainID, bondTx) + } + case *txs.UnbondTx: + unbondTx := tx.(*txs.UnbondTx) + unbondTx.Signature = acm.ChainSign(privAccounts[0], chainID, unbondTx) + case *txs.RebondTx: + rebondTx := tx.(*txs.RebondTx) + rebondTx.Signature = acm.ChainSign(privAccounts[0], chainID, rebondTx) + default: + return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) + } + return tx, nil +} + +func vmParams(blockchain blockchain.Blockchain) evm.Params { + tip := blockchain.Tip() + return evm.Params{ + BlockHeight: tip.LastBlockHeight(), + BlockHash: binary.LeftPadWord256(tip.LastBlockHash()), + BlockTime: tip.LastBlockTime().Unix(), + GasLimit: GasLimit, + } +} diff --git a/execution/tx_cache.go b/execution/tx_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..4dc56485f739954c5ab9f5c7e0f70c210c66a826 --- /dev/null +++ b/execution/tx_cache.go @@ -0,0 +1,128 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package execution + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" +) + +type TxCache struct { + backend acm.StateReader + accounts map[acm.Address]vmAccountInfo + storages map[binary.Tuple256]binary.Word256 +} + +var _ acm.StateWriter = &TxCache{} + +func NewTxCache(backend acm.StateReader) *TxCache { + return &TxCache{ + backend: backend, + accounts: make(map[acm.Address]vmAccountInfo), + storages: make(map[binary.Tuple256]binary.Word256), + } +} + +//------------------------------------- +// TxCache.account + +func (cache *TxCache) GetAccount(addr acm.Address) (acm.Account, error) { + acc, removed := cache.accounts[addr].unpack() + if removed { + return nil, nil + } else if acc == nil { + return cache.backend.GetAccount(addr) + } + return acc, nil +} + +func (cache *TxCache) UpdateAccount(acc acm.Account) error { + _, removed := cache.accounts[acc.Address()].unpack() + if removed { + return fmt.Errorf("UpdateAccount on a removed account %s", acc.Address()) + } + cache.accounts[acc.Address()] = vmAccountInfo{acc, false} + return nil +} + +func (cache *TxCache) RemoveAccount(addr acm.Address) error { + acc, removed := cache.accounts[addr].unpack() + if removed { + fmt.Errorf("RemoveAccount on a removed account %s", addr) + } + cache.accounts[addr] = vmAccountInfo{acc, true} + return nil +} + +// TxCache.account +//------------------------------------- +// TxCache.storage + +func (cache *TxCache) GetStorage(addr acm.Address, key binary.Word256) (binary.Word256, error) { + // Check cache + value, ok := cache.storages[binary.Tuple256{First: addr.Word256(), Second: key}] + if ok { + return value, nil + } + + // Load from backend + return cache.backend.GetStorage(addr, key) +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *TxCache) SetStorage(addr acm.Address, key binary.Word256, value binary.Word256) error { + _, removed := cache.accounts[addr].unpack() + if removed { + fmt.Errorf("SetStorage on a removed account %s", addr) + } + cache.storages[binary.Tuple256{First: addr.Word256(), Second: key}] = value + return nil +} + +// TxCache.storage +//------------------------------------- + +// These updates do not have to be in deterministic order, +// the backend is responsible for ordering updates. +func (cache *TxCache) Sync(backend acm.StateWriter) { + // Remove or update storage + for addrKey, value := range cache.storages { + addrWord256, key := binary.Tuple256Split(addrKey) + backend.SetStorage(acm.AddressFromWord256(addrWord256), key, value) + } + + // Remove or update accounts + for addr, accInfo := range cache.accounts { + acc, removed := accInfo.unpack() + if removed { + backend.RemoveAccount(addr) + } else { + backend.UpdateAccount(acc) + } + } +} + +//----------------------------------------------------------------------------- + +type vmAccountInfo struct { + account acm.Account + removed bool +} + +func (accInfo vmAccountInfo) unpack() (acm.Account, bool) { + return accInfo.account, accInfo.removed +} diff --git a/files/files.go b/files/files.go deleted file mode 100644 index 2ab8b5152489d1fc82632b3636a0df56b30a98dc..0000000000000000000000000000000000000000 --- a/files/files.go +++ /dev/null @@ -1,130 +0,0 @@ -// Cross-platform file utils. -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package files - -import ( - "fmt" - "io/ioutil" - "os" -) - -// We don't concern ourselves with executable files here. -const ( - FILE_RW = os.FileMode(0666) - FILE_W = os.FileMode(0222) - FILE_R = os.FileMode(0444) -) - -func IsWritable(fm os.FileMode) bool { - return fm&2 == 2 -} - -// Write a file that has both read and write flags set. -func WriteFileRW(fileName string, data []byte) error { - return WriteFile(fileName, data, FILE_RW) -} - -// Write file with the read-only flag set. -func WriteFileReadOnly(fileName string, data []byte) error { - return WriteFile(fileName, data, FILE_R) -} - -// Write file with the write-only flag set. -func WriteFileWriteOnly(fileName string, data []byte) error { - return WriteFile(fileName, data, FILE_W) -} - -// WriteFile. -func WriteFile(fileName string, data []byte, perm os.FileMode) error { - f, err := os.Create(fileName) - if err != nil { - fmt.Println("ERROR OPENING: " + err.Error()) - return err - } - defer f.Close() - n, err2 := f.Write(data) - if err2 != nil { - fmt.Println("ERROR WRITING: " + err.Error()) - return err - } - if err2 == nil && n < len(data) { - return err - } - - return nil -} - -// Does the file with the given name exist? -func FileExists(fileName string) bool { - _, err := os.Stat(fileName) - return !os.IsNotExist(err) -} - -func IsRegular(fileName string) bool { - fs, err := os.Stat(fileName) - if err != nil { - return false - } - return fs.Mode().IsRegular() -} - -func WriteAndBackup(fileName string, data []byte) error { - fs, err := os.Stat(fileName) - fmt.Println("Write and backup") - if err != nil { - if os.IsNotExist(err) { - WriteFileRW(fileName, data) - return nil - } - return err - } - if !fs.Mode().IsRegular() { - return fmt.Errorf("Not a regular file: " + fileName) - } - backupName := fileName + ".bak" - fs, err = os.Stat(backupName) - if err != nil { - if !os.IsNotExist(err) { - return err - } - } else { - // We only work with regular files. - if !fs.Mode().IsRegular() { - return fmt.Errorf(backupName + " is not a regular file.") - } - errR := os.Remove(backupName) - if errR != nil { - return errR - } - } - // Backup file should now be gone. - // Read from original file. - bts, errR := ReadFile(fileName) - if errR != nil { - return errR - } - // Write it to the backup. - errW := WriteFileRW(backupName, bts) - if errW != nil { - return errW - } - // Write new bytes to original. - return WriteFileRW(fileName, data) -} - -func ReadFile(fileName string) ([]byte, error) { - return ioutil.ReadFile(fileName) -} diff --git a/files/files_test.go b/files/files_test.go deleted file mode 100644 index 1cf51762f6c7c8b3ae3d3977026318c73341b8f7..0000000000000000000000000000000000000000 --- a/files/files_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package files - -import ( - "bytes" - "os" - "path" - "testing" - - "github.com/stretchr/testify/assert" -) - -var tempFolder = os.TempDir() -var fileData = []byte("aaaaaaaaaaaabbbbbbbbbbbbbbbbbbbaeeeeeeeeeeeeeeaaaaaa") -var fileData2 = []byte("bbbbbbbbbbbb66666666666666666666664bb") - -func TestWriteRemove(t *testing.T) { - fileName := "testfile" - write(t, fileName, fileData) - remove(t, fileName) -} - -func TestWriteReadRemove(t *testing.T) { - fileName := "testfile" - write(t, fileName, fileData) - readAndCheck(t, fileName, fileData) - remove(t, fileName) -} - -func TestRenameRemove(t *testing.T) { - fileName0 := "file0" - fileName1 := "file1" - write(t, fileName0, fileData) - rename(t, fileName0, fileName1) - readAndCheck(t, fileName1, fileData) - remove(t, fileName1) - checkGone(t, fileName0) -} - -func TestWriteAndBackup(t *testing.T) { - fileName := "testfile" - backupName := "testfile.bak" - if FileExists(fileName) { - remove(t, fileName) - } - if FileExists(backupName) { - remove(t, backupName) - } - write(t, fileName, fileData) - readAndCheck(t, fileName, fileData) - WriteAndBackup(getName(fileName), fileData2) - readAndCheck(t, backupName, fileData) - remove(t, fileName) - remove(t, backupName) - checkGone(t, fileName) -} - -// Helpers - -func getName(name string) string { - return path.Join(tempFolder, name) -} - -func write(t *testing.T, fileName string, data []byte) { - err := WriteFile(getName(fileName), data, FILE_RW) - assert.NoError(t, err) -} - -func readAndCheck(t *testing.T, fileName string, btsIn []byte) { - bts, err := ReadFile(getName(fileName)) - assert.NoError(t, err) - assert.True(t, bytes.Equal(bts, btsIn), "Failed to read file data. Written: %s, Read: %s\n", string(fileData), string(bts)) -} - -func remove(t *testing.T, fileName string) { - err := os.Remove(getName(fileName)) - assert.NoError(t, err) - checkGone(t, fileName) -} - -func rename(t *testing.T, fileName0, fileName1 string) { - assert.NoError(t, Rename(getName(fileName0), getName(fileName1))) -} - -func checkGone(t *testing.T, fileName string) { - name := getName(fileName) - _, err := os.Stat(name) - assert.True(t, os.IsNotExist(err), "File not removed: "+name) -} diff --git a/files/posix.go b/files/posix.go deleted file mode 100644 index 037f40dd7cd12c5c679e89be35661b2029e7e5d2..0000000000000000000000000000000000000000 --- a/files/posix.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build !windows - -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package files - -import "os" - -// Rename for linux and macs etc. Don't really care about the rest. -func Rename(oldname, newname string) error { - return os.Rename(oldname, newname) -} diff --git a/files/windows.go b/files/windows.go deleted file mode 100644 index 27a7fd84a447a8843730ac517bc9605832cb39d0..0000000000000000000000000000000000000000 --- a/files/windows.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build windows - -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package files - -import ( - "fmt" - "os" -) - -// TODO finish up. -func Rename(oldname, newname string) error { - - // Some extra fluff here. - if fs, err := os.Stat(newname); !os.IsNotExist(err) { - if fs.Mode().IsRegular() && isWritable(fs.Mode().Perm()) { - errRM := os.Remove(newname) - if errRM != nil { - return errRM - } - } else { - return fmt.Errorf("Target exists and cannot be over-written (is a directory or read-only file): " + newname) - } - } - errRN := os.Rename(oldname, newname) - if errRN != nil { - return errRN - } - - return nil -} diff --git a/genesis/deterministic_genesis.go b/genesis/deterministic_genesis.go new file mode 100644 index 0000000000000000000000000000000000000000..19d94cbf3cbd1d7c922732fdac67e4b6d5acd0f1 --- /dev/null +++ b/genesis/deterministic_genesis.go @@ -0,0 +1,89 @@ +package genesis + +import ( + "fmt" + "math/rand" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" +) + +type deterministicGenesis struct { + random *rand.Rand +} + +// Generates deterministic pseudo-random genesis state +func NewDeterministicGenesis(seed int64) *deterministicGenesis { + return &deterministicGenesis{ + random: rand.New(rand.NewSource(seed)), + } +} + +func (dg *deterministicGenesis) GenesisDoc(numAccounts int, randBalance bool, minBalance uint64, numValidators int, + randBonded bool, minBonded int64) (*GenesisDoc, []acm.PrivateAccount) { + + accounts := make([]Account, numAccounts) + privAccounts := make([]acm.PrivateAccount, numAccounts) + defaultPerms := permission.DefaultAccountPermissions + for i := 0; i < numAccounts; i++ { + account, privAccount := dg.Account(randBalance, minBalance) + accounts[i] = Account{ + BasicAccount: BasicAccount{ + Address: account.Address(), + Amount: account.Balance(), + }, + Permissions: defaultPerms.Clone(), // This will get copied into each state.Account. + } + privAccounts[i] = privAccount + } + validators := make([]Validator, numValidators) + for i := 0; i < numValidators; i++ { + validator := acm.GeneratePrivateAccountFromSecret(fmt.Sprintf("val_%v", i)) + validators[i] = Validator{ + BasicAccount: BasicAccount{ + Address: validator.Address(), + PublicKey: validator.PublicKey(), + Amount: uint64(dg.random.Int63()), + }, + UnbondTo: []BasicAccount{ + { + Address: validator.Address(), + Amount: uint64(dg.random.Int63()), + }, + }, + } + } + return &GenesisDoc{ + ChainName: "TestChain", + GenesisTime: time.Unix(1506172037, 0), + Accounts: accounts, + Validators: validators, + }, privAccounts + +} + +func (dg *deterministicGenesis) Account(randBalance bool, minBalance uint64) (acm.Account, acm.PrivateAccount) { + privKey, err := acm.GeneratePrivateKey(dg.random) + if err != nil { + panic(fmt.Errorf("could not generate private key deterministically")) + } + pubKey := acm.PublicKeyFromGoCryptoPubKey(privKey.PubKey()) + privAccount := &acm.ConcretePrivateAccount{ + PublicKey: pubKey, + PrivateKey: privKey, + Address: pubKey.Address(), + } + perms := permission.DefaultAccountPermissions + acc := &acm.ConcreteAccount{ + Address: privAccount.Address, + PublicKey: privAccount.PublicKey, + Sequence: uint64(dg.random.Int()), + Balance: minBalance, + Permissions: perms, + } + if randBalance { + acc.Balance += uint64(dg.random.Int()) + } + return acc.Account(), privAccount.PrivateAccount() +} diff --git a/genesis/gen_test.go b/genesis/gen_test.go deleted file mode 100644 index 3a55eea7df1cbbba038cfb07be248dc383f5c889..0000000000000000000000000000000000000000 --- a/genesis/gen_test.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "testing" - "time" -) - -// set the chain ID -var chainID string = "genesis-file-maker-test" - -var genesisFileExpected string = `{ - "genesis_time": "0001-01-01T00:00:00.000Z", - "chain_id": "genesis-file-maker-test", - "params": null, - "accounts": [ - { - "address": "74417C1BEFB3938B71B22B202050A4C6591FFCF6", - "amount": 9999999999, - "name": "genesis-file-maker-test_developer_000", - "permissions": { - "base": { - "perms": 14430, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "0C9DAEA4046491A661FCE0B41B0CAA2AD3415268", - "amount": 99999999999999, - "name": "genesis-file-maker-test_full_000", - "permissions": { - "base": { - "perms": 16383, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "E1BD50A1B90A15861F5CF0F182D291F556B21A86", - "amount": 9999999999, - "name": "genesis-file-maker-test_participant_000", - "permissions": { - "base": { - "perms": 2118, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "A6C8E2DE652DB8ADB4036293DC21F8FE389D77C2", - "amount": 9999999999, - "name": "genesis-file-maker-test_root_000", - "permissions": { - "base": { - "perms": 16383, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "E96CB7910001320B6F1E2266A8431D5E98FF0183", - "amount": 9999999999, - "name": "genesis-file-maker-test_validator_000", - "permissions": { - "base": { - "perms": 32, - "set": 16383 - }, - "roles": [] - } - } - ], - "validators": [ - { - "pub_key": [ - 1, - "238E1A77CC7CDCD13F4D77841F1FE4A46A77DB691EC89718CD0D4CB3409F61D2" - ], - "amount": 9999999999, - "name": "genesis-file-maker-test_full_000", - "unbond_to": [ - { - "address": "0C9DAEA4046491A661FCE0B41B0CAA2AD3415268", - "amount": 9999999999 - } - ] - }, - { - "pub_key": [ - 1, - "7F53D78C526F96C87ACBD0D2B9DB2E9FC176981623D26B1DB1CF59748EE9F4CF" - ], - "amount": 9999999998, - "name": "genesis-file-maker-test_validator_000", - "unbond_to": [ - { - "address": "E96CB7910001320B6F1E2266A8431D5E98FF0183", - "amount": 9999999998 - } - ] - } - ] -}` - -var accountsCSV string = `F0BD5CE45D306D61C9AB73CE5268C2B59D52CAF7127EF0E3B65523302254350A,9999999999,genesis-file-maker-test_developer_000,14430,16383 -238E1A77CC7CDCD13F4D77841F1FE4A46A77DB691EC89718CD0D4CB3409F61D2,99999999999999,genesis-file-maker-test_full_000,16383,16383 -E37A655E560D53721C9BB06BA742398323504DFE2EB2C67E71F8D16E71E0471B,9999999999,genesis-file-maker-test_participant_000,2118,16383 -EC0E38CC8308EC9E720EE839242A7BC5C781D1F852E962FAC5A8E0599CE5B224,9999999999,genesis-file-maker-test_root_000,16383,16383 -7F53D78C526F96C87ACBD0D2B9DB2E9FC176981623D26B1DB1CF59748EE9F4CF,9999999999,genesis-file-maker-test_validator_000,32,16383` - -var validatorsCSV string = `238E1A77CC7CDCD13F4D77841F1FE4A46A77DB691EC89718CD0D4CB3409F61D2,9999999999,genesis-file-maker-test_full_000,16383,16383 -7F53D78C526F96C87ACBD0D2B9DB2E9FC176981623D26B1DB1CF59748EE9F4CF,9999999998,genesis-file-maker-test_validator_000,32,16383` - -func TestKnownCSV(t *testing.T) { - // make temp dir - dir, err := ioutil.TempDir(os.TempDir(), "genesis-file-maker-test") - if err != nil { - t.Fatal(err) - } - - defer func() { - //cleanup - os.RemoveAll(dir) - if err != nil { - t.Fatal(err) - } - - }() - - // set the filepaths to be written to - accountsCSVpath := filepath.Join(dir, "accounts.csv") - validatorsCSVpath := filepath.Join(dir, "validators.csv") - - // write the accounts.csv - if err := ioutil.WriteFile(accountsCSVpath, []byte(accountsCSV), 0600); err != nil { - t.Fatal(err) - } - - // write the validators.csv - if err := ioutil.WriteFile(validatorsCSVpath, []byte(validatorsCSV), 0600); err != nil { - t.Fatal(err) - } - - // create the genesis file - // NOTE: [ben] set time to zero time, "genesis_time": "0001-01-01T00:00:00.000Z" - genesisFileWritten, err := generateKnownWithTime(chainID, accountsCSVpath, validatorsCSVpath, time.Time{}) - if err != nil { - t.Fatal(err) - } - - // compare - if !bytes.Equal([]byte(genesisFileExpected), []byte(genesisFileWritten)) { - t.Fatalf("Bad genesis file: got (%s), expected (%s)", genesisFileWritten, genesisFileExpected) - } - -} diff --git a/genesis/genesis.go b/genesis/genesis.go index 4eb99cbbc64c498b45b816386c0295601c905001..7d62594e5f60e35b7ec3055d41f9629874ec6075 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -15,70 +15,214 @@ package genesis import ( - "bytes" + "crypto/sha256" "encoding/json" + "fmt" + "sort" "time" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" - wire "github.com/tendermint/go-wire" ) -// MakeGenesisDocFromAccounts takes a chainName and a slice of pointers to GenesisAccount, -// and a slice of pointers to GenesisValidator to construct a GenesisDoc, or returns an error on -// failure. In particular MakeGenesisDocFromAccount uses the local time as a -// timestamp for the GenesisDoc. -func MakeGenesisDocFromAccounts(chainName string, accounts []*GenesisAccount, - validators []*GenesisValidator) (GenesisDoc, error) { - - // TODO: assert valid accounts and validators - // TODO: [ben] expose setting global permissions - globalPermissions := ptypes.DefaultAccountPermissions.Clone() - genesisParameters := &GenesisParams{ - GlobalPermissions: &globalPermissions, +// How many bytes to take from the front of the GenesisDoc hash to append +// to the ChainName to form the ChainID. The idea is to avoid some classes +// of replay attack between chains with the same name. +const ShortHashSuffixBytes = 3 + +// we store the GenesisDoc in the db under this key + +var GenDocKey = []byte("GenDocKey") + +//------------------------------------------------------------ +// core types for a genesis definition + +type BasicAccount struct { + // Address is convenient to have in file for reference, but otherwise ignored since derived from PublicKey + Address acm.Address + PublicKey acm.PublicKey + Amount uint64 +} + +type Account struct { + BasicAccount + Name string + Permissions ptypes.AccountPermissions +} + +type Validator struct { + BasicAccount + Name string + UnbondTo []BasicAccount +} + +//------------------------------------------------------------ +// GenesisDoc is stored in the state database + +type GenesisDoc struct { + GenesisTime time.Time + ChainName string + Salt []byte `json:",omitempty"` + GlobalPermissions ptypes.AccountPermissions + Accounts []Account + Validators []Validator +} + +// JSONBytes returns the JSON (not-yet) canonical bytes for a given +// GenesisDoc or an error. +func (genesisDoc *GenesisDoc) JSONBytes() ([]byte, error) { + // TODO: write JSON in canonical order + return json.MarshalIndent(genesisDoc, "", "\t") +} + +func (genesisDoc *GenesisDoc) Hash() []byte { + genesisDocBytes, err := genesisDoc.JSONBytes() + if err != nil { + panic(fmt.Errorf("could not create hash of GenesisDoc: %v", err)) } - // copy slice of pointers to accounts into slice of accounts - accountsCopy := make([]GenesisAccount, len(accounts)) - for i, genesisAccount := range accounts { - accountsCopy[i] = genesisAccount.Clone() + hasher := sha256.New() + hasher.Write(genesisDocBytes) + return hasher.Sum(nil) +} + +func (genesisDoc *GenesisDoc) ShortHash() []byte { + return genesisDoc.Hash()[:ShortHashSuffixBytes] +} + +func (genesisDoc *GenesisDoc) ChainID() string { + return fmt.Sprintf("%s-%X", genesisDoc.ChainName, genesisDoc.ShortHash()) +} + +//------------------------------------------------------------ +// Make genesis state from file + +func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) { + genDoc := new(GenesisDoc) + err := json.Unmarshal(jsonBlob, genDoc) + if err != nil { + return nil, fmt.Errorf("couldn't read GenesisDoc: %v", err) } - // copy slice of pointers to validators into slice of validators - validatorsCopy := make([]GenesisValidator, len(validators)) - for i, genesisValidator := range validators { - genesisValidatorCopy, err := genesisValidator.Clone() - if err != nil { - return GenesisDoc{}, err - } - validatorsCopy[i] = genesisValidatorCopy + return genDoc, nil +} + +//------------------------------------------------------------ +// Account methods + +func GenesisAccountFromAccount(name string, account acm.Account) Account { + return Account{ + Name: name, + Permissions: account.Permissions(), + BasicAccount: BasicAccount{ + Address: account.Address(), + Amount: account.Balance(), + }, } - genesisDoc := GenesisDoc{ - GenesisTime: time.Now(), - // TODO: this needs to be corrected for ChainName, and ChainId - // is the derived hash from the GenesisDoc serialised bytes - ChainID: chainName, - Params: genesisParameters, - Accounts: accountsCopy, - Validators: validatorsCopy, +} + +// Clone clones the genesis account +func (genesisAccount *Account) Clone() Account { + // clone the account permissions + return Account{ + BasicAccount: BasicAccount{ + Address: genesisAccount.Address, + Amount: genesisAccount.Amount, + }, + Name: genesisAccount.Name, + Permissions: genesisAccount.Permissions.Clone(), } - return genesisDoc, nil } -// GetGenesisFileBytes returns the JSON (not-yet) canonical bytes for a given -// GenesisDoc or an error. In a first rewrite, rely on go-wire -// for the JSON serialisation with type-bytes. -func GetGenesisFileBytes(genesisDoc *GenesisDoc) ([]byte, error) { +//------------------------------------------------------------ +// Validator methods - // TODO: write JSON in canonical order - var err error - buffer, n := new(bytes.Buffer), new(int) - // write JSON with go-wire type-bytes (for public keys) - wire.WriteJSON(genesisDoc, buffer, n, &err) - if err != nil { - return nil, err +func (gv *Validator) Validator() acm.Validator { + return acm.ConcreteValidator{ + Address: gv.PublicKey.Address(), + PublicKey: gv.PublicKey, + Power: uint64(gv.Amount), + }.Validator() +} + +// Clone clones the genesis validator +func (gv *Validator) Clone() Validator { + // clone the addresses to unbond to + unbondToClone := make([]BasicAccount, len(gv.UnbondTo)) + for i, basicAccount := range gv.UnbondTo { + unbondToClone[i] = basicAccount.Clone() + } + return Validator{ + BasicAccount: BasicAccount{ + PublicKey: gv.PublicKey, + Amount: gv.Amount, + }, + Name: gv.Name, + UnbondTo: unbondToClone, + } +} + +//------------------------------------------------------------ +// BasicAccount methods + +// Clone clones the basic account +func (basicAccount *BasicAccount) Clone() BasicAccount { + return BasicAccount{ + Address: basicAccount.Address, + Amount: basicAccount.Amount, + } +} + +// MakeGenesisDocFromAccounts takes a chainName and a slice of pointers to Account, +// and a slice of pointers to Validator to construct a GenesisDoc, or returns an error on +// failure. In particular MakeGenesisDocFromAccount uses the local time as a +// timestamp for the GenesisDoc. +func MakeGenesisDocFromAccounts(chainName string, salt []byte, genesisTime time.Time, accounts map[string]acm.Account, + validators map[string]acm.Validator) *GenesisDoc { + + // Establish deterministic order of accounts by name so we obtain identical GenesisDoc + // from identical input + names := make([]string, 0, len(accounts)) + for name := range accounts { + names = append(names, name) + } + sort.Strings(names) + // copy slice of pointers to accounts into slice of accounts + genesisAccounts := make([]Account, 0, len(accounts)) + for _, name := range names { + genesisAccounts = append(genesisAccounts, GenesisAccountFromAccount(name, accounts[name])) + } + // Sigh... + names = names[:0] + for name := range validators { + names = append(names, name) + } + sort.Strings(names) + // copy slice of pointers to validators into slice of validators + genesisValidators := make([]Validator, 0, len(validators)) + for _, name := range names { + val := validators[name] + genesisValidators = append(genesisValidators, Validator{ + Name: name, + BasicAccount: BasicAccount{ + Address: val.Address(), + PublicKey: val.PublicKey(), + Amount: val.Power(), + }, + // Simpler to just do this by convention + UnbondTo: []BasicAccount{ + { + Amount: val.Power(), + Address: val.Address(), + }, + }, + }) } - // rewrite buffer with indentation - indentedBuffer := new(bytes.Buffer) - if err := json.Indent(indentedBuffer, buffer.Bytes(), "", "\t"); err != nil { - return nil, err + return &GenesisDoc{ + ChainName: chainName, + Salt: salt, + GenesisTime: genesisTime, + GlobalPermissions: permission.DefaultAccountPermissions.Clone(), + Accounts: genesisAccounts, + Validators: genesisValidators, } - return indentedBuffer.Bytes(), nil } diff --git a/genesis/genesis_test.go b/genesis/genesis_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0c5021bb0a0b07f3ad6d10a96e003d807cbbf305 --- /dev/null +++ b/genesis/genesis_test.go @@ -0,0 +1,59 @@ +package genesis + +import ( + "fmt" + "testing" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" +) + +var genesisTime, _ = time.Parse("02-01-2006", "27-10-2017") + +func TestMakeGenesisDocFromAccounts(t *testing.T) { + genDoc := MakeGenesisDocFromAccounts("test-chain", nil, genesisTime, + accountMap("Tinkie-winkie", "Lala", "Po", "Dipsy"), + validatorMap("Foo", "Bar", "Baz"), + ) + + // Check we have matching serialisation after a round trip + bs, err := genDoc.JSONBytes() + assert.NoError(t, err) + + genDocOut, err := GenesisDocFromJSON(bs) + assert.NoError(t, err) + + bsOut, err := genDocOut.JSONBytes() + assert.NoError(t, err) + + assert.Equal(t, bs, bsOut) + assert.Equal(t, genDoc.Hash(), genDocOut.Hash()) + fmt.Println(string(bs)) +} + +func accountMap(names ...string) map[string]acm.Account { + accounts := make(map[string]acm.Account, len(names)) + for _, name := range names { + accounts[name] = accountFromName(name) + } + return accounts +} + +func validatorMap(names ...string) map[string]acm.Validator { + validators := make(map[string]acm.Validator, len(names)) + for _, name := range names { + validators[name] = acm.AsValidator(accountFromName(name)) + } + return validators +} + +func accountFromName(name string) acm.Account { + ca := acm.NewConcreteAccountFromSecret(name) + for _, c := range name { + ca.Balance += uint64(c) + } + ca.Permissions = permission.AllAccountPermissions.Clone() + return ca.Account() +} diff --git a/genesis/make_genesis_file.go b/genesis/make_genesis_file.go deleted file mode 100644 index 624b73d23f351ad1649aecd44287e792d4ebe054..0000000000000000000000000000000000000000 --- a/genesis/make_genesis_file.go +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "bytes" - "encoding/csv" - "encoding/hex" - "encoding/json" - "fmt" - "os" - "strconv" - "time" - - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/util" - - "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" -) - -//------------------------------------------------------------------------------------ -// interface functions that are consumed by monax tooling -// TODO: [ben] these interfaces will be deprecated from v0.17 - -func GenerateKnown(chainID, accountsPathCSV, validatorsPathCSV string) (string, error) { - return generateKnownWithTime(chainID, accountsPathCSV, validatorsPathCSV, - // set the timestamp for the genesis - time.Now()) -} - -//------------------------------------------------------------------------------------ -// interface functions that are consumed by monax tooling - -func GenerateGenesisFileBytes(chainName string, genesisAccounts []*GenesisAccount, - genesisValidators []*GenesisValidator) ([]byte, error) { - genesisDoc, err := MakeGenesisDocFromAccounts(chainName, genesisAccounts, genesisValidators) - - buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int) - wire.WriteJSON(genesisDoc, buf, n, &err) - if err != nil { - return nil, err - } - if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil { - return nil, err - } - - return buf2.Bytes(), nil -} - -//------------------------------------------------------------------------------------ -// core functions that provide functionality for monax tooling in v0.16 - -// GenerateKnownWithTime takes chainId, an accounts and validators CSV filepath -// and a timestamp to generate the string of `genesis.json` -// NOTE: [ben] is introduced as technical debt to preserve the signature -// of GenerateKnown but in order to introduce the timestamp gradually -// This will be deprecated in v0.17 -func generateKnownWithTime(chainID, accountsPathCSV, validatorsPathCSV string, - genesisTime time.Time) (string, error) { - var genDoc *GenesisDoc - - // TODO [eb] eliminate reading priv_val ... [zr] where? - if accountsPathCSV == "" || validatorsPathCSV == "" { - return "", fmt.Errorf("both accounts.csv and validators.csv is required") - } - - pubkeys, amts, names, perms, setbits, err := parseCsv(validatorsPathCSV) - if err != nil { - return "", err - } - - pubkeysA, amtsA, namesA, permsA, setbitsA, err := parseCsv(accountsPathCSV) - if err != nil { - return "", err - } - - genDoc = newGenDoc(chainID, genesisTime, len(pubkeys), len(pubkeysA)) - for i, pk := range pubkeys { - genDocAddValidator(genDoc, pk, amts[i], names[i], perms[i], setbits[i], i) - } - for i, pk := range pubkeysA { - genDocAddAccount(genDoc, pk, amtsA[i], namesA[i], permsA[i], setbitsA[i], i) - } - - buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int) - wire.WriteJSON(genDoc, buf, n, &err) - if err != nil { - return "", err - } - if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil { - return "", err - } - - return buf2.String(), nil -} - -//----------------------------------------------------------------------------- -// gendoc convenience functions - -func newGenDoc(chainID string, genesisTime time.Time, nVal, nAcc int) *GenesisDoc { - genDoc := GenesisDoc{ - ChainID: chainID, - GenesisTime: genesisTime, - } - genDoc.Accounts = make([]GenesisAccount, nAcc) - genDoc.Validators = make([]GenesisValidator, nVal) - return &genDoc -} - -func genDocAddAccount(genDoc *GenesisDoc, pubKey crypto.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) { - addr := pubKey.Address() - acc := GenesisAccount{ - Address: addr, - Amount: amt, - Name: name, - Permissions: &ptypes.AccountPermissions{ - Base: ptypes.BasePermissions{ - Perms: perm, - SetBit: setbit, - }, - }, - } - if index < 0 { - genDoc.Accounts = append(genDoc.Accounts, acc) - } else { - genDoc.Accounts[index] = acc - } -} - -func genDocAddValidator(genDoc *GenesisDoc, pubKey crypto.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) { - addr := pubKey.Address() - genDoc.Validators[index] = GenesisValidator{ - PubKey: pubKey, - Amount: amt, - Name: name, - UnbondTo: []BasicAccount{ - { - Address: addr, - Amount: amt, - }, - }, - } - // [zr] why no index < 0 like in genDocAddAccount? -} - -//----------------------------------------------------------------------------- -// util functions - -// convert hex strings to ed25519 pubkeys -func pubKeyStringsToPubKeys(pubkeys []string) ([]crypto.PubKeyEd25519, error) { - pubKeys := make([]crypto.PubKeyEd25519, len(pubkeys)) - for i, k := range pubkeys { - pubBytes, err := hex.DecodeString(k) - if err != nil { - return pubKeys, err - } - copy(pubKeys[i][:], pubBytes) - } - return pubKeys, nil -} - -// empty is over written -func ifExistsElse(list []string, index int, defaultValue string) string { - if len(list) > index { - if list[index] != "" { - return list[index] - } - } - return defaultValue -} - -// takes a csv in the following format: pubkey, starting balance, name, permissions, setbit -func parseCsv(filePath string) (pubKeys []crypto.PubKeyEd25519, amts []int64, names []string, perms, setbits []ptypes.PermFlag, err error) { - - csvFile, err := os.Open(filePath) - if err != nil { - util.Fatalf("Couldn't open file: %s: %v", filePath, err) - } - defer csvFile.Close() - - r := csv.NewReader(csvFile) - //r.FieldsPerRecord = # of records expected - params, err := r.ReadAll() - if err != nil { - util.Fatalf("Couldn't read file: %v", err) - - } - - pubkeys := make([]string, len(params)) - amtS := make([]string, len(params)) - names = make([]string, len(params)) - permsS := make([]string, len(params)) - setbitS := make([]string, len(params)) - for i, each := range params { - pubkeys[i] = each[0] - amtS[i] = ifExistsElse(each, 1, "1000") - names[i] = ifExistsElse(each, 2, "") - permsS[i] = ifExistsElse(each, 3, fmt.Sprintf("%d", ptypes.DefaultPermFlags)) - setbitS[i] = ifExistsElse(each, 4, permsS[i]) - } - - //TODO convert int to uint64, see issue #25 - perms = make([]ptypes.PermFlag, len(permsS)) - for i, perm := range permsS { - pflag, err := strconv.Atoi(perm) - if err != nil { - util.Fatalf("Permissions (%v) must be an integer", perm) - } - perms[i] = ptypes.PermFlag(pflag) - } - setbits = make([]ptypes.PermFlag, len(setbitS)) - for i, setbit := range setbitS { - setbitsFlag, err := strconv.Atoi(setbit) - if err != nil { - util.Fatalf("SetBits (%v) must be an integer", setbit) - } - setbits[i] = ptypes.PermFlag(setbitsFlag) - } - - // convert amts to ints - amts = make([]int64, len(amtS)) - for i, a := range amtS { - if amts[i], err = strconv.ParseInt(a, 10, 64); err != nil { - err = fmt.Errorf("Invalid amount: %v", err) - return - } - } - - // convert pubkey hex strings to struct - pubKeys, err = pubKeyStringsToPubKeys(pubkeys) - if err != nil { - return - } - - return pubKeys, amts, names, perms, setbits, nil -} diff --git a/genesis/maker.go b/genesis/maker.go deleted file mode 100644 index 48f09c563e0cd49ad786be7e235091d82946e436..0000000000000000000000000000000000000000 --- a/genesis/maker.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "fmt" - - ptypes "github.com/hyperledger/burrow/permission/types" - - "github.com/tendermint/go-crypto" -) - -const ( - PublicKeyEd25519ByteLength int = 32 - PublicKeySecp256k1ByteLength int = 64 -) - -// NewGenesisAccount returns a new GenesisAccount -func NewGenesisAccount(address []byte, amount int64, name string, - permissions *ptypes.AccountPermissions) *GenesisAccount { - return &GenesisAccount{ - Address: address, - Amount: amount, - Name: name, - Permissions: permissions, - } -} - -func NewGenesisValidator(amount int64, name string, unbondToAddress []byte, - unbondAmount int64, keyType string, publicKeyBytes []byte) (*GenesisValidator, error) { - // convert the key bytes into a typed fixed size byte array - var typedPublicKeyBytes []byte - switch keyType { - case "ed25519": // ed25519 has type byte 0x01 - // TODO: [ben] functionality and checks need to be inherit in the type - if len(publicKeyBytes) != PublicKeyEd25519ByteLength { - return nil, fmt.Errorf("Invalid length provided for ed25519 public key (%v bytes provided but expected %v bytes)", - len(publicKeyBytes), PublicKeyEd25519ByteLength) - } - // prepend type byte to public key - typedPublicKeyBytes = append([]byte{crypto.TypeEd25519}, publicKeyBytes...) - case "secp256k1": // secp256k1 has type byte 0x02 - if len(publicKeyBytes) != PublicKeySecp256k1ByteLength { - return nil, fmt.Errorf("Invalid length provided for secp256k1 public key (%v bytes provided but expected %v bytes)", - len(publicKeyBytes), PublicKeySecp256k1ByteLength) - } - // prepend type byte to public key - typedPublicKeyBytes = append([]byte{crypto.TypeSecp256k1}, publicKeyBytes...) - default: - return nil, fmt.Errorf("Unsupported key type (%s)", keyType) - } - newPublicKey, err := crypto.PubKeyFromBytes(typedPublicKeyBytes) - if err != nil { - return nil, err - } - // ability to unbond to multiple accounts currently unused - var unbondTo []BasicAccount - - return &GenesisValidator{ - PubKey: newPublicKey, - Amount: unbondAmount, - Name: name, - UnbondTo: append(unbondTo, BasicAccount{ - Address: unbondToAddress, - Amount: unbondAmount, - }), - }, nil -} diff --git a/genesis/spec/genesis_spec.go b/genesis/spec/genesis_spec.go new file mode 100644 index 0000000000000000000000000000000000000000..be653182b652a63522e27cb158ed3f7462a48265 --- /dev/null +++ b/genesis/spec/genesis_spec.go @@ -0,0 +1,233 @@ +package spec + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" +) + +const DefaultAmount uint64 = 1000000 +const DefaultAmountBonded uint64 = 10000 + +// A GenesisSpec is schematic representation of a genesis state, that is it is a template +// for a GenesisDoc excluding that which needs to be instantiated at the point of genesis +// so it describes the type and number of accounts, the genesis salt, but not the +// account keys or addresses, or the GenesisTime. It is responsible for generating keys +// by interacting with the KeysClient it is passed and other information not known at +// specification time +type GenesisSpec struct { + GenesisTime *time.Time `json:",omitempty"` + ChainName string `json:",omitempty"` + Salt []byte `json:",omitempty"` + GlobalPermissions []string `json:",omitempty"` + Accounts []TemplateAccount `json:",omitempty"` +} + +type TemplateAccount struct { + // Address is convenient to have in file for reference, but otherwise ignored since derived from PublicKey + Address *acm.Address `json:",omitempty"` + PubKey *acm.PublicKey `json:",omitempty"` + Amount *uint64 `json:",omitempty"` + // If any bonded amount then this account is also a Validator + AmountBonded *uint64 `json:",omitempty"` + Name string `json:",omitempty"` + Permissions []string `json:",omitempty"` + Roles []string `json:",omitempty"` +} + +func (ta TemplateAccount) Validator(keyClient keys.KeyClient, index int) (*genesis.Validator, error) { + var err error + gv := new(genesis.Validator) + gv.PublicKey, gv.Address, err = ta.RealisePubKeyAndAddress(keyClient) + if err != nil { + return nil, err + } + if ta.AmountBonded == nil { + gv.Amount = DefaultAmountBonded + } else { + gv.Amount = *ta.AmountBonded + } + if ta.Name == "" { + gv.Name = accountNameFromIndex(index) + } else { + gv.Name = ta.Name + } + + gv.UnbondTo = []genesis.BasicAccount{{ + Address: gv.Address, + PublicKey: gv.PublicKey, + Amount: gv.Amount, + }} + return gv, nil +} + +func (ta TemplateAccount) AccountPermissions() (ptypes.AccountPermissions, error) { + basePerms, err := permission.BasePermissionsFromStringList(ta.Permissions) + if err != nil { + return permission.ZeroAccountPermissions, nil + } + return ptypes.AccountPermissions{ + Base: basePerms, + Roles: ta.Roles, + }, nil +} + +func (ta TemplateAccount) Account(keyClient keys.KeyClient, index int) (*genesis.Account, error) { + var err error + ga := new(genesis.Account) + ga.PublicKey, ga.Address, err = ta.RealisePubKeyAndAddress(keyClient) + if err != nil { + return nil, err + } + if ta.Amount == nil { + ga.Amount = DefaultAmount + } else { + ga.Amount = *ta.Amount + } + if ta.Name == "" { + ga.Name = accountNameFromIndex(index) + } else { + ga.Name = ta.Name + } + if ta.Permissions == nil { + ga.Permissions = permission.DefaultAccountPermissions.Clone() + } else { + ga.Permissions, err = ta.AccountPermissions() + if err != nil { + return nil, err + } + } + return ga, nil +} + +// Adds a public key and address to the template. If PublicKey will try to fetch it by Address. +// If both PublicKey and Address are not set will use the keyClient to generate a new keypair +func (ta TemplateAccount) RealisePubKeyAndAddress(keyClient keys.KeyClient) (pubKey acm.PublicKey, address acm.Address, err error) { + if ta.PubKey == nil { + if ta.Address == nil { + // If neither PublicKey or Address set then generate a new one + address, err = keyClient.Generate(ta.Name, keys.KeyTypeEd25519Ripemd160) + if err != nil { + return + } + } else { + address = *ta.Address + } + // Get the (possibly existing) key + pubKey, err = keyClient.PublicKey(address) + if err != nil { + return + } + } else { + address := ta.PubKey.Address() + if ta.Address != nil && *ta.Address != address { + err = fmt.Errorf("template address %s does not match public key derived address %s", ta.Address, + ta.PubKey) + } + } + return +} + +// Produce a fully realised GenesisDoc from a template GenesisDoc that may omit values +func (gs *GenesisSpec) GenesisDoc(keyClient keys.KeyClient) (*genesis.GenesisDoc, error) { + genesisDoc := new(genesis.GenesisDoc) + if gs.GenesisTime == nil { + genesisDoc.GenesisTime = time.Now() + } else { + genesisDoc.GenesisTime = *gs.GenesisTime + } + + if gs.ChainName == "" { + genesisDoc.ChainName = fmt.Sprintf("BurrowChain_%X", gs.ShortHash()) + } else { + genesisDoc.ChainName = gs.ChainName + } + + if len(gs.GlobalPermissions) == 0 { + genesisDoc.GlobalPermissions = permission.DefaultAccountPermissions.Clone() + } else { + basePerms, err := permission.BasePermissionsFromStringList(gs.GlobalPermissions) + if err != nil { + return nil, err + } + genesisDoc.GlobalPermissions = ptypes.AccountPermissions{ + Base: basePerms, + } + } + + templateAccounts := gs.Accounts + if len(gs.Accounts) == 0 { + amountBonded := DefaultAmountBonded + templateAccounts = append(templateAccounts, TemplateAccount{ + AmountBonded: &amountBonded, + }) + } + + for i, templateAccount := range templateAccounts { + account, err := templateAccount.Account(keyClient, i) + if err != nil { + return nil, fmt.Errorf("could not create Account from template: %v", err) + } + genesisDoc.Accounts = append(genesisDoc.Accounts, *account) + // Create a corresponding validator + if templateAccount.AmountBonded != nil { + // Note this does not modify the input template + templateAccount.Address = &account.Address + validator, err := templateAccount.Validator(keyClient, i) + if err != nil { + return nil, fmt.Errorf("could not create Validator from template: %v", err) + } + genesisDoc.Validators = append(genesisDoc.Validators, *validator) + } + } + + return genesisDoc, nil +} + +func (gs *GenesisSpec) JSONBytes() ([]byte, error) { + bs, err := json.Marshal(gs) + if err != nil { + return nil, err + } + // rewrite buffer with indentation + indentedBuffer := new(bytes.Buffer) + if err := json.Indent(indentedBuffer, bs, "", "\t"); err != nil { + return nil, err + } + return indentedBuffer.Bytes(), nil +} + +func (gs *GenesisSpec) Hash() []byte { + gsBytes, err := gs.JSONBytes() + if err != nil { + panic(fmt.Errorf("could not create hash of GenesisDoc: %v", err)) + } + hasher := sha256.New() + hasher.Write(gsBytes) + return hasher.Sum(nil) +} + +func (gs *GenesisSpec) ShortHash() []byte { + return gs.Hash()[:genesis.ShortHashSuffixBytes] +} + +func GenesisSpecFromJSON(jsonBlob []byte) (*GenesisSpec, error) { + genDoc := new(GenesisSpec) + err := json.Unmarshal(jsonBlob, genDoc) + if err != nil { + return nil, fmt.Errorf("couldn't read GenesisSpec: %v", err) + } + return genDoc, nil +} + +func accountNameFromIndex(index int) string { + return fmt.Sprintf("Account_%v", index) +} diff --git a/genesis/spec/genesis_spec_test.go b/genesis/spec/genesis_spec_test.go new file mode 100644 index 0000000000000000000000000000000000000000..19dae19ffe1a6b1bf03ca69c8c5e3c16600545d9 --- /dev/null +++ b/genesis/spec/genesis_spec_test.go @@ -0,0 +1,91 @@ +package spec + +import ( + "fmt" + "testing" + + "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/keys/mock" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesisSpec_GenesisDoc(t *testing.T) { + keyClient := mock.NewMockKeyClient() + + // Try a spec with a single account/validator + amtBonded := uint64(100) + genesisSpec := GenesisSpec{ + Accounts: []TemplateAccount{{ + AmountBonded: &amtBonded, + }}, + } + + genesisDoc, err := genesisSpec.GenesisDoc(keyClient) + require.NoError(t, err) + require.Len(t, genesisDoc.Accounts, 1) + // Should create validator + require.Len(t, genesisDoc.Validators, 1) + assert.NotZero(t, genesisDoc.Accounts[0].Address) + assert.NotZero(t, genesisDoc.Accounts[0].PublicKey) + assert.Equal(t, genesisDoc.Accounts[0].Address, genesisDoc.Validators[0].Address) + assert.Equal(t, genesisDoc.Accounts[0].PublicKey, genesisDoc.Validators[0].PublicKey) + assert.Equal(t, amtBonded, genesisDoc.Validators[0].Amount) + assert.NotEmpty(t, genesisDoc.ChainName, "Chain name should not be empty") + + address, err := keyClient.Generate("test-lookup-of-key", keys.KeyTypeEd25519Ripemd160) + require.NoError(t, err) + pubKey, err := keyClient.PublicKey(address) + require.NoError(t, err) + + // Try a spec with two accounts and no validators + amt := uint64(99299299) + genesisSpec = GenesisSpec{ + Accounts: []TemplateAccount{ + { + Address: &address, + }, + { + Amount: &amt, + Permissions: []string{permission.CreateAccountString, permission.CallString}, + }}, + } + + genesisDoc, err = genesisSpec.GenesisDoc(keyClient) + require.NoError(t, err) + + require.Len(t, genesisDoc.Accounts, 2) + // Nothing bonded so no validators + require.Len(t, genesisDoc.Validators, 0) + assert.Equal(t, pubKey, genesisDoc.Accounts[0].PublicKey) + assert.Equal(t, amt, genesisDoc.Accounts[1].Amount) + permFlag := permission.CreateAccount | permission.Call + assert.Equal(t, permFlag, genesisDoc.Accounts[1].Permissions.Base.Perms) + assert.Equal(t, permFlag, genesisDoc.Accounts[1].Permissions.Base.SetBit) + + // Try an empty spec + genesisSpec = GenesisSpec{} + + genesisDoc, err = genesisSpec.GenesisDoc(keyClient) + require.NoError(t, err) + + // Similar assersions to first case - should generate our default single identity chain + require.Len(t, genesisDoc.Accounts, 1) + // Should create validator + require.Len(t, genesisDoc.Validators, 1) + assert.NotZero(t, genesisDoc.Accounts[0].Address) + assert.NotZero(t, genesisDoc.Accounts[0].PublicKey) + assert.Equal(t, genesisDoc.Accounts[0].Address, genesisDoc.Validators[0].Address) + assert.Equal(t, genesisDoc.Accounts[0].PublicKey, genesisDoc.Validators[0].PublicKey) +} + +func TestJSONRoundTrip(t *testing.T) { + + var a []byte + + b := []byte{1, 2, 3} + + c := append(b, a...) + fmt.Println(c) +} diff --git a/genesis/spec/presets.go b/genesis/spec/presets.go new file mode 100644 index 0000000000000000000000000000000000000000..28f3ac94e0c77857976ec37b610c4bb2459138dc --- /dev/null +++ b/genesis/spec/presets.go @@ -0,0 +1,117 @@ +package spec + +import ( + "fmt" + + "sort" + + "github.com/hyperledger/burrow/permission" +) + +// Files here can be used as starting points for building various 'chain types' but are otherwise +// a fairly unprincipled collection of GenesisSpecs that we find useful in testing and development + +func FullAccount(index int) GenesisSpec { + // Inheriting from the arbitrary figures used by monax tool for now + amount := uint64(99999999999999) + amountBonded := uint64(9999999999) + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Full_%v", index), + Amount: &amount, + AmountBonded: &amountBonded, + Permissions: []string{permission.AllString}, + }, + }, + } +} + +func RootAccount(index int) GenesisSpec { + // Inheriting from the arbitrary figures used by monax tool for now + amount := uint64(99999999999999) + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Root_%v", index), + Amount: &amount, + Permissions: []string{permission.AllString}, + }, + }, + } +} + +func ParticipantAccount(index int) GenesisSpec { + // Inheriting from the arbitrary figures used by monax tool for now + amount := uint64(9999999999) + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Participant_%v", index), + Amount: &amount, + Permissions: []string{permission.SendString, permission.CallString, permission.NameString, + permission.HasRoleString}, + }}, + } +} + +func DeveloperAccount(index int) GenesisSpec { + // Inheriting from the arbitrary figures used by monax tool for now + amount := uint64(9999999999) + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Developer_%v", index), + Amount: &amount, + Permissions: []string{permission.SendString, permission.CallString, permission.CreateContractString, + permission.CreateAccountString, permission.NameString, permission.HasRoleString, + permission.RemoveRoleString}, + }}, + } +} + +func ValidatorAccount(index int) GenesisSpec { + // Inheriting from the arbitrary figures used by monax tool for now + amount := uint64(9999999999) + amountBonded := amount - 1 + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Validator_%v", index), + Amount: &amount, + AmountBonded: &amountBonded, + Permissions: []string{permission.BondString}, + }}, + } +} + +func MergeGenesisSpecs(genesisSpecs ...GenesisSpec) GenesisSpec { + mergedGenesisSpec := GenesisSpec{} + // We will deduplicate and merge global permissions flags + permSet := make(map[string]bool) + + for _, genesisSpec := range genesisSpecs { + // We'll overwrite chain name for later specs + if genesisSpec.ChainName != "" { + mergedGenesisSpec.ChainName = genesisSpec.ChainName + } + // Take the max genesis time + if mergedGenesisSpec.GenesisTime == nil || + (genesisSpec.GenesisTime != nil && genesisSpec.GenesisTime.After(*mergedGenesisSpec.GenesisTime)) { + mergedGenesisSpec.GenesisTime = genesisSpec.GenesisTime + } + + for _, permString := range genesisSpec.GlobalPermissions { + permSet[permString] = true + } + + mergedGenesisSpec.Salt = append(mergedGenesisSpec.Salt, genesisSpec.Salt...) + mergedGenesisSpec.Accounts = append(mergedGenesisSpec.Accounts, genesisSpec.Accounts...) + } + + mergedGenesisSpec.GlobalPermissions = make([]string, 0, len(permSet)) + + for permString := range permSet { + mergedGenesisSpec.GlobalPermissions = append(mergedGenesisSpec.GlobalPermissions, permString) + } + + // Make sure merged GenesisSpec is deterministic on inputs + sort.Strings(mergedGenesisSpec.GlobalPermissions) + + return mergedGenesisSpec +} diff --git a/genesis/spec/presets_test.go b/genesis/spec/presets_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dc65fd9e92c34671439f0d5e21744ee2b83db7dc --- /dev/null +++ b/genesis/spec/presets_test.go @@ -0,0 +1,35 @@ +package spec + +import ( + "testing" + + "github.com/hyperledger/burrow/keys/mock" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMergeGenesisSpecAccounts(t *testing.T) { + keyClient := mock.NewMockKeyClient() + gs := MergeGenesisSpecs(FullAccount(0), ParticipantAccount(1), ParticipantAccount(2)) + gd, err := gs.GenesisDoc(keyClient) + require.NoError(t, err) + assert.Len(t, gd.Validators, 1) + assert.Len(t, gd.Accounts, 3) + //bs, err := gd.JSONBytes() + //require.NoError(t, err) + //fmt.Println(string(bs)) +} + +func TestMergeGenesisSpecGlobalPermissions(t *testing.T) { + gs1 := GenesisSpec{ + GlobalPermissions: []string{permission.CreateAccountString, permission.CreateAccountString}, + } + gs2 := GenesisSpec{ + GlobalPermissions: []string{permission.SendString, permission.CreateAccountString, permission.HasRoleString}, + } + + gsMerged := MergeGenesisSpecs(gs1, gs2) + assert.Equal(t, []string{permission.CreateAccountString, permission.HasRoleString, permission.SendString}, + gsMerged.GlobalPermissions) +} diff --git a/genesis/types.go b/genesis/types.go deleted file mode 100644 index 5f1d4ada090dc8465e3b43f76e8fa8414de35390..0000000000000000000000000000000000000000 --- a/genesis/types.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "fmt" - "os" - "time" - - ptypes "github.com/hyperledger/burrow/permission/types" - - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" -) - -//------------------------------------------------------------ -// we store the GenesisDoc in the db under this key - -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 crypto.PubKey `json:"pub_key"` - Amount int64 `json:"amount"` - Name string `json:"name"` - UnbondTo []BasicAccount `json:"unbond_to"` -} - -// GenesisPrivateValidator marshals the state of the private -// validator for the purpose of Genesis creation; and hence -// is defined in genesis and not under consensus, where -// PrivateValidator (currently inherited from Tendermint) is. -type GenesisPrivateValidator struct { - Address string `json:"address"` - PubKey []interface{} `json:"pub_key"` - PrivKey []interface{} `json:"priv_key"` - LastHeight int64 `json:"last_height"` - LastRound int64 `json:"last_round"` - LastStep int64 `json:"last_step"` -} - -type GenesisParams struct { - GlobalPermissions *ptypes.AccountPermissions `json:"global_permissions"` -} - -//------------------------------------------------------------ -// GenesisDoc is stored in the state database - -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 { - fmt.Printf("Couldn't read GenesisDoc: %v", err) - // TODO: on error return error, not exit - os.Exit(1) - } - return -} - -//------------------------------------------------------------ -// Methods for genesis types -// NOTE: breaks formatting convention -// TODO: split each genesis type in its own file definition - -//------------------------------------------------------------ -// GenesisAccount methods - -// Clone clones the genesis account -func (genesisAccount *GenesisAccount) Clone() GenesisAccount { - // clone the address - addressClone := make([]byte, len(genesisAccount.Address)) - copy(addressClone, genesisAccount.Address) - // clone the account permissions - accountPermissionsClone := genesisAccount.Permissions.Clone() - return GenesisAccount{ - Address: addressClone, - Amount: genesisAccount.Amount, - Name: genesisAccount.Name, - Permissions: &accountPermissionsClone, - } -} - -//------------------------------------------------------------ -// GenesisValidator methods - -// Clone clones the genesis validator -func (genesisValidator *GenesisValidator) Clone() (GenesisValidator, error) { - if genesisValidator == nil { - return GenesisValidator{}, fmt.Errorf("Cannot clone nil GenesisValidator.") - } - if genesisValidator.PubKey == nil { - return GenesisValidator{}, fmt.Errorf("Invalid GenesisValidator %s with nil public key.", - genesisValidator.Name) - } - // clone the addresses to unbond to - unbondToClone := make([]BasicAccount, len(genesisValidator.UnbondTo)) - for i, basicAccount := range genesisValidator.UnbondTo { - unbondToClone[i] = basicAccount.Clone() - } - return GenesisValidator{ - PubKey: genesisValidator.PubKey, - Amount: genesisValidator.Amount, - Name: genesisValidator.Name, - UnbondTo: unbondToClone, - }, nil -} - -//------------------------------------------------------------ -// BasicAccount methods - -// Clone clones the basic account -func (basicAccount *BasicAccount) Clone() BasicAccount { - // clone the address - addressClone := make([]byte, len(basicAccount.Address)) - copy(addressClone, basicAccount.Address) - return BasicAccount{ - Address: addressClone, - Amount: basicAccount.Amount, - } -} diff --git a/keys/config.go b/keys/config.go new file mode 100644 index 0000000000000000000000000000000000000000..2286cabf282d14f97602bad3b7250b0f03a0bf78 --- /dev/null +++ b/keys/config.go @@ -0,0 +1,12 @@ +package keys + +type KeysConfig struct { + URL string +} + +func DefaultKeysConfig() *KeysConfig { + return &KeysConfig{ + // Default Monax keys port + URL: "http://localhost:4767", + } +} diff --git a/keys/key_client.go b/keys/key_client.go index 99046e5e316960de264d56b3041ec9491e136236..2afb5ce3132ccdd266dab9795cc35234479d17a2 100644 --- a/keys/key_client.go +++ b/keys/key_client.go @@ -18,31 +18,102 @@ import ( "encoding/hex" "fmt" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/logging" logging_types "github.com/hyperledger/burrow/logging/types" ) type KeyClient interface { - // Sign needs to return the signature bytes for given message to sign - // and the address to sign it with. - Sign(signBytesString string, signAddress []byte) (signature []byte, err error) - // PublicKey needs to return the public key associated with a given address - PublicKey(address []byte) (publicKey []byte, err error) + // Sign returns the signature bytes for given message signed with the key associated with signAddress + Sign(signAddress acm.Address, message []byte) (signature acm.Signature, err error) + + // PublicKey returns the public key associated with a given address + PublicKey(address acm.Address) (publicKey acm.PublicKey, err error) + + // Generate requests that a key be generate within the keys instance and returns the address + Generate(keyName string, keyType KeyType) (keyAddress acm.Address, err error) + + // Returns nil if the keys instance is healthy, error otherwise + HealthCheck() error +} + +// This mirrors "github.com/monax/keys/crypto/KeyType" but since we have no use for the struct here it seems simpler +// to replicate rather than cop an import +type KeyType string + +func (kt KeyType) String() string { + return string(kt) } -// NOTE [ben] Compiler check to ensure monaxKeyClient successfully implements +const ( + KeyTypeEd25519Ripemd160 KeyType = "ed25519,ripemd160" + KeyTypeEd25519Ripemd160sha256 = "ed25519,ripemd160sha256" + KeyTypeEd25519Ripemd160sha3 = "ed25519,sha3" + KeyTypeSecp256k1Ripemd160 = "secp256k1,ripemd160" + KeyTypeSecp256k1Ripemd160sha256 = "secp256k1,ripemd160sha256" + KeyTypeSecp256k1Ripemd160sha3 = "secp256k1,sha3" + KeyTypeDefault = KeyTypeEd25519Ripemd160 +) + +// NOTE [ben] Compiler check to ensure keyClient successfully implements // burrow/keys.KeyClient -var _ KeyClient = (*monaxKeyClient)(nil) +var _ KeyClient = (*keyClient)(nil) -type monaxKeyClient struct { +type keyClient struct { rpcString string logger logging_types.InfoTraceLogger } -// monaxKeyClient.New returns a new monax-keys client for provided rpc location +type signer struct { + keyClient KeyClient + address acm.Address +} + +// Creates a Signer that assumes the address holds an Ed25519 key +func Signer(keyClient KeyClient, address acm.Address) acm.Signer { + // TODO: we can do better than this and return a typed signature when we reform the keys service + return &signer{ + keyClient: keyClient, + address: address, + } +} + +type keyAddressable struct { + publicKey acm.PublicKey + address acm.Address +} + +func (ka *keyAddressable) Address() acm.Address { + return ka.address +} + +func (ka *keyAddressable) PublicKey() acm.PublicKey { + return ka.publicKey +} + +func Addressable(keyClient KeyClient, address acm.Address) (acm.Addressable, error) { + pubKey, err := keyClient.PublicKey(address) + if err != nil { + return nil, err + } + return &keyAddressable{ + address: address, + publicKey: pubKey, + }, nil +} + +func (ms *signer) Sign(messsage []byte) (acm.Signature, error) { + signature, err := ms.keyClient.Sign(ms.address, messsage) + if err != nil { + return acm.Signature{}, err + } + return signature, nil +} + +// keyClient.New returns a new monax-keys client for provided rpc location // Monax-keys connects over http request-responses -func NewBurrowKeyClient(rpcString string, logger logging_types.InfoTraceLogger) *monaxKeyClient { - return &monaxKeyClient{ +func NewBurrowKeyClient(rpcString string, logger logging_types.InfoTraceLogger) *keyClient { + return &keyClient{ rpcString: rpcString, logger: logging.WithScope(logger, "BurrowKeyClient"), } @@ -50,34 +121,61 @@ func NewBurrowKeyClient(rpcString string, logger logging_types.InfoTraceLogger) // Monax-keys client Sign requests the signature from BurrowKeysClient over rpc for the given // bytes to be signed and the address to sign them with. -func (monaxKeys *monaxKeyClient) Sign(signBytesString string, signAddress []byte) (signature []byte, err error) { +func (monaxKeys *keyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) { args := map[string]string{ - "msg": signBytesString, - "hash": signBytesString, // TODO:[ben] backwards compatibility - "addr": fmt.Sprintf("%X", signAddress), + "msg": hex.EncodeToString(message), + "addr": signAddress.String(), } sigS, err := RequestResponse(monaxKeys.rpcString, "sign", args, monaxKeys.logger) if err != nil { - return + return acm.Signature{}, err } sigBytes, err := hex.DecodeString(sigS) if err != nil { - return + return acm.Signature{}, err } - return sigBytes, err + return acm.SignatureFromBytes(sigBytes) } // Monax-keys client PublicKey requests the public key associated with an address from // the monax-keys server. -func (monaxKeys *monaxKeyClient) PublicKey(address []byte) (publicKey []byte, err error) { +func (monaxKeys *keyClient) PublicKey(address acm.Address) (acm.PublicKey, error) { args := map[string]string{ - "addr": fmt.Sprintf("%X", address), + "addr": address.String(), } pubS, err := RequestResponse(monaxKeys.rpcString, "pub", args, monaxKeys.logger) if err != nil { - return + return acm.PublicKey{}, err + } + pubKeyBytes, err := hex.DecodeString(pubS) + if err != nil { + return acm.PublicKey{}, err + } + publicKey, err := acm.PublicKeyFromBytes(pubKeyBytes) + if err != nil { + return acm.PublicKey{}, err + } + if address != publicKey.Address() { + return acm.PublicKey{}, fmt.Errorf("public key %s maps to address %s but was returned for address %s", + publicKey, publicKey.Address(), address) + } + return publicKey, nil +} + +func (monaxKeys *keyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) { + args := map[string]string{ + //"auth": auth, + "name": keyName, + "type": keyType.String(), + } + addr, err := RequestResponse(monaxKeys.rpcString, "gen", args, monaxKeys.logger) + if err != nil { + return acm.ZeroAddress, err } - // TODO: [ben] assert that received public key results in - // address - return hex.DecodeString(pubS) + return acm.AddressFromHexString(addr) +} + +func (monaxKeys *keyClient) HealthCheck() error { + _, err := RequestResponse(monaxKeys.rpcString, "name/ls", nil, monaxKeys.logger) + return err } diff --git a/keys/key_client_test.go b/keys/key_client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..127a677e7d4d24c6a365534117864391e717e932 --- /dev/null +++ b/keys/key_client_test.go @@ -0,0 +1,87 @@ +package keys + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/monax/keys/monax-keys" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//var logger, _ = lifecycle.NewStdErrLogger() +var logger = loggers.NewNoopInfoTraceLogger() + +const keysHost = "localhost" +const keysPort = "56757" + +var rpcString = fmt.Sprintf("http://%s:%s", keysHost, keysPort) + +func TestMain(m *testing.M) { + var err error + keys.KeysDir, err = ioutil.TempDir("", "key_client_test") + if err != nil { + fatalf("couldn't create temp dir: %v", err) + } + go keys.StartServer(keysHost, keysPort) + os.Exit(m.Run()) +} + +func TestMonaxKeyClient_Generate(t *testing.T) { + keyClient := NewBurrowKeyClient(rpcString, logger) + addr, err := keyClient.Generate("I'm a lovely hat", KeyTypeEd25519Ripemd160) + assert.NoError(t, err) + assert.NotEqual(t, acm.ZeroAddress, addr) +} + +func TestMonaxKeyClient_PublicKey(t *testing.T) { + keyClient := NewBurrowKeyClient(rpcString, logger) + addr, err := keyClient.Generate("I'm a lovely hat", KeyTypeEd25519Ripemd160) + assert.NoError(t, err) + pubKey, err := keyClient.PublicKey(addr) + assert.Equal(t, addr, pubKey.Address()) +} + +func TestMonaxKeyClient_PublicKey_NonExistent(t *testing.T) { + keyClient := NewBurrowKeyClient(rpcString, logger) + _, err := keyClient.PublicKey(acm.Address{8, 7, 6, 222}) + assert.Error(t, err) +} + +func TestMonaxKeyClient_Sign(t *testing.T) { + keyClient := NewBurrowKeyClient(rpcString, logger) + addr, err := keyClient.Generate("I'm a lovely hat", KeyTypeEd25519Ripemd160) + require.NoError(t, err) + pubKey, err := keyClient.PublicKey(addr) + assert.NoError(t, err) + message := []byte("I'm a hat, a hat, a hat") + signature, err := keyClient.Sign(addr, message) + assert.NoError(t, err) + assert.True(t, pubKey.VerifyBytes(message, signature), "signature should verify message") +} + +func TestMonaxKeyClient_HealthCheck(t *testing.T) { + deadKeyClient := NewBurrowKeyClient("http://localhost:99999", logger) + assert.NotNil(t, deadKeyClient.HealthCheck()) + keyClient := NewBurrowKeyClient(rpcString, logger) + assert.Nil(t, keyClient.HealthCheck()) +} + +func TestPublicKeyAddressAgreement(t *testing.T) { + keyClient := NewBurrowKeyClient(rpcString, logger) + addr, err := keyClient.Generate("I'm a lovely hat", KeyTypeEd25519Ripemd160) + require.NoError(t, err) + pubKey, err := keyClient.PublicKey(addr) + addrOut := pubKey.Address() + require.NoError(t, err) + assert.Equal(t, addr, addrOut) +} + +func fatalf(format string, a ...interface{}) { + fmt.Fprintf(os.Stderr, format, a...) + os.Exit(1) +} diff --git a/keys/key_client_util.go b/keys/key_client_util.go index c0677f993cb841526a5a3527b028daffe9aef4f0..ab553031f6600d239903d64e6430598f9455f3c0 100644 --- a/keys/key_client_util.go +++ b/keys/key_client_util.go @@ -56,7 +56,7 @@ func RequestResponse(addr, method string, args map[string]string, logger logging } logging.TraceMsg(logger, "Received response from key server", "endpoint", endpoint, - "request body", string(body), + "request_body", string(body), "response", res, ) return res, nil diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go index 291ec748e25ce51e13fcb23b9bf11f986eb2ef7b..53b311aed212eba77c4c7e42ca8fe36e2946e33c 100644 --- a/keys/mock/key_client_mock.go +++ b/keys/mock/key_client_mock.go @@ -16,14 +16,12 @@ package mock import ( "crypto/rand" - "encoding/hex" "fmt" + acm "github.com/hyperledger/burrow/account" . "github.com/hyperledger/burrow/keys" - - // NOTE: prior to building out /crypto, use - // tendermint/go-crypto for the mock client "github.com/tendermint/ed25519" + crypto "github.com/tendermint/go-crypto" "golang.org/x/crypto/ripemd160" ) @@ -32,14 +30,13 @@ import ( // Simple ed25519 key structure for mock purposes with ripemd160 address type MockKey struct { - Address []byte + Address acm.Address PrivateKey [ed25519.PrivateKeySize]byte PublicKey []byte } func newMockKey() (*MockKey, error) { key := &MockKey{ - Address: make([]byte, 20), PublicKey: make([]byte, ed25519.PublicKeySize), } // this is a mock key, so the entropy of the source is purely @@ -55,15 +52,15 @@ func newMockKey() (*MockKey, error) { typedPublicKeyBytes := append([]byte{0x01}, key.PublicKey...) hasher := ripemd160.New() hasher.Write(typedPublicKeyBytes) - key.Address = hasher.Sum(nil) + key.Address, err = acm.AddressFromBytes(hasher.Sum(nil)) + if err != nil { + return nil, err + } return key, nil } -func (mockKey *MockKey) Sign(message []byte) ([]byte, error) { - signatureBytes := make([]byte, ed25519.SignatureSize) - signature := ed25519.Sign(&mockKey.PrivateKey, message) - copy(signatureBytes[:], signature[:]) - return signatureBytes, nil +func (mockKey *MockKey) Sign(message []byte) (acm.Signature, error) { + return acm.SignatureFromBytes(ed25519.Sign(&mockKey.PrivateKey, message)[:]) } //--------------------------------------------------------------------- @@ -73,41 +70,47 @@ func (mockKey *MockKey) Sign(message []byte) ([]byte, error) { var _ KeyClient = (*MockKeyClient)(nil) type MockKeyClient struct { - knownKeys map[string]*MockKey + knownKeys map[acm.Address]*MockKey } func NewMockKeyClient() *MockKeyClient { return &MockKeyClient{ - knownKeys: make(map[string]*MockKey), + knownKeys: make(map[acm.Address]*MockKey), } } -func (mock *MockKeyClient) NewKey() (address []byte) { +func (mock *MockKeyClient) NewKey() acm.Address { // Only tests ED25519 curve and ripemd160. key, err := newMockKey() if err != nil { panic(fmt.Sprintf("Mocked key client failed on key generation: %s", err)) } - mock.knownKeys[fmt.Sprintf("%X", key.Address)] = key + mock.knownKeys[key.Address] = key return key.Address } -func (mock *MockKeyClient) Sign(signBytesString string, signAddress []byte) ([]byte, error) { - key := mock.knownKeys[fmt.Sprintf("%X", signAddress)] +func (mock *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) { + key := mock.knownKeys[signAddress] if key == nil { - return nil, fmt.Errorf("Unknown address (%X)", signAddress) - } - signBytes, err := hex.DecodeString(signBytesString) - if err != nil { - return nil, fmt.Errorf("Sign bytes string is invalid hex string: %s", err.Error()) + return acm.Signature{}, fmt.Errorf("Unknown address (%s)", signAddress) } - return key.Sign(signBytes) + return key.Sign(message) } -func (mock *MockKeyClient) PublicKey(address []byte) (publicKey []byte, err error) { - key := mock.knownKeys[fmt.Sprintf("%X", address)] +func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) { + key := mock.knownKeys[address] if key == nil { - return nil, fmt.Errorf("Unknown address (%X)", address) + return acm.PublicKey{}, fmt.Errorf("Unknown address (%s)", address) } - return key.PublicKey, nil + pubKeyEd25519 := crypto.PubKeyEd25519{} + copy(pubKeyEd25519[:], key.PublicKey) + return acm.PublicKeyFromGoCryptoPubKey(pubKeyEd25519.Wrap()), nil +} + +func (mock *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) { + return mock.NewKey(), nil +} + +func (mock *MockKeyClient) HealthCheck() error { + return nil } diff --git a/logging/adapters/tendermint_log15/capture.go b/logging/adapters/tendermint_log15/capture.go deleted file mode 100644 index 1ad8550fa8335842338ad3c4e4a29999db288279..0000000000000000000000000000000000000000 --- a/logging/adapters/tendermint_log15/capture.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tendermint_log15 - -import ( - "time" - - kitlog "github.com/go-kit/kit/log" - "github.com/go-stack/stack" - "github.com/hyperledger/burrow/logging/structure" - "github.com/hyperledger/burrow/logging/types" - . "github.com/hyperledger/burrow/util/slice" - "github.com/tendermint/log15" -) - -type infoTraceLoggerAsLog15Handler struct { - logger types.InfoTraceLogger -} - -var _ log15.Handler = (*infoTraceLoggerAsLog15Handler)(nil) - -type log15HandlerAsKitLogger struct { - handler log15.Handler -} - -var _ kitlog.Logger = (*log15HandlerAsKitLogger)(nil) - -func (l *log15HandlerAsKitLogger) Log(keyvals ...interface{}) error { - record := LogLineToRecord(keyvals...) - return l.handler.Log(record) -} - -func (h *infoTraceLoggerAsLog15Handler) Log(record *log15.Record) error { - if record.Lvl < log15.LvlDebug { - // Send to Critical, Warning, Error, and Info to the Info channel - h.logger.Info(RecordToLogLine(record)...) - } else { - // Send to Debug to the Trace channel - h.logger.Trace(RecordToLogLine(record)...) - } - return nil -} - -func Log15HandlerAsKitLogger(handler log15.Handler) kitlog.Logger { - return &log15HandlerAsKitLogger{ - handler: handler, - } -} - -func InfoTraceLoggerAsLog15Handler(logger types.InfoTraceLogger) log15.Handler { - return &infoTraceLoggerAsLog15Handler{ - logger: logger, - } -} - -// Convert a go-kit log line (i.e. keyvals... interface{}) into a log15 record -// This allows us to use log15 output handlers -func LogLineToRecord(keyvals ...interface{}) *log15.Record { - vals, ctx := structure.ValuesAndContext(keyvals, structure.TimeKey, - structure.MessageKey, structure.CallerKey, structure.LevelKey) - - // Mapping of log line to Record is on a best effort basis - theTime, _ := vals[structure.TimeKey].(time.Time) - call, _ := vals[structure.CallerKey].(stack.Call) - level, _ := vals[structure.LevelKey].(string) - message, _ := vals[structure.MessageKey].(string) - - return &log15.Record{ - Time: theTime, - Lvl: Log15LvlFromString(level), - Msg: message, - Call: call, - Ctx: ctx, - KeyNames: log15.RecordKeyNames{ - Time: structure.TimeKey, - Msg: structure.MessageKey, - Lvl: structure.LevelKey, - }} -} - -// Convert a log15 record to a go-kit log line (i.e. keyvals... interface{}) -// This allows us to capture output from dependencies using log15 -func RecordToLogLine(record *log15.Record) []interface{} { - return Concat( - Slice( - structure.CallerKey, record.Call, - structure.LevelKey, record.Lvl.String(), - ), - record.Ctx, - Slice( - structure.MessageKey, record.Msg, - )) -} - -// Collapse our weak notion of leveling and log15's into a log15.Lvl -func Log15LvlFromString(level string) log15.Lvl { - if level == "" { - return log15.LvlDebug - } - switch level { - case types.InfoLevelName: - return log15.LvlInfo - case types.TraceLevelName: - return log15.LvlDebug - default: - lvl, err := log15.LvlFromString(level) - if err == nil { - return lvl - } - return log15.LvlDebug - } -} diff --git a/logging/config/config.go b/logging/config/config.go index f620f816a5cad755db7db81266f0204919faef48..9eba4085bbb8a47a0cabd45f003e548690541a8d 100644 --- a/logging/config/config.go +++ b/logging/config/config.go @@ -6,16 +6,18 @@ import ( "github.com/hyperledger/burrow/logging/structure" + "encoding/json" + "github.com/BurntSushi/toml" ) type LoggingConfig struct { - RootSink *SinkConfig `toml:"root_sink,omitempty"` + RootSink *SinkConfig `toml:",omitempty"` } // For encoding a top-level '[logging]' TOML table type LoggingConfigWrapper struct { - Logging *LoggingConfig `toml:"logging"` + Logging *LoggingConfig `toml:",omitempty"` } func DefaultNodeLoggingConfig() *LoggingConfig { @@ -36,11 +38,19 @@ func DefaultClientLoggingConfig() *LoggingConfig { // Returns the TOML for a top-level logging config wrapped with [logging] func (lc *LoggingConfig) RootTOMLString() string { - return tomlString(LoggingConfigWrapper{lc}) + return TOMLString(LoggingConfigWrapper{lc}) } func (lc *LoggingConfig) TOMLString() string { - return tomlString(lc) + return TOMLString(lc) +} + +func (lc *LoggingConfig) RootJSONString() string { + return JSONString(LoggingConfigWrapper{lc}) +} + +func (lc *LoggingConfig) JSONString() string { + return JSONString(lc) } func LoggingConfigFromMap(loggingRootMap map[string]interface{}) (*LoggingConfig, error) { @@ -65,7 +75,7 @@ func LoggingConfigFromMap(loggingRootMap map[string]interface{}) (*LoggingConfig return lc, nil } -func tomlString(v interface{}) string { +func TOMLString(v interface{}) string { buf := new(bytes.Buffer) encoder := toml.NewEncoder(buf) err := encoder.Encode(v) @@ -75,3 +85,11 @@ func tomlString(v interface{}) string { } return buf.String() } + +func JSONString(v interface{}) string { + bs, err := json.MarshalIndent(v, "", "\t") + if err != nil { + return fmt.Sprintf("Error encoding JSON: %s", err) + } + return string(bs) +} diff --git a/logging/config/config_test.go b/logging/config/config_test.go index de53dd3a73ae256dffd3367ff10ae39e30346571..089710aa7e9cf5ec24ffcfba1977df09443704d6 100644 --- a/logging/config/config_test.go +++ b/logging/config/config_test.go @@ -1,42 +1,32 @@ package config import ( - "strings" "testing" "github.com/BurntSushi/toml" - "github.com/spf13/viper" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var complexConfig *LoggingConfig = &LoggingConfig{ - RootSink: Sink(). - SetTransform(LabelTransform(false, "Info", "Trace")). - AddSinks( - Sink(). - SetOutput(StdoutOutput()). - SetTransform(FilterTransform(ExcludeWhenAnyMatches, - "Foo", "Bars")). - AddSinks( - Sink(). - SetOutput(RemoteSyslogOutput("Eris-db", "tcp://example.com:6514")), - Sink(). - SetOutput(StdoutOutput()), - ), - ), -} - func TestLoggingConfig_String(t *testing.T) { + complexConfig := &LoggingConfig{ + RootSink: Sink(). + SetTransform(LabelTransform(false, "Info", "Trace")). + AddSinks( + Sink(). + SetOutput(StdoutOutput()). + SetTransform(FilterTransform(ExcludeWhenAnyMatches, + "Foo", "Bars")). + AddSinks( + Sink(). + SetOutput(StderrOutput()), + Sink(). + SetOutput(StdoutOutput()), + ), + ), + } lc := new(LoggingConfig) - toml.Decode(complexConfig.TOMLString(), lc) - assert.Equal(t, complexConfig, lc) -} - -func TestReadViperConfig(t *testing.T) { - conf := viper.New() - conf.SetConfigType("toml") - conf.ReadConfig(strings.NewReader(complexConfig.TOMLString())) - lc, err := LoggingConfigFromMap(conf.AllSettings()) - assert.NoError(t, err) + _, err := toml.Decode(complexConfig.TOMLString(), lc) + require.NoError(t, err) assert.Equal(t, complexConfig, lc) } diff --git a/logging/config/filter.go b/logging/config/filter.go index 1ddf35ff6a1339776f6793c63dc2a7aea19b5a4a..d4d4aede83b8751c76407d2f2234685b388315a1 100644 --- a/logging/config/filter.go +++ b/logging/config/filter.go @@ -4,8 +4,6 @@ import ( "fmt" "regexp" - - "github.com/hyperledger/burrow/common/math/integral" ) func BuildFilterPredicate(filterConfig *FilterConfig) (func([]interface{}) bool, error) { @@ -59,7 +57,10 @@ func matchLogLine(keyvals []interface{}, keyRegexes, valueRegexes []*regexp.Rege all := matchAll // We should be passed an aligned list of keyRegexes and valueRegexes, but since we can't error here we'll guard // against a failure of the caller to pass valid arguments - length := integral.MinInt(len(keyRegexes), len(valueRegexes)) + length := len(keyRegexes) + if len(valueRegexes) < length { + length = len(valueRegexes) + } for i := 0; i < length; i++ { matched := findMatchInLogLine(keyvals, keyRegexes[i], valueRegexes[i]) if matchAll { diff --git a/logging/config/filter_test.go b/logging/config/filter_test.go index 3c5edf5ad4df8c54e999c33792c70e10f4a99c62..bde13531837fb689148b1d08900d815bad848b17 100644 --- a/logging/config/filter_test.go +++ b/logging/config/filter_test.go @@ -3,7 +3,6 @@ package config import ( "testing" - . "github.com/hyperledger/burrow/util/slice" "github.com/stretchr/testify/assert" ) @@ -16,7 +15,7 @@ func TestBuildKeyValuesPredicateMatchAll(t *testing.T) { } kvp, err := BuildKeyValuesPredicate(conf, true) assert.NoError(t, err) - assert.True(t, kvp(Slice("Foo", "bar", "Bosh", "Bish"))) + assert.True(t, kvp([]interface{}{"Foo", "bar", "Bosh", "Bish"})) } func TestBuildKeyValuesPredicateMatchAny(t *testing.T) { @@ -28,7 +27,7 @@ func TestBuildKeyValuesPredicateMatchAny(t *testing.T) { } kvp, err := BuildKeyValuesPredicate(conf, false) assert.NoError(t, err) - assert.True(t, kvp(Slice("Foo", "bar", "Bosh", "Bish"))) + assert.True(t, kvp([]interface{}{"Foo", "bar", "Bosh", "Bish"})) } func TestExcludeAllFilterPredicate(t *testing.T) { @@ -47,9 +46,9 @@ func TestExcludeAllFilterPredicate(t *testing.T) { } fp, err := BuildFilterPredicate(fc) assert.NoError(t, err) - assert.False(t, fp(Slice("Bosh", "Bash", "Shoes", 42))) - assert.True(t, fp(Slice("Bosh", "Bash", "Foo", "bar", "Shoes", 42, "Bosh", "Bish"))) - assert.False(t, fp(Slice("Food", 0.2, "Shoes", 42))) + assert.False(t, fp([]interface{}{"Bosh", "Bash", "Shoes", 42})) + assert.True(t, fp([]interface{}{"Bosh", "Bash", "Foo", "bar", "Shoes", 42, "Bosh", "Bish"})) + assert.False(t, fp([]interface{}{"Food", 0.2, "Shoes", 42})) } func TestExcludeAnyFilterPredicate(t *testing.T) { @@ -68,9 +67,9 @@ func TestExcludeAnyFilterPredicate(t *testing.T) { } fp, err := BuildFilterPredicate(fc) assert.NoError(t, err) - assert.False(t, fp(Slice("Foo", "bar", "Shoes", 42))) - assert.True(t, fp(Slice("Foo", "bar", "Shoes", 42, "Bosh", "Bish"))) - assert.True(t, fp(Slice("Food", 0.2, "Shoes", 42, "Bosh", "Bish"))) + assert.False(t, fp([]interface{}{"Foo", "bar", "Shoes", 42})) + assert.True(t, fp([]interface{}{"Foo", "bar", "Shoes", 42, "Bosh", "Bish"})) + assert.True(t, fp([]interface{}{"Food", 0.2, "Shoes", 42, "Bosh", "Bish"})) } @@ -90,10 +89,10 @@ func TestIncludeAllFilterPredicate(t *testing.T) { } fp, err := BuildFilterPredicate(fc) assert.NoError(t, err) - assert.True(t, fp(Slice("Foo", "bar", "Shoes", 42))) + assert.True(t, fp([]interface{}{"Foo", "bar", "Shoes", 42})) // Don't filter, it has all the required key values - assert.False(t, fp(Slice("Foo", "bar", "Planks", 0.2, "Shoes", 42, "Bosh", "Bish"))) - assert.True(t, fp(Slice("Food", 0.2, "Shoes", 42))) + assert.False(t, fp([]interface{}{"Foo", "bar", "Planks", 0.2, "Shoes", 42, "Bosh", "Bish"})) + assert.True(t, fp([]interface{}{"Food", 0.2, "Shoes", 42})) } func TestIncludeAnyFilterPredicate(t *testing.T) { @@ -112,8 +111,8 @@ func TestIncludeAnyFilterPredicate(t *testing.T) { } fp, err := BuildFilterPredicate(fc) assert.NoError(t, err) - assert.False(t, fp(Slice("Foo", "bar", "Shoes", 3427))) - assert.False(t, fp(Slice("Foo", "bar", "Shoes", 42, "Bosh", "Bish"))) - assert.False(t, fp(Slice("Food", 0.2, "Shoes", 42))) + assert.False(t, fp([]interface{}{"Foo", "bar", "Shoes", 3427})) + assert.False(t, fp([]interface{}{"Foo", "bar", "Shoes", 42, "Bosh", "Bish"})) + assert.False(t, fp([]interface{}{"Food", 0.2, "Shoes", 42})) } diff --git a/logging/config/presets/instructions.go b/logging/config/presets/instructions.go new file mode 100644 index 0000000000000000000000000000000000000000..2844ae8bc19f8453033e536c611d3e8039580ec9 --- /dev/null +++ b/logging/config/presets/instructions.go @@ -0,0 +1,198 @@ +package presets + +import ( + "fmt" + + "github.com/hyperledger/burrow/logging/config" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/logging/types" +) + +// Function to generate part of a tree of Sinks (e.g. append a single child node, or an entire subtree). +// Each Instruction takes a (sub)root, which it may modify, and appends further child sinks below that root. +// Returning the new subroot from which to apply any further Presets. +// When chained together in a pre-order instructions can be composed to form an entire Sink tree. +type Instruction struct { + name string + desc string + // The builder for the Instruction is a function that may modify the stack or ops string. Typically + // by mutating the sink at the top of the stack and may move the cursor or by pushing child sinks + // to the stack. The builder may also return a modified ops slice whereby it may insert Instruction calls + // acting as a macro or consume ops as arguments. + builder func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) +} + +func (i Instruction) Name() string { + return i.name +} + +func (i Instruction) Description() string { + return i.desc +} + +const ( + Info = "info" + Minimal = "minimal" + IncludeAny = "include-any" + Stderr = "stderr" + Stdout = "stdout" + Terminal = "terminal" + Up = "up" + Down = "down" +) + +var instructions = []Instruction{ + { + name: Up, + desc: "Ascend the sink tree by travelling up the stack to the previous sink recorded on the stack", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + return pop(stack), ops, nil + }, + }, + { + name: Down, + desc: "Descend the sink tree by inserting a sink as a child to the current sink and adding it to the stack", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + return push(stack, config.Sink()), ops, nil + }, + }, + { + name: Minimal, + desc: "A generally less chatty log output, follow with output options", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + return push(stack, + config.Sink().SetTransform(config.PruneTransform(structure.TraceKey, structure.RunId)), + config.Sink().SetTransform(config.FilterTransform(config.IncludeWhenAllMatch, + structure.ChannelKey, types.InfoChannelName)), + config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, + structure.ComponentKey, "Tendermint", + "module", "p2p", + "module", "mempool"))), + ops, nil + }, + }, + { + name: IncludeAny, + desc: "Establish an 'include when any predicate matches' filter transform at this this sink", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + sink := peek(stack) + ensureFilter(sink) + sink.Transform.FilterConfig.FilterMode = config.IncludeWhenAnyMatches + return stack, ops, nil + }, + }, + { + name: Info, + desc: "Add a filter predicate to match the Info logging channel", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + sink := peek(stack) + ensureFilter(sink) + sink.Transform.FilterConfig.AddPredicate(structure.ChannelKey, types.InfoChannelName) + return stack, ops, nil + }, + }, + { + name: Stdout, + desc: "Use Stdout output for this sink", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + sink := peek(stack) + ensureOutput(sink) + sink.Output.OutputType = config.Stdout + return stack, ops, nil + }, + }, + { + name: Stderr, + desc: "Use Stderr output for this sink", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + sink := peek(stack) + ensureOutput(sink) + sink.Output.OutputType = config.Stderr + return stack, ops, nil + }, + }, + { + name: Terminal, + desc: "Use the the terminal output format for this sink", + builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + sink := peek(stack) + ensureOutput(sink) + sink.Output.Format = loggers.TerminalFormat + return stack, ops, nil + }, + }, +} + +var instructionsMap map[string]Instruction + +func init() { + instructionsMap = make(map[string]Instruction, len(instructions)) + for _, p := range instructions { + instructionsMap[p.name] = p + } +} + +func Instructons() []Instruction { + ins := make([]Instruction, len(instructions)) + copy(ins, instructions) + return ins +} + +func Describe(name string) string { + preset, ok := instructionsMap[name] + if !ok { + return fmt.Sprintf("No logging preset named '%s'", name) + } + return preset.desc +} + +func BuildSinkConfig(ops ...string) (*config.SinkConfig, error) { + stack := []*config.SinkConfig{config.Sink()} + var err error + for len(ops) > 0 { + // Keep applying instructions until their are no ops left + preset, ok := instructionsMap[ops[0]] + if !ok { + return nil, fmt.Errorf("could not find logging preset '%s'", ops[0]) + } + stack, ops, err = preset.builder(stack, ops[1:]) + if err != nil { + return nil, err + } + } + return stack[0], nil +} + +func ensureFilter(sinkConfig *config.SinkConfig) { + if sinkConfig.Transform == nil { + sinkConfig.Transform = &config.TransformConfig{} + } + if sinkConfig.Transform.FilterConfig == nil { + sinkConfig.Transform.FilterConfig = &config.FilterConfig{} + } + sinkConfig.Transform.TransformType = config.Filter +} + +func ensureOutput(sinkConfig *config.SinkConfig) { + if sinkConfig.Output == nil { + sinkConfig.Output = &config.OutputConfig{} + } +} + +// Push a path sequence of sinks onto the stack +func push(stack []*config.SinkConfig, sinkConfigs ...*config.SinkConfig) []*config.SinkConfig { + for _, sinkConfig := range sinkConfigs { + peek(stack).AddSinks(sinkConfig) + stack = append(stack, sinkConfig) + } + return stack +} + +func pop(stack []*config.SinkConfig) []*config.SinkConfig { + return stack[:len(stack)-1] +} + +func peek(stack []*config.SinkConfig) *config.SinkConfig { + return stack[len(stack)-1] +} diff --git a/logging/config/presets/instructions_test.go b/logging/config/presets/instructions_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7252db2887b10d5f8d7e346880daa14a1de12253 --- /dev/null +++ b/logging/config/presets/instructions_test.go @@ -0,0 +1,41 @@ +package presets + +import ( + "testing" + + "fmt" + + "github.com/BurntSushi/toml" + "github.com/hyperledger/burrow/logging/config" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/logging/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildSinkConfig(t *testing.T) { + builtSink, err := BuildSinkConfig(IncludeAny, Info, Stdout, Terminal, Down, Down, Info, Stdout, Up, Info, Stderr) + require.NoError(t, err) + expectedSink := config.Sink(). + SetTransform(config.FilterTransform(config.IncludeWhenAnyMatches, + structure.ChannelKey, types.InfoChannelName)).SetOutput(config.StdoutOutput().SetFormat(loggers.TerminalFormat)).AddSinks( + config.Sink().SetTransform(config.FilterTransform(config.NoFilterMode, + structure.ChannelKey, types.InfoChannelName)).SetOutput(config.StderrOutput()).AddSinks( + config.Sink().SetTransform(config.FilterTransform(config.NoFilterMode, + structure.ChannelKey, types.InfoChannelName)).SetOutput(config.StdoutOutput()))) + + fmt.Println(config.JSONString(expectedSink), "\n", config.JSONString(builtSink)) + assert.Equal(t, config.JSONString(expectedSink), config.JSONString(builtSink)) +} + +func TestMinimalPreset(t *testing.T) { + builtSink, err := BuildSinkConfig(Minimal) + require.NoError(t, err) + loggingConfig := &config.LoggingConfig{ + RootSink: builtSink, + } + loggingConfigOut := new(config.LoggingConfig) + toml.Decode(loggingConfig.TOMLString(), loggingConfigOut) + assert.Equal(t, loggingConfig.TOMLString(), loggingConfigOut.TOMLString()) +} diff --git a/logging/config/sinks.go b/logging/config/sinks.go index fbea38d84a6a7c692a2ae27db56b5d2d482b44ac..3974fd5fafdef85f761c74c5fd784eec863b5087 100644 --- a/logging/config/sinks.go +++ b/logging/config/sinks.go @@ -4,10 +4,9 @@ import ( "fmt" "os" - "net/url" - "github.com/eapache/channels" kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/logging/structure" ) @@ -22,11 +21,9 @@ type filterMode string const ( // OutputType NoOutput outputType = "" - Graylog outputType = "graylog" - Syslog outputType = "syslog" - File outputType = "file" Stdout outputType = "stdout" Stderr outputType = "stderr" + File outputType = "file" // TransformType NoTransform transformType = "" @@ -37,14 +34,17 @@ const ( // Add key value pairs to each log line Label transformType = "label" Capture transformType = "capture" + Sort transformType = "sort" + // TODO [Silas]: add 'flush on exit' transform which flushes the buffer of // CaptureLogger to its OutputLogger a non-passthrough capture when an exit // signal is detected or some other exceptional thing happens + NoFilterMode filterMode = "" IncludeWhenAllMatch filterMode = "include_when_all_match" - IncludeWhenAnyMatches filterMode = "include_when_any_matches" + IncludeWhenAnyMatches filterMode = "include_when_any_match" ExcludeWhenAllMatch filterMode = "exclude_when_all_match" - ExcludeWhenAnyMatches filterMode = "exclude_when_any_matches" + ExcludeWhenAnyMatches filterMode = "exclude_when_any_match" ) // Only include log lines matching the filter so negate the predicate in filter @@ -80,63 +80,66 @@ func (mode filterMode) MatchAny() bool { // Sink configuration types type ( // Outputs - GraylogConfig struct { - } - + // TODO: reintroduce syslog removed when we dropped log15 dependency SyslogConfig struct { - Url string `toml:"url"` - Tag string `toml:"tag"` + Url string + Tag string } FileConfig struct { - Path string `toml:"path"` + Path string } OutputConfig struct { - OutputType outputType `toml:"output_type"` - Format string `toml:"format,omitempty"` - *GraylogConfig - *FileConfig - *SyslogConfig + OutputType outputType + Format string + *FileConfig `json:",omitempty" toml:",omitempty"` + *SyslogConfig `json:",omitempty" toml:",omitempty"` } // Transforms LabelConfig struct { - Labels map[string]string `toml:"labels"` - Prefix bool `toml:"prefix"` + Labels map[string]string + Prefix bool } PruneConfig struct { - Keys []string `toml:"keys"` + Keys []string } CaptureConfig struct { - Name string `toml:"name"` - BufferCap int `toml:"buffer_cap"` - Passthrough bool `toml:"passthrough"` + Name string + BufferCap int + Passthrough bool } // Generates true if KeyRegex matches a log line key and ValueRegex matches that key's value. // If ValueRegex is empty then returns true if any key matches // If KeyRegex is empty then returns true if any value matches KeyValuePredicateConfig struct { - KeyRegex string `toml:"key_regex"` - ValueRegex string `toml:"value_regex"` + KeyRegex string + ValueRegex string } // Filter types FilterConfig struct { - FilterMode filterMode `toml:"filter_mode"` + FilterMode filterMode // Predicates to match a log line against using FilterMode - Predicates []*KeyValuePredicateConfig `toml:"predicates"` + Predicates []*KeyValuePredicateConfig + } + + SortConfig struct { + // Sort keys-values with keys in this list first + Keys []string } TransformConfig struct { - TransformType transformType `toml:"transform_type"` - *LabelConfig - *PruneConfig - *CaptureConfig - *FilterConfig + TransformType transformType + LabelConfig *LabelConfig `json:",omitempty" toml:",omitempty"` + PruneConfig *PruneConfig `json:",omitempty" toml:",omitempty"` + CaptureConfig *CaptureConfig `json:",omitempty" toml:",omitempty"` + FilterConfig *FilterConfig `json:",omitempty" toml:",omitempty"` + SortConfig *SortConfig `json:",omitempty" toml:",omitempty"` } // Sink @@ -144,9 +147,9 @@ type ( // before transmitting its log it applies zero or one transforms to the stream of log lines. // by chaining together many Sinks arbitrary transforms to and multi SinkConfig struct { - Transform *TransformConfig `toml:"transform"` - Sinks []*SinkConfig `toml:"sinks"` - Output *OutputConfig `toml:"output"` + Transform *TransformConfig `json:",omitempty" toml:",omitempty"` + Sinks []*SinkConfig `json:",omitempty" toml:",omitempty"` + Output *OutputConfig `json:",omitempty" toml:",omitempty"` } ) @@ -187,10 +190,6 @@ func StderrOutput() *OutputConfig { } } -func SyslogOutput(tag string) *OutputConfig { - return RemoteSyslogOutput(tag, "") -} - func FileOutput(path string) *OutputConfig { return &OutputConfig{ OutputType: File, @@ -200,16 +199,6 @@ func FileOutput(path string) *OutputConfig { } } -func RemoteSyslogOutput(tag, remoteUrl string) *OutputConfig { - return &OutputConfig{ - OutputType: Syslog, - SyslogConfig: &SyslogConfig{ - Url: remoteUrl, - Tag: tag, - }, - } -} - func CaptureTransform(name string, bufferCap int, passthrough bool) *TransformConfig { return &TransformConfig{ TransformType: Capture, @@ -264,13 +253,36 @@ func FilterTransform(fmode filterMode, keyvalueRegexes ...string) *TransformConf } } +func (filterConfig *FilterConfig) SetFilterMode(filterMode filterMode) *FilterConfig { + filterConfig.FilterMode = filterMode + return filterConfig +} + +func (filterConfig *FilterConfig) AddPredicate(keyRegex, valueRegex string) *FilterConfig { + filterConfig.Predicates = append(filterConfig.Predicates, &KeyValuePredicateConfig{ + KeyRegex: keyRegex, + ValueRegex: valueRegex, + }) + return filterConfig +} + +func SortTransform(keys ...string) *TransformConfig { + return &TransformConfig{ + TransformType: Sort, + SortConfig: &SortConfig{ + Keys: keys, + }, + } +} + // Logger formation func (sinkConfig *SinkConfig) BuildLogger() (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { return BuildLoggerFromSinkConfig(sinkConfig, make(map[string]*loggers.CaptureLogger)) } -func BuildLoggerFromSinkConfig(sinkConfig *SinkConfig, - captures map[string]*loggers.CaptureLogger) (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { +func BuildLoggerFromSinkConfig(sinkConfig *SinkConfig, captures map[string]*loggers.CaptureLogger) (kitlog.Logger, + map[string]*loggers.CaptureLogger, error) { + if sinkConfig == nil { return kitlog.NewNopLogger(), captures, nil } @@ -279,11 +291,11 @@ func BuildLoggerFromSinkConfig(sinkConfig *SinkConfig, // We need a depth-first post-order over the output loggers so we'll keep // recurring into children sinks we reach a terminal sink (with no children) for i, sc := range sinkConfig.Sinks { - l, captures, err := BuildLoggerFromSinkConfig(sc, captures) + var err error + outputLoggers[i], captures, err = BuildLoggerFromSinkConfig(sc, captures) if err != nil { - return nil, captures, err + return nil, nil, err } - outputLoggers[i] = l } // Grab the outputs after we have terminated any children sinks above @@ -298,7 +310,7 @@ func BuildLoggerFromSinkConfig(sinkConfig *SinkConfig, outputLogger := loggers.NewMultipleOutputLogger(outputLoggers...) if sinkConfig.Transform != nil && sinkConfig.Transform.TransformType != NoTransform { - return BuildTransformLogger(sinkConfig.Transform, captures, outputLogger) + return BuildTransformLoggerPassthrough(sinkConfig.Transform, captures, outputLogger) } return outputLogger, captures, nil } @@ -307,41 +319,39 @@ func BuildOutputLogger(outputConfig *OutputConfig) (kitlog.Logger, error) { switch outputConfig.OutputType { case NoOutput: return kitlog.NewNopLogger(), nil - //case Graylog: - case Syslog: - urlString := outputConfig.SyslogConfig.Url - if urlString != "" { - remoteUrl, err := url.Parse(urlString) - if err != nil { - return nil, fmt.Errorf("Error parsing remote syslog URL: %s, "+ - "error: %s", - urlString, err) - } - return loggers.NewRemoteSyslogLogger(remoteUrl, - outputConfig.SyslogConfig.Tag, outputConfig.Format) - } - return loggers.NewSyslogLogger(outputConfig.SyslogConfig.Tag, - outputConfig.Format) + case File: + return loggers.NewFileLogger(outputConfig.FileConfig.Path, outputConfig.Format) case Stdout: return loggers.NewStreamLogger(os.Stdout, outputConfig.Format), nil case Stderr: return loggers.NewStreamLogger(os.Stderr, outputConfig.Format), nil - case File: - return loggers.NewFileLogger(outputConfig.FileConfig.Path, outputConfig.Format) default: - return nil, fmt.Errorf("Could not build logger for output: '%s'", + return nil, fmt.Errorf("could not build logger for output: '%s'", outputConfig.OutputType) } } -func BuildTransformLogger(transformConfig *TransformConfig, - captures map[string]*loggers.CaptureLogger, +func BuildTransformLoggerPassthrough(transformConfig *TransformConfig, captures map[string]*loggers.CaptureLogger, outputLogger kitlog.Logger) (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { + + transformThenOutputLogger, captures, err := BuildTransformLogger(transformConfig, captures, outputLogger) + if err != nil { + return nil, nil, err + } + return signalPassthroughLogger(outputLogger, transformThenOutputLogger), captures, nil +} + +func BuildTransformLogger(transformConfig *TransformConfig, captures map[string]*loggers.CaptureLogger, + outputLogger kitlog.Logger) (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { + switch transformConfig.TransformType { case NoTransform: return outputLogger, captures, nil case Label: - keyvals := make([]interface{}, 0, len(transformConfig.Labels)*2) + if transformConfig.LabelConfig == nil { + return nil, nil, fmt.Errorf("label transform specified but no LabelConfig provided") + } + keyvals := make([]interface{}, 0, len(transformConfig.LabelConfig.Labels)*2) for k, v := range transformConfig.LabelConfig.Labels { keyvals = append(keyvals, k, v) } @@ -351,6 +361,9 @@ func BuildTransformLogger(transformConfig *TransformConfig, return kitlog.With(outputLogger, keyvals...), captures, nil } case Prune: + if transformConfig.PruneConfig == nil { + return nil, nil, fmt.Errorf("prune transform specified but no PruneConfig provided") + } keys := make([]interface{}, len(transformConfig.PruneConfig.Keys)) for i, k := range transformConfig.PruneConfig.Keys { keys[i] = k @@ -360,9 +373,12 @@ func BuildTransformLogger(transformConfig *TransformConfig, }), captures, nil case Capture: + if transformConfig.CaptureConfig == nil { + return nil, nil, fmt.Errorf("capture transform specified but no CaptureConfig provided") + } name := transformConfig.CaptureConfig.Name if _, ok := captures[name]; ok { - return nil, captures, fmt.Errorf("Could not register new logging capture since name '%s' already "+ + return nil, captures, fmt.Errorf("could not register new logging capture since name '%s' already "+ "registered", name) } // Create a capture logger according to configuration (it may tee the output) @@ -375,12 +391,29 @@ func BuildTransformLogger(transformConfig *TransformConfig, // Pass it upstream to be logged to return captureLogger, captures, nil case Filter: + if transformConfig.FilterConfig == nil { + return nil, nil, fmt.Errorf("filter transform specified but no FilterConfig provided") + } predicate, err := BuildFilterPredicate(transformConfig.FilterConfig) if err != nil { - return nil, captures, fmt.Errorf("Could not build filter predicate: '%s'", err) + return nil, captures, fmt.Errorf("could not build filter predicate: '%s'", err) + } + return loggers.FilterLogger(outputLogger, predicate), captures, nil + case Sort: + if transformConfig.SortConfig == nil { + return nil, nil, fmt.Errorf("sort transform specified but no SortConfig provided") } - return loggers.NewFilterLogger(outputLogger, predicate), captures, nil + return loggers.SortLogger(outputLogger, transformConfig.SortConfig.Keys...), captures, nil default: - return nil, captures, fmt.Errorf("Could not build logger for transform: '%s'", transformConfig.TransformType) + return nil, captures, fmt.Errorf("could not build logger for transform: '%s'", transformConfig.TransformType) } } + +func signalPassthroughLogger(ifSignalLogger kitlog.Logger, otherwiseLogger kitlog.Logger) kitlog.Logger { + return kitlog.LoggerFunc(func(keyvals ...interface{}) error { + if logging.Signal(keyvals) != "" { + return ifSignalLogger.Log(keyvals...) + } + return otherwiseLogger.Log(keyvals...) + }) +} diff --git a/logging/config/sinks_test.go b/logging/config/sinks_test.go index fbe035bb410310ba7b3b13e993ff924973f4121a..a0d7835863d55fc97e61a1fe942844530928700f 100644 --- a/logging/config/sinks_test.go +++ b/logging/config/sinks_test.go @@ -3,6 +3,11 @@ package config import ( "testing" + "fmt" + + "encoding/json" + + "github.com/hyperledger/burrow/logging" "github.com/stretchr/testify/assert" ) @@ -28,6 +33,19 @@ func TestBuildLoggerFromSinkConfig(t *testing.T) { captures["cap"].BufferLogger().FlushLogLines()) } +func TestFileLoggerSink(t *testing.T) { + sinkConfig := Sink(). + SetOutput(FileOutput("/tmp/logmclogface")).AddSinks( + Sink().SetOutput(FileOutput("/tmp/doubleloglog"))) + + bs, err := json.Marshal(sinkConfig) + assert.NoError(t, err) + + fmt.Println(string(bs)) + _, _, err = sinkConfig.BuildLogger() + assert.NoError(t, err) +} + func TestFilterSinks(t *testing.T) { sinkConfig := Sink(). SetOutput(StderrOutput()). diff --git a/logging/config/sort.go b/logging/config/sort.go new file mode 100644 index 0000000000000000000000000000000000000000..b663f62c4785c1c1d22f13079c315e61c5b62289 --- /dev/null +++ b/logging/config/sort.go @@ -0,0 +1,3 @@ +package config + + diff --git a/logging/convention.go b/logging/convention.go index d470451a9670a480bf47f9d36f6a4b900f5124c2..65dc2e5b98106caa86768139dbf509e0c321e92d 100644 --- a/logging/convention.go +++ b/logging/convention.go @@ -18,7 +18,6 @@ import ( kitlog "github.com/go-kit/kit/log" "github.com/hyperledger/burrow/logging/structure" "github.com/hyperledger/burrow/logging/types" - "github.com/hyperledger/burrow/util/slice" ) // Helper functions for InfoTraceLoggers, sort of extension methods to loggers @@ -26,13 +25,13 @@ import ( // logging interface // Record structured Info log line with a message -func InfoMsg(logger types.InfoTraceLogger, message string, keyvals ...interface{}) { - Msg(kitlog.LoggerFunc(logger.Info), message, keyvals...) +func InfoMsg(logger types.InfoTraceLogger, message string, keyvals ...interface{}) error { + return Msg(kitlog.LoggerFunc(logger.Info), message, keyvals...) } // Record structured Trace log line with a message -func TraceMsg(logger types.InfoTraceLogger, message string, keyvals ...interface{}) { - Msg(kitlog.LoggerFunc(logger.Trace), message, keyvals...) +func TraceMsg(logger types.InfoTraceLogger, message string, keyvals ...interface{}) error { + return Msg(kitlog.LoggerFunc(logger.Trace), message, keyvals...) } // Establish or extend the scope of this logger by appending scopeName to the Scope vector. @@ -46,6 +45,25 @@ func WithScope(logger types.InfoTraceLogger, scopeName string) types.InfoTraceLo // Record a structured log line with a message func Msg(logger kitlog.Logger, message string, keyvals ...interface{}) error { - prepended := slice.CopyPrepend(keyvals, structure.MessageKey, message) + prepended := structure.CopyPrepend(keyvals, structure.MessageKey, message) return logger.Log(prepended...) } + +// Sends the sync signal which causes any syncing loggers to sync. +// loggers receiving the signal should drop the signal logline from output +func Sync(logger kitlog.Logger) error { + return logger.Log(structure.SignalKey, structure.SyncSignal) +} + +// Tried to interpret the logline as a signal by matching the last key-value pair as a signal, returns empty string if +// no match +func Signal(keyvals []interface{}) string { + last := len(keyvals) - 1 + if last > 0 && keyvals[last-1] == structure.SignalKey { + signal, ok := keyvals[last].(string) + if ok { + return signal + } + } + return "" +} diff --git a/logging/lifecycle/lifecycle.go b/logging/lifecycle/lifecycle.go index bd9d6a839b1af71a62c4c1b43997557eb535e2c6..d6992a94f5d14ddcdf13a1f63f8fc0dc5d14f5eb 100644 --- a/logging/lifecycle/lifecycle.go +++ b/logging/lifecycle/lifecycle.go @@ -21,7 +21,6 @@ import ( "time" "github.com/hyperledger/burrow/logging/adapters/stdlib" - tmLog15adapter "github.com/hyperledger/burrow/logging/adapters/tendermint_log15" "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/logging/structure" @@ -33,7 +32,6 @@ import ( "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/types" "github.com/streadway/simpleuuid" - tmLog15 "github.com/tendermint/log15" ) // Lifecycle provides a canonical source for burrow loggers. Components should use the functions here @@ -93,10 +91,8 @@ func NewLogger(outputLogger kitlog.Logger) (types.InfoTraceLogger, channels.Chan return logging.WithMetadata(infoTraceLogger.With(structure.RunId, runId)), errCh } -func CaptureTendermintLog15Output(infoTraceLogger types.InfoTraceLogger) { - tmLog15.Root().SetHandler( - tmLog15adapter.InfoTraceLoggerAsLog15Handler(infoTraceLogger. - With(structure.CapturedLoggingSourceKey, "tendermint_log15"))) +func JustLogger(logger types.InfoTraceLogger, _ channels.Channel) types.InfoTraceLogger { + return logger } func CaptureStdlibLogOutput(infoTraceLogger types.InfoTraceLogger) { diff --git a/logging/lifecycle/lifecycle_test.go b/logging/lifecycle/lifecycle_test.go index f0de3789dd94f58836ac24372aa5d0cdcf37da7f..c38e6491406a376edd1c962ecf89aadf2405bff9 100644 --- a/logging/lifecycle/lifecycle_test.go +++ b/logging/lifecycle/lifecycle_test.go @@ -6,9 +6,7 @@ import ( "bufio" - . "github.com/hyperledger/burrow/logging/config" "github.com/stretchr/testify/assert" - "github.com/tendermint/log15" ) func TestNewLoggerFromLoggingConfig(t *testing.T) { @@ -23,27 +21,6 @@ func TestNewLoggerFromLoggingConfig(t *testing.T) { assert.NotEmpty(t, lineString) } -func TestCaptureTendermintLog15Output(t *testing.T) { - reader := CaptureStderr(t, func() { - loggingConfig := &LoggingConfig{ - RootSink: Sink(). - SetOutput(StderrOutput().SetFormat("logfmt")). - SetTransform(FilterTransform(ExcludeWhenAllMatch, - "log_channel", "Trace", - )), - } - outputLogger, err := NewLoggerFromLoggingConfig(loggingConfig) - assert.NoError(t, err) - CaptureTendermintLog15Output(outputLogger) - log15Logger := log15.New() - log15Logger.Info("bar", "number_of_forks", 2) - }) - line, _, err := reader.ReadLine() - assert.NoError(t, err) - assert.Contains(t, string(line), "number_of_forks=2") - assert.Contains(t, string(line), "message=bar") -} - func CaptureStderr(t *testing.T, runner func()) *bufio.Reader { stderr := os.Stderr defer func() { diff --git a/logging/loggers/burrow_format_logger.go b/logging/loggers/burrow_format_logger.go index 2af1b4032eb3d0576fbffc9296ad238dd500bf92..d0a9a41031a70145f724de881f9e769bb09e89e9 100644 --- a/logging/loggers/burrow_format_logger.go +++ b/logging/loggers/burrow_format_logger.go @@ -16,11 +16,10 @@ package loggers import ( "fmt" - - "github.com/hyperledger/burrow/logging/structure" + "time" kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/word256" + "github.com/hyperledger/burrow/logging/structure" ) // Logger that implements some formatting conventions for burrow and burrow-client @@ -34,15 +33,15 @@ type burrowFormatLogger struct { var _ kitlog.Logger = &burrowFormatLogger{} -func (efl *burrowFormatLogger) Log(keyvals ...interface{}) error { - if efl.logger == nil { +func (bfl *burrowFormatLogger) Log(keyvals ...interface{}) error { + if bfl.logger == nil { return nil } if len(keyvals)%2 != 0 { - return fmt.Errorf("Log line contains an odd number of elements so "+ + return fmt.Errorf("log line contains an odd number of elements so "+ "was dropped: %v", keyvals) } - return efl.logger.Log(structure.MapKeyValues(keyvals, burrowFormatKeyValueMapper)...) + return bfl.logger.Log(structure.MapKeyValues(keyvals, burrowFormatKeyValueMapper)...) } func burrowFormatKeyValueMapper(key, value interface{}) (interface{}, interface{}) { @@ -51,12 +50,12 @@ func burrowFormatKeyValueMapper(key, value interface{}) (interface{}, interface{ switch v := value.(type) { case []byte: return key, fmt.Sprintf("%X", v) - case word256.Word256: - return burrowFormatKeyValueMapper(key, v.Bytes()) + case time.Time: + return key, v.Format(time.RFC3339Nano) } } - return key, value + return key, fmt.Sprintf("%v", value) } func BurrowFormatLogger(logger kitlog.Logger) *burrowFormatLogger { diff --git a/logging/loggers/filter_logger.go b/logging/loggers/filter_logger.go index ba9e542f525c41ec0b54b1b8e292ee2e22134ab6..67f8949b98add9310c9772a33ef553f55ed5494a 100644 --- a/logging/loggers/filter_logger.go +++ b/logging/loggers/filter_logger.go @@ -4,25 +4,12 @@ import kitlog "github.com/go-kit/kit/log" // Filter logger allows us to filter lines logged to it before passing on to underlying // output logger -type filterLogger struct { - logger kitlog.Logger - predicate func(keyvals []interface{}) bool -} - -var _ kitlog.Logger = (*filterLogger)(nil) - -func (fl filterLogger) Log(keyvals ...interface{}) error { - if !fl.predicate(keyvals) { - return fl.logger.Log(keyvals...) - } - return nil -} - // Creates a logger that removes lines from output when the predicate evaluates true -func NewFilterLogger(outputLogger kitlog.Logger, - predicate func(keyvals []interface{}) bool) kitlog.Logger { - return &filterLogger{ - logger: outputLogger, - predicate: predicate, - } +func FilterLogger(outputLogger kitlog.Logger, predicate func(keyvals []interface{}) bool) kitlog.Logger { + return kitlog.LoggerFunc(func(keyvals ...interface{}) error { + if !predicate(keyvals) { + return outputLogger.Log(keyvals...) + } + return nil + }) } diff --git a/logging/loggers/filter_logger_test.go b/logging/loggers/filter_logger_test.go index f200a8276668fd5d88b7a85586ad6f5a40e2e712..e989ad81d06ba7a9188f2aa37c6250c292d63772 100644 --- a/logging/loggers/filter_logger_test.go +++ b/logging/loggers/filter_logger_test.go @@ -3,16 +3,15 @@ package loggers import ( "testing" - . "github.com/hyperledger/burrow/util/slice" "github.com/stretchr/testify/assert" ) func TestFilterLogger(t *testing.T) { testLogger := NewChannelLogger(100) - filterLogger := NewFilterLogger(testLogger, func(keyvals []interface{}) bool { + filterLogger := FilterLogger(testLogger, func(keyvals []interface{}) bool { return len(keyvals) > 0 && keyvals[0] == "Spoon" }) filterLogger.Log("Fish", "Present") filterLogger.Log("Spoon", "Present") - assert.Equal(t, [][]interface{}{Slice("Fish", "Present")}, testLogger.FlushLogLines()) + assert.Equal(t, [][]interface{}{{"Fish", "Present"}}, testLogger.FlushLogLines()) } diff --git a/logging/loggers/info_trace_logger.go b/logging/loggers/info_trace_logger.go index 2b9a553933f8f34132edf520af276f91764f264e..7e3923e0c7a09c242e8e6e9857402c6a6e726ce4 100644 --- a/logging/loggers/info_trace_logger.go +++ b/logging/loggers/info_trace_logger.go @@ -97,5 +97,6 @@ func (l *infoTraceLogger) Log(keyvals ...interface{}) error { // Wrap the output loggers with a a set of standard transforms, a non-blocking // ChannelLogger and an outer context func wrapOutputLogger(outputLogger kitlog.Logger) (kitlog.Logger, channels.Channel) { - return NonBlockingLogger(BurrowFormatLogger(VectorValuedLogger(outputLogger))) + return NonBlockingLogger(VectorValuedLogger(SortLogger(BurrowFormatLogger(outputLogger), + structure.ChannelKey, structure.MessageKey, structure.TimeKey, structure.ComponentKey))) } diff --git a/logging/loggers/output_loggers.go b/logging/loggers/output_loggers.go index 28f46fae38973c5df4c6ec858e717a2a0a8c83f8..6cd2e3d292742b317bbb84b5bfe6cf8ad2ceb91f 100644 --- a/logging/loggers/output_loggers.go +++ b/logging/loggers/output_loggers.go @@ -3,16 +3,16 @@ package loggers import ( "io" - "log/syslog" - "net/url" + "os" kitlog "github.com/go-kit/kit/log" - log15a "github.com/hyperledger/burrow/logging/adapters/tendermint_log15" - "github.com/tendermint/log15" + "github.com/go-kit/kit/log/term" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/logging/types" ) const ( - syslogPriority = syslog.LOG_LOCAL0 JSONFormat = "json" LogfmtFormat = "logfmt" TerminalFormat = "terminal" @@ -20,48 +20,41 @@ const ( ) func NewStreamLogger(writer io.Writer, formatName string) kitlog.Logger { + var logger kitlog.Logger switch formatName { case JSONFormat: - return kitlog.NewJSONLogger(writer) + logger = kitlog.NewJSONLogger(writer) case LogfmtFormat: - return kitlog.NewLogfmtLogger(writer) + logger = kitlog.NewLogfmtLogger(writer) default: - return log15a.Log15HandlerAsKitLogger(log15.StreamHandler(writer, - format(formatName))) + logger = term.NewLogger(writer, kitlog.NewLogfmtLogger, func(keyvals ...interface{}) term.FgBgColor { + switch structure.Value(keyvals, structure.ChannelKey) { + case types.TraceChannelName: + return term.FgBgColor{Fg: term.DarkGreen} + default: + return term.FgBgColor{Fg: term.Yellow} + } + }) } + // Don't log signals + return kitlog.LoggerFunc(func(keyvals ...interface{}) error { + if logging.Signal(keyvals) != "" { + return nil + } + return logger.Log(keyvals...) + }) } func NewFileLogger(path string, formatName string) (kitlog.Logger, error) { - handler, err := log15.FileHandler(path, format(formatName)) - return log15a.Log15HandlerAsKitLogger(handler), err -} - -func NewRemoteSyslogLogger(url *url.URL, tag, formatName string) (kitlog.Logger, error) { - handler, err := log15.SyslogNetHandler(url.Scheme, url.Host, syslogPriority, - tag, format(formatName)) - if err != nil { - return nil, err - } - return log15a.Log15HandlerAsKitLogger(handler), nil -} - -func NewSyslogLogger(tag, formatName string) (kitlog.Logger, error) { - handler, err := log15.SyslogHandler(syslogPriority, tag, format(formatName)) + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } - return log15a.Log15HandlerAsKitLogger(handler), nil -} - -func format(name string) log15.Format { - switch name { - case JSONFormat: - return log15.JsonFormat() - case LogfmtFormat: - return log15.LogfmtFormat() - case TerminalFormat: - return log15.TerminalFormat() - default: - return format(defaultFormatName) - } + streamLogger := NewStreamLogger(f, formatName) + return kitlog.LoggerFunc(func(keyvals ...interface{}) error { + if logging.Signal(keyvals) == structure.SyncSignal { + return f.Sync() + } + return streamLogger.Log(keyvals...) + }), nil } diff --git a/logging/loggers/output_loggers_test.go b/logging/loggers/output_loggers_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7c229c66e108698150850dc8a4d8ad2c2346ea1b --- /dev/null +++ b/logging/loggers/output_loggers_test.go @@ -0,0 +1,43 @@ +package loggers + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/hyperledger/burrow/logging" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewFileLogger(t *testing.T) { + f, err := ioutil.TempFile("", "TestNewFileLogger.log") + require.NoError(t, err) + logPath := f.Name() + f.Close() + fileLogger, err := NewFileLogger(logPath, JSONFormat) + require.NoError(t, err) + + err = fileLogger.Log("foo", "bar") + require.NoError(t, err) + + err = logging.Sync(fileLogger) + require.NoError(t, err) + + bs, err := ioutil.ReadFile(logPath) + + require.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\"}\n", string(bs)) +} + +func TestNewStreamLogger(t *testing.T) { + buf := new(bytes.Buffer) + logger := NewStreamLogger(buf, LogfmtFormat) + err := logger.Log("oh", "my") + require.NoError(t, err) + + err = logging.Sync(logger) + require.NoError(t, err) + + assert.Equal(t, "oh=my\n", string(buf.Bytes())) +} diff --git a/logging/loggers/shared_test.go b/logging/loggers/shared_test.go index 94c2a15e7be3fc55334cca1226204da04bf277d9..55657e2cba3458ba94c256a13a102b252b570375 100644 --- a/logging/loggers/shared_test.go +++ b/logging/loggers/shared_test.go @@ -8,7 +8,7 @@ import ( kitlog "github.com/go-kit/kit/log" ) -const logLineTimeout time.Duration = time.Second +const logLineTimeout = time.Second type testLogger struct { channelLogger *ChannelLogger @@ -27,7 +27,7 @@ func (tl *testLogger) logLines(numberOfLines int) ([][]interface{}, error) { case logLine := <-tl.logLineCh: logLines[i] = logLine case <-time.After(logLineTimeout): - return logLines, fmt.Errorf("Timed out waiting for log line "+ + return logLines, fmt.Errorf("timed out waiting for log line "+ "(waited %s)", logLineTimeout) } } @@ -49,7 +49,7 @@ func newTestLogger() *testLogger { func makeTestLogger(err error) *testLogger { cl := NewChannelLogger(100) - logLineCh := make(chan ([]interface{})) + logLineCh := make(chan []interface{}) go cl.DrainForever(kitlog.LoggerFunc(func(keyvals ...interface{}) error { logLineCh <- keyvals return nil @@ -61,8 +61,9 @@ func makeTestLogger(err error) *testLogger { } } +// Utility function that returns a slice of log lines. // Takes a variadic argument of log lines as a list of key value pairs delimited -// by the empty string +// by the empty string and splits func logLines(keyvals ...string) [][]interface{} { llines := make([][]interface{}, 0) line := make([]interface{}, 0) diff --git a/logging/loggers/sort_logger.go b/logging/loggers/sort_logger.go new file mode 100644 index 0000000000000000000000000000000000000000..bddb1b2170448e03786c1d33cb36a1d506b508a5 --- /dev/null +++ b/logging/loggers/sort_logger.go @@ -0,0 +1,68 @@ +package loggers + +import ( + "sort" + + kitlog "github.com/go-kit/kit/log" +) + +type sortableKeyvals struct { + indices map[string]int + keyvals []interface{} + len int +} + +func sortKeyvals(indices map[string]int, keyvals []interface{}) { + sort.Stable(sortable(indices, keyvals)) +} + +func sortable(indices map[string]int, keyvals []interface{}) *sortableKeyvals { + return &sortableKeyvals{ + indices: indices, + keyvals: keyvals, + len: len(keyvals) / 2, + } +} + +func (skv *sortableKeyvals) Len() int { + return skv.len +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (skv *sortableKeyvals) Less(i, j int) bool { + return skv.indexOfKey(i) < skv.indexOfKey(j) +} + +// Swap swaps the elements with indexes i and j. +func (skv *sortableKeyvals) Swap(i, j int) { + keyIdx, keyJdx := i*2, j*2 + valIdx, valJdx := keyIdx+1, keyJdx+1 + keyI, valI := skv.keyvals[keyIdx], skv.keyvals[valIdx] + skv.keyvals[keyIdx], skv.keyvals[valIdx] = skv.keyvals[keyJdx], skv.keyvals[valJdx] + skv.keyvals[keyJdx], skv.keyvals[valJdx] = keyI, valI +} + +func (skv *sortableKeyvals) indexOfKey(i int) int { + key, ok := skv.keyvals[i*2].(string) + if !ok { + return skv.len + 1 + } + idx, ok := skv.indices[key] + if !ok { + return skv.len + } + return idx +} + +// Provides a logger that sorts key-values with keys in keys before other key-values +func SortLogger(outputLogger kitlog.Logger, keys... string) kitlog.Logger { + indices := make(map[string]int, len(keys)) + for i, k := range keys { + indices[k] = i + } + return kitlog.LoggerFunc(func(keyvals ...interface{}) error { + sortKeyvals(indices, keyvals) + return outputLogger.Log(keyvals...) + }) +} diff --git a/logging/loggers/sort_logger_test.go b/logging/loggers/sort_logger_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e22e0136d5e37b60479716c3abf1ac7fc157227a --- /dev/null +++ b/logging/loggers/sort_logger_test.go @@ -0,0 +1,31 @@ +package loggers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_sortKeyvals(t *testing.T) { + keyvals := []interface{}{"foo", 3, "bar", 5} + indices := map[string]int{"foo": 1, "bar": 0} + sortKeyvals(indices, keyvals) + assert.Equal(t, []interface{}{"bar", 5, "foo", 3}, keyvals) +} + +func TestSortLogger(t *testing.T) { + testLogger := newTestLogger() + sortLogger := SortLogger(testLogger, "foo", "bar", "baz") + sortLogger.Log([][]int{}, "bar", "foo", 3, "baz", "horse", "crabs", "cycle", "bar", 4, "ALL ALONE") + sortLogger.Log("foo", 0) + sortLogger.Log("bar", "foo", "foo", "baz") + lines, err := testLogger.logLines(3) + require.NoError(t, err) + // non string keys sort after string keys, specified keys sort before unspecifed keys, specified key sort in order + assert.Equal(t, [][]interface{}{ + {"foo", 3, "bar", 4, "baz", "horse", "crabs", "cycle", [][]int{}, "bar", "ALL ALONE"}, + {"foo", 0}, + {"foo", "baz", "bar", "foo"}, + }, lines) +} diff --git a/logging/loggers/vector_valued_logger_test.go b/logging/loggers/vector_valued_logger_test.go index 62d85f613d04008ee5036202149855163f65835b..b551068856eaf2dd3f9347e2ec2605e7bd569d6d 100644 --- a/logging/loggers/vector_valued_logger_test.go +++ b/logging/loggers/vector_valued_logger_test.go @@ -17,7 +17,6 @@ package loggers import ( "testing" - . "github.com/hyperledger/burrow/util/slice" "github.com/stretchr/testify/assert" ) @@ -27,6 +26,6 @@ func TestVectorValuedLogger(t *testing.T) { vvl.Log("foo", "bar", "seen", 1, "seen", 3, "seen", 2) lls, err := logger.logLines(1) assert.NoError(t, err) - assert.Equal(t, Slice("foo", "bar", "seen", Slice(1, 3, 2)), + assert.Equal(t, []interface{}{"foo", "bar", "seen", []interface{}{1, 3, 2}}, lls[0]) } diff --git a/logging/metadata.go b/logging/metadata.go index 8d8ca1d32b25ecf59d02c2b57a972b9227db2c49..1699fb1a2c08e43997673ecf6789d7bc1a501262 100644 --- a/logging/metadata.go +++ b/logging/metadata.go @@ -38,7 +38,6 @@ var defaultTimestampUTCValuer kitlog.Valuer = func() interface{} { func WithMetadata(infoTraceLogger types.InfoTraceLogger) types.InfoTraceLogger { return infoTraceLogger.With(structure.TimeKey, defaultTimestampUTCValuer, - structure.CallerKey, kitlog.Caller(infoTraceLoggerCallDepth), structure.TraceKey, TraceValuer()) } diff --git a/logging/structure/structure.go b/logging/structure/structure.go index 271d8564a5e266ac0eddfe6abed20c9f42a64207..f929c4747453b91b6673822cf866ff30aa354409 100644 --- a/logging/structure/structure.go +++ b/logging/structure/structure.go @@ -14,8 +14,6 @@ package structure -import . "github.com/hyperledger/burrow/util/slice" - const ( // Log time (time.Time) TimeKey = "time" @@ -38,6 +36,10 @@ const ( // Globally unique identifier persisting while a single instance (root process) // of this program/service is running RunId = "run_id" + // Provides special instructions (that may be ignored) to downstream loggers + SignalKey = "__signal__" + // The sync signal instructs sync-able loggers to sync + SyncSignal = "__sync__" ) // Pull the specified values from a structured log line into a map. @@ -160,3 +162,29 @@ func MapKeyValues(keyvals []interface{}, fn func(interface{}, interface{}) (inte } return mappedKeyvals } + +// Deletes n elements starting with the ith from a slice by splicing. +// Beware uses append so the underlying backing array will be modified! +func Delete(slice []interface{}, i int, n int) []interface{} { + return append(slice[:i], slice[i+n:]...) +} + +// Delete an element at a specific index and return the contracted list +func DeleteAt(slice []interface{}, i int) []interface{} { + return Delete(slice, i, 1) +} + +// Prepend elements to slice in the order they appear +func CopyPrepend(slice []interface{}, elements ...interface{}) []interface{} { + elementsLength := len(elements) + newSlice := make([]interface{}, len(slice)+elementsLength) + for i, e := range elements { + newSlice[i] = e + } + for i, e := range slice { + newSlice[elementsLength+i] = e + } + return newSlice +} + + diff --git a/logging/structure/structure_test.go b/logging/structure/structure_test.go index d7e147356460f27cdec71e8c4f30f28df44687f6..995c187c31a6ce562976e2b84325bc6a527dfee6 100644 --- a/logging/structure/structure_test.go +++ b/logging/structure/structure_test.go @@ -17,19 +17,18 @@ package structure import ( "testing" - . "github.com/hyperledger/burrow/util/slice" "github.com/stretchr/testify/assert" ) func TestValuesAndContext(t *testing.T) { - keyvals := Slice("hello", 1, "dog", 2, "fish", 3, "fork", 5) + keyvals := []interface{}{"hello", 1, "dog", 2, "fish", 3, "fork", 5} vals, ctx := ValuesAndContext(keyvals, "hello", "fish") assert.Equal(t, map[interface{}]interface{}{"hello": 1, "fish": 3}, vals) - assert.Equal(t, Slice("dog", 2, "fork", 5), ctx) + assert.Equal(t, []interface{}{"dog", 2, "fork", 5}, ctx) } func TestVectorise(t *testing.T) { - kvs := Slice( + kvs := []interface{}{ "scope", "lawnmower", "hub", "budub", "occupation", "fish brewer", @@ -37,36 +36,48 @@ func TestVectorise(t *testing.T) { "flub", "dub", "scope", "rake", "flub", "brub", - ) + } kvsVector := Vectorise(kvs, "occupation", "scope") // Vectorise scope - assert.Equal(t, Slice( - "scope", Slice("lawnmower", "hose pipe", "rake"), + assert.Equal(t, []interface{}{ + "scope", []interface{}{"lawnmower", "hose pipe", "rake"}, "hub", "budub", "occupation", "fish brewer", - "flub", Slice("dub", "brub"), - ), + "flub", []interface{}{"dub", "brub"}, + }, kvsVector) } func TestRemoveKeys(t *testing.T) { // Remove multiple of same key - assert.Equal(t, Slice("Fish", 9), - RemoveKeys(Slice("Foo", "Bar", "Fish", 9, "Foo", "Baz", "odd-key"), + assert.Equal(t, []interface{}{"Fish", 9}, + RemoveKeys([]interface{}{"Foo", "Bar", "Fish", 9, "Foo", "Baz", "odd-key"}, "Foo")) // Remove multiple different keys - assert.Equal(t, Slice("Fish", 9), - RemoveKeys(Slice("Foo", "Bar", "Fish", 9, "Foo", "Baz", "Bar", 89), + assert.Equal(t, []interface{}{"Fish", 9}, + RemoveKeys([]interface{}{"Foo", "Bar", "Fish", 9, "Foo", "Baz", "Bar", 89}, "Foo", "Bar")) // Remove nothing but supply keys - assert.Equal(t, Slice("Foo", "Bar", "Fish", 9), - RemoveKeys(Slice("Foo", "Bar", "Fish", 9), + assert.Equal(t, []interface{}{"Foo", "Bar", "Fish", 9}, + RemoveKeys([]interface{}{"Foo", "Bar", "Fish", 9}, "A", "B", "C")) // Remove nothing since no keys supplied - assert.Equal(t, Slice("Foo", "Bar", "Fish", 9), - RemoveKeys(Slice("Foo", "Bar", "Fish", 9))) + assert.Equal(t, []interface{}{"Foo", "Bar", "Fish", 9}, + RemoveKeys([]interface{}{"Foo", "Bar", "Fish", 9})) +} + +func TestDelete(t *testing.T) { + assert.Equal(t, []interface{}{1, 2, 4, 5}, Delete([]interface{}{1, 2, 3, 4, 5}, 2, 1)) +} + +func TestCopyPrepend(t *testing.T) { + assert.Equal(t, []interface{}{"three", 4, 1, "two"}, + CopyPrepend([]interface{}{1, "two"}, "three", 4)) + assert.Equal(t, []interface{}{}, CopyPrepend(nil)) + assert.Equal(t, []interface{}{1}, CopyPrepend(nil, 1)) + assert.Equal(t, []interface{}{1}, CopyPrepend([]interface{}{1})) } diff --git a/manager/burrow-mint/accounts.go b/manager/burrow-mint/accounts.go deleted file mode 100644 index b0e58c3566d5c09ed40778ef2115c9f874368600..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/accounts.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package burrowmint - -// Accounts is part of the pipe for BurrowMint and provides the implementation -// for the pipe to call into the BurrowMint application - -import ( - "bytes" - "encoding/hex" - "fmt" - "sync" - - account "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - definitions "github.com/hyperledger/burrow/definitions" - event "github.com/hyperledger/burrow/event" - word256 "github.com/hyperledger/burrow/word256" -) - -// NOTE [ben] Compiler check to ensure Accounts successfully implements -// burrow/definitions.Accounts -var _ definitions.Accounts = (*accounts)(nil) - -// The accounts struct has methods for working with accounts. -type accounts struct { - burrowMint *BurrowMint - filterFactory *event.FilterFactory -} - -func newAccounts(burrowMint *BurrowMint) *accounts { - ff := event.NewFilterFactory() - - ff.RegisterFilterPool("code", &sync.Pool{ - New: func() interface{} { - return &AccountCodeFilter{} - }, - }) - - ff.RegisterFilterPool("balance", &sync.Pool{ - New: func() interface{} { - return &AccountBalanceFilter{} - }, - }) - - return &accounts{burrowMint, ff} - -} - -// Generate a new Private Key Account. -func (this *accounts) GenPrivAccount() (*account.PrivAccount, error) { - pa := account.GenPrivAccount() - return pa, nil -} - -// Generate a new Private Key Account. -func (this *accounts) GenPrivAccountFromKey(privKey []byte) ( - *account.PrivAccount, error) { - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not 64 bytes long.") - } - fmt.Printf("PK BYTES FROM ACCOUNTS: %x\n", privKey) - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - return pa, nil -} - -// Get all accounts. -func (this *accounts) Accounts(fda []*event.FilterData) ( - *core_types.AccountList, error) { - accounts := make([]*account.Account, 0) - state := this.burrowMint.GetState() - filter, err := this.filterFactory.NewFilter(fda) - if err != nil { - return nil, fmt.Errorf("Error in query: " + err.Error()) - } - state.GetAccounts().Iterate(func(key, value []byte) bool { - acc := account.DecodeAccount(value) - if filter.Match(acc) { - accounts = append(accounts, acc) - } - return false - }) - return &core_types.AccountList{accounts}, nil -} - -// Get an account. -func (this *accounts) Account(address []byte) (*account.Account, error) { - cache := this.burrowMint.GetState() // NOTE: we want to read from mempool! - acc := cache.GetAccount(address) - if acc == nil { - acc = this.newAcc(address) - } - return acc, nil -} - -// Get the value stored at 'key' in the account with address 'address' -// Both the key and value is returned. -func (this *accounts) StorageAt(address, key []byte) (*core_types.StorageItem, - error) { - state := this.burrowMint.GetState() - account := state.GetAccount(address) - if account == nil { - return &core_types.StorageItem{key, []byte{}}, nil - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - - _, value, _ := storageTree.Get(word256.LeftPadWord256(key).Bytes()) - if value == nil { - return &core_types.StorageItem{key, []byte{}}, nil - } - return &core_types.StorageItem{key, value}, nil -} - -// Get the storage of the account with address 'address'. -func (this *accounts) Storage(address []byte) (*core_types.Storage, error) { - - state := this.burrowMint.GetState() - account := state.GetAccount(address) - storageItems := make([]core_types.StorageItem, 0) - if account == nil { - return &core_types.Storage{nil, storageItems}, nil - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - - storageTree.Iterate(func(key, value []byte) bool { - storageItems = append(storageItems, core_types.StorageItem{ - key, value}) - return false - }) - return &core_types.Storage{storageRoot, storageItems}, nil -} - -// Create a new account. -func (this *accounts) newAcc(address []byte) *account.Account { - return &account.Account{ - Address: address, - PubKey: nil, - Sequence: 0, - Balance: 0, - Code: nil, - StorageRoot: nil, - } -} - -// Filter for account code. -// Ops: == or != -// Could be used to match against nil, to see if an account is a contract account. -type AccountCodeFilter struct { - op string - value []byte - match func([]byte, []byte) bool -} - -func (this *AccountCodeFilter) Configure(fd *event.FilterData) error { - op := fd.Op - val, err := hex.DecodeString(fd.Value) - - if err != nil { - return fmt.Errorf("Wrong value type.") - } - if op == "==" { - this.match = func(a, b []byte) bool { - return bytes.Equal(a, b) - } - } else if op == "!=" { - this.match = func(a, b []byte) bool { - return !bytes.Equal(a, b) - } - } else { - return fmt.Errorf("Op: " + this.op + " is not supported for 'code' filtering") - } - this.op = op - this.value = val - return nil -} - -func (this *AccountCodeFilter) Match(v interface{}) bool { - acc, ok := v.(*account.Account) - if !ok { - return false - } - return this.match(acc.Code, this.value) -} - -// Filter for account balance. -// Ops: All -type AccountBalanceFilter struct { - op string - value int64 - match func(int64, int64) bool -} - -func (this *AccountBalanceFilter) Configure(fd *event.FilterData) error { - val, err := event.ParseNumberValue(fd.Value) - if err != nil { - return err - } - match, err2 := event.GetRangeFilter(fd.Op, "balance") - if err2 != nil { - return err2 - } - this.match = match - this.op = fd.Op - this.value = val - return nil -} - -func (this *AccountBalanceFilter) Match(v interface{}) bool { - acc, ok := v.(*account.Account) - if !ok { - return false - } - return this.match(int64(acc.Balance), this.value) -} diff --git a/manager/burrow-mint/burrow-mint.go b/manager/burrow-mint/burrow-mint.go deleted file mode 100644 index d3255452a3585f2da307dce4b65226e6767a2231..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/burrow-mint.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package burrowmint - -import ( - "bytes" - "fmt" - "sync" - "time" - - abci "github.com/tendermint/abci/types" - tendermint_events "github.com/tendermint/go-events" - wire "github.com/tendermint/go-wire" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" - sm "github.com/hyperledger/burrow/manager/burrow-mint/state" - manager_types "github.com/hyperledger/burrow/manager/types" - "github.com/hyperledger/burrow/txs" -) - -//-------------------------------------------------------------------------------- -// BurrowMint holds the current state, runs transactions, computes hashes. -// Typically two connections are opened by the tendermint core: -// one for mempool, one for consensus. - -type BurrowMint struct { - mtx sync.Mutex - - state *sm.State - cache *sm.BlockCache - checkCache *sm.BlockCache // for CheckTx (eg. so we get nonces right) - - evc *tendermint_events.EventCache - evsw tendermint_events.EventSwitch - - nTxs int // count txs in a block - logger logging_types.InfoTraceLogger -} - -// Currently we just wrap ConsensusEngine but this interface can give us -// arbitrary control over the type of ConsensusEngine at such a point that we -// support others. For example it can demand a 'marker' function: -// func IsBurrowMint_0.XX.XX_CompatibleConsensusEngine() -type BurrowMintCompatibleConsensusEngine interface { - consensus_types.ConsensusEngine -} - -// NOTE [ben] Compiler check to ensure BurrowMint successfully implements -// burrow/manager/types.Application -var _ manager_types.Application = (*BurrowMint)(nil) - -func (app *BurrowMint) CompatibleConsensus(consensusEngine consensus_types.ConsensusEngine) bool { - _, ok := consensusEngine.(BurrowMintCompatibleConsensusEngine) - return ok -} - -func (app *BurrowMint) GetState() *sm.State { - app.mtx.Lock() - defer app.mtx.Unlock() - return app.state.Copy() -} - -// TODO: this is used for call/callcode and to get nonces during mempool. -// the former should work on last committed state only and the later should -// be handled by the client, or a separate wallet-like nonce tracker thats not part of the app -func (app *BurrowMint) GetCheckCache() *sm.BlockCache { - return app.checkCache -} - -func NewBurrowMint(s *sm.State, evsw tendermint_events.EventSwitch, - logger logging_types.InfoTraceLogger) *BurrowMint { - return &BurrowMint{ - state: s, - cache: sm.NewBlockCache(s), - checkCache: sm.NewBlockCache(s), - evc: tendermint_events.NewEventCache(evsw), - evsw: evsw, - logger: logging.WithScope(logger, "BurrowMint"), - } -} - -// Implements manager/types.Application -func (app *BurrowMint) Info() (info abci.ResponseInfo) { - return abci.ResponseInfo{} -} - -// Implements manager/types.Application -func (app *BurrowMint) SetOption(key string, value string) (log string) { - return "" -} - -// Implements manager/types.Application -func (app *BurrowMint) DeliverTx(txBytes []byte) abci.Result { - app.nTxs += 1 - - // XXX: if we had tx ids we could cache the decoded txs on CheckTx - var n int - var err error - tx := new(txs.Tx) - buf := bytes.NewBuffer(txBytes) - wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) - if err != nil { - return abci.NewError(abci.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) - } - - err = sm.ExecTx(app.cache, *tx, true, app.evc, app.logger) - if err != nil { - return abci.NewError(abci.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) - } - - receipt := txs.GenerateReceipt(app.state.ChainID, *tx) - receiptBytes := wire.BinaryBytes(receipt) - return abci.NewResultOK(receiptBytes, "Success") -} - -// Implements manager/types.Application -func (app *BurrowMint) CheckTx(txBytes []byte) abci.Result { - var n int - var err error - tx := new(txs.Tx) - buf := bytes.NewBuffer(txBytes) - wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) - if err != nil { - return abci.NewError(abci.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) - } - - // TODO: map ExecTx errors to sensible abci error codes - err = sm.ExecTx(app.checkCache, *tx, false, nil, app.logger) - if err != nil { - return abci.NewError(abci.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) - } - receipt := txs.GenerateReceipt(app.state.ChainID, *tx) - receiptBytes := wire.BinaryBytes(receipt) - return abci.NewResultOK(receiptBytes, "Success") -} - -// Implements manager/types.Application -// Commit the state (called at end of block) -// NOTE: CheckTx/AppendTx must not run concurrently with Commit - -// the mempool should run during AppendTxs, but lock for Commit and Update -func (app *BurrowMint) Commit() (res abci.Result) { - app.mtx.Lock() // the lock protects app.state - defer app.mtx.Unlock() - - app.state.LastBlockHeight += 1 - logging.InfoMsg(app.logger, "Committing block", - "last_block_height", app.state.LastBlockHeight) - - // sync the AppendTx cache - app.cache.Sync() - - // Refresh the checkCache with the latest commited state - logging.InfoMsg(app.logger, "Resetting checkCache", - "txs", app.nTxs) - app.checkCache = sm.NewBlockCache(app.state) - - app.nTxs = 0 - - // save state to disk - app.state.Save() - - // flush events to listeners (XXX: note issue with blocking) - app.evc.Flush() - - // TODO: [ben] over the tendermint 0.6 TMSP interface we have - // no access to the block header implemented; - // On Tendermint v0.8 load the blockheader into the application - // state and remove the fixed 2-"seconds" per block internal clock. - // NOTE: set internal time as two seconds per block - app.state.LastBlockTime = app.state.LastBlockTime.Add(time.Duration(2) * time.Second) - appHash := app.state.Hash() - return abci.NewResultOK(appHash, "Success") -} - -func (app *BurrowMint) Query(query abci.RequestQuery) (res abci.ResponseQuery) { - return abci.ResponseQuery{ - Code: abci.CodeType_OK, - Log: "success", - } -} - -// BlockchainAware interface - -// Initialise the blockchain -// validators: genesis validators from tendermint core -func (app *BurrowMint) InitChain(validators []*abci.Validator) { - // Could verify agreement on initial validator set here -} - -// Signals the beginning of a block -func (app *BurrowMint) BeginBlock(hash []byte, header *abci.Header) { - -} - -// Signals the end of a blockchain, return value can be used to modify validator -// set and voting power distribution see our BlockchainAware interface -func (app *BurrowMint) EndBlock(height uint64) (respEndblock abci.ResponseEndBlock) { - // TODO: [Silas] Bondage - // TODO: [Silas] this might be a better place for us to dispatch new block - // events particularly if we want to separate ourselves from go-events - return -} diff --git a/manager/burrow-mint/evm/fake_app_state.go b/manager/burrow-mint/evm/fake_app_state.go deleted file mode 100644 index c9b1e7474ec2e077ee0dc3df0d2d7d14cea15832..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/evm/fake_app_state.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vm - -import ( - "fmt" - - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" - . "github.com/hyperledger/burrow/word256" -) - -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.Sprintf("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.Sprintf("Invalid account addr: %X", addr)) - } -} - -func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 { - _, ok := fas.accounts[addr.String()] - if !ok { - panic(fmt.Sprintf("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.Sprintf("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]) -} diff --git a/manager/burrow-mint/evm/types.go b/manager/burrow-mint/evm/types.go deleted file mode 100644 index f083812e59884c121e77291b56c5310e0d840c2c..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/evm/types.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vm - -import ( - "fmt" - - ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/hyperledger/burrow/word256" -) - -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.Sprintf("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 -} diff --git a/manager/burrow-mint/pipe.go b/manager/burrow-mint/pipe.go deleted file mode 100644 index cb91fb2a2fb97791c904271713cd81a59e4f00e9..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/pipe.go +++ /dev/null @@ -1,677 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package burrowmint - -import ( - "bytes" - "fmt" - - abci_types "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - db "github.com/tendermint/go-db" - go_events "github.com/tendermint/go-events" - wire "github.com/tendermint/go-wire" - tm_types "github.com/tendermint/tendermint/types" - - "github.com/hyperledger/burrow/account" - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - imath "github.com/hyperledger/burrow/common/math/integral" - "github.com/hyperledger/burrow/config" - consensus_types "github.com/hyperledger/burrow/consensus/types" - core_types "github.com/hyperledger/burrow/core/types" - "github.com/hyperledger/burrow/definitions" - edb_event "github.com/hyperledger/burrow/event" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" - vm "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/manager/burrow-mint/state" - manager_types "github.com/hyperledger/burrow/manager/types" - rpc_tm_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" -) - -type burrowMintPipe struct { - burrowMintState *state.State - burrowMint *BurrowMint - // Pipe implementations - accounts *accounts - blockchain blockchain_types.Blockchain - consensusEngine consensus_types.ConsensusEngine - events edb_event.EventEmitter - namereg *namereg - transactor *transactor - // Genesis cache - genesisDoc *genesis.GenesisDoc - genesisState *state.State - logger logging_types.InfoTraceLogger -} - -// Interface type assertions -var _ definitions.Pipe = (*burrowMintPipe)(nil) - -var _ definitions.TendermintPipe = (*burrowMintPipe)(nil) - -func NewBurrowMintPipe(moduleConfig *config.ModuleConfig, - eventSwitch go_events.EventSwitch, - logger logging_types.InfoTraceLogger) (*burrowMintPipe, error) { - - startedState, genesisDoc, err := startState(moduleConfig.DataDir, - moduleConfig.Config.GetString("db_backend"), moduleConfig.GenesisFile, - moduleConfig.ChainId) - if err != nil { - return nil, fmt.Errorf("Failed to start state: %v", err) - } - logger = logging.WithScope(logger, "BurrowMintPipe") - // assert ChainId matches genesis ChainId - logging.InfoMsg(logger, "Loaded state", - "chainId", startedState.ChainID, - "lastBlockHeight", startedState.LastBlockHeight, - "lastBlockHash", startedState.LastBlockHash) - // start the application - burrowMint := NewBurrowMint(startedState, eventSwitch, logger) - - // initialise the components of the pipe - events := edb_event.NewEvents(eventSwitch, logger) - accounts := newAccounts(burrowMint) - namereg := newNameReg(burrowMint) - - pipe := &burrowMintPipe{ - burrowMintState: startedState, - burrowMint: burrowMint, - accounts: accounts, - events: events, - namereg: namereg, - // We need to set transactor later since we are introducing a mutual dependency - // NOTE: this will be cleaned up when the RPC is unified - transactor: nil, - // genesis cache - genesisDoc: genesisDoc, - genesisState: nil, - // consensus and blockchain should both be loaded into the pipe by a higher - // authority - this is a sort of dependency injection pattern - consensusEngine: nil, - blockchain: nil, - logger: logger, - } - - // NOTE: [Silas] - // This is something of a loopback, but seems like a nicer option than - // transactor calling the Tendermint native RPC (as it was before), - // or indeed calling this RPC over the wire given that we have direct access. - // - // We could just hand transactor a copy of Pipe, but doing it this way seems - // like a reasonably minimal and flexible way of providing transactor with the - // broadcast function it needs, without making it explicitly - // aware of/depend on Pipe. - transactor := newTransactor(moduleConfig.ChainId, eventSwitch, burrowMint, - events, - func(tx txs.Tx) error { - _, err := pipe.BroadcastTxSync(tx) - return err - }) - - pipe.transactor = transactor - return pipe, nil -} - -//------------------------------------------------------------------------------ -// Start state - -// Start state tries to load the existing state in the data directory; -// if an existing database can be loaded, it will validate that the -// chainId in the genesis of that loaded state matches the asserted chainId. -// If no state can be loaded, the JSON genesis file will be loaded into the -// state database as the zero state. -func startState(dataDir, backend, genesisFile, chainId string) (*state.State, - *genesis.GenesisDoc, error) { - // avoid Tendermints PanicSanity and return a clean error - if backend != db.MemDBBackendStr && - backend != db.LevelDBBackendStr { - return nil, nil, fmt.Errorf("Database backend %s is not supported "+ - "by burrowmint", backend) - } - - stateDB := db.NewDB("burrowmint", backend, dataDir) - newState := state.LoadState(stateDB) - var genesisDoc *genesis.GenesisDoc - if newState == nil { - genesisDoc, newState = state.MakeGenesisStateFromFile(stateDB, genesisFile) - newState.Save() - buf, n, err := new(bytes.Buffer), new(int), new(error) - wire.WriteJSON(genesisDoc, buf, n, err) - stateDB.Set(genesis.GenDocKey, buf.Bytes()) - if *err != nil { - return nil, nil, fmt.Errorf("Unable to write genesisDoc to db: %v", err) - } - } else { - loadedGenesisDocBytes := stateDB.Get(genesis.GenDocKey) - err := new(error) - wire.ReadJSONPtr(&genesisDoc, loadedGenesisDocBytes, err) - if *err != nil { - return nil, nil, fmt.Errorf("Unable to read genesisDoc from db on startState: %v", err) - } - // assert loaded genesis doc has the same chainId as the provided chainId - if genesisDoc.ChainID != chainId { - return nil, nil, fmt.Errorf("ChainId (%s) loaded from genesis document in existing database does not match"+ - " configuration chainId (%s).", genesisDoc.ChainID, chainId) - } - } - - return newState, genesisDoc, nil -} - -//------------------------------------------------------------------------------ -// Implement definitions.Pipe for burrowMintPipe - -func (pipe *burrowMintPipe) Logger() logging_types.InfoTraceLogger { - return pipe.logger -} - -func (pipe *burrowMintPipe) Accounts() definitions.Accounts { - return pipe.accounts -} - -func (pipe *burrowMintPipe) Blockchain() blockchain_types.Blockchain { - return pipe.blockchain -} - -func (pipe *burrowMintPipe) Events() edb_event.EventEmitter { - return pipe.events -} - -func (pipe *burrowMintPipe) NameReg() definitions.NameReg { - return pipe.namereg -} - -func (pipe *burrowMintPipe) Transactor() definitions.Transactor { - return pipe.transactor -} - -func (pipe *burrowMintPipe) GetApplication() manager_types.Application { - return pipe.burrowMint -} - -func (pipe *burrowMintPipe) SetBlockchain( - blockchain blockchain_types.Blockchain) error { - if pipe.blockchain == nil { - pipe.blockchain = blockchain - } else { - return fmt.Errorf("Failed to set Blockchain for pipe; already set") - } - return nil -} - -func (pipe *burrowMintPipe) GetBlockchain() blockchain_types.Blockchain { - return pipe.blockchain -} - -func (pipe *burrowMintPipe) SetConsensusEngine( - consensusEngine consensus_types.ConsensusEngine) error { - if pipe.consensusEngine == nil { - pipe.consensusEngine = consensusEngine - } else { - return fmt.Errorf("Failed to set consensus engine for pipe; already set") - } - return nil -} - -func (pipe *burrowMintPipe) GetConsensusEngine() consensus_types.ConsensusEngine { - return pipe.consensusEngine -} - -func (pipe *burrowMintPipe) GetTendermintPipe() (definitions.TendermintPipe, - error) { - return definitions.TendermintPipe(pipe), nil -} - -func (pipe *burrowMintPipe) consensusAndManagerEvents() edb_event.EventEmitter { - // NOTE: [Silas] We could initialise this lazily and use the cached instance, - // but for the time being that feels like a premature optimisation - return edb_event.Multiplex(pipe.events, pipe.consensusEngine.Events()) -} - -//------------------------------------------------------------------------------ -// Implement definitions.TendermintPipe for burrowMintPipe -func (pipe *burrowMintPipe) Subscribe(eventId string, - rpcResponseWriter func(result rpc_tm_types.BurrowResult)) (*rpc_tm_types.ResultSubscribe, error) { - subscriptionId, err := edb_event.GenerateSubId() - if err != nil { - return nil, err - logging.InfoMsg(pipe.logger, "Subscribing to event", - "eventId", eventId, "subscriptionId", subscriptionId) - } - pipe.consensusAndManagerEvents().Subscribe(subscriptionId, eventId, - func(eventData txs.EventData) { - result := rpc_tm_types.BurrowResult( - &rpc_tm_types.ResultEvent{ - Event: eventId, - Data: txs.EventData(eventData)}) - // NOTE: EventSwitch callbacks must be nonblocking - rpcResponseWriter(result) - }) - return &rpc_tm_types.ResultSubscribe{ - SubscriptionId: subscriptionId, - Event: eventId, - }, nil -} - -func (pipe *burrowMintPipe) Unsubscribe(subscriptionId string) (*rpc_tm_types.ResultUnsubscribe, error) { - logging.InfoMsg(pipe.logger, "Unsubscribing from event", - "subscriptionId", subscriptionId) - pipe.consensusAndManagerEvents().Unsubscribe(subscriptionId) - return &rpc_tm_types.ResultUnsubscribe{SubscriptionId: subscriptionId}, nil -} -func (pipe *burrowMintPipe) GenesisState() *state.State { - if pipe.genesisState == nil { - memoryDatabase := db.NewMemDB() - pipe.genesisState = state.MakeGenesisState(memoryDatabase, pipe.genesisDoc) - } - return pipe.genesisState -} - -func (pipe *burrowMintPipe) GenesisHash() []byte { - return pipe.GenesisState().Hash() -} - -func (pipe *burrowMintPipe) Status() (*rpc_tm_types.ResultStatus, error) { - if pipe.consensusEngine == nil { - return nil, fmt.Errorf("Consensus Engine not initialised in burrowmint pipe.") - } - latestHeight := pipe.blockchain.Height() - var ( - latestBlockMeta *tm_types.BlockMeta - latestBlockHash []byte - latestBlockTime int64 - ) - if latestHeight != 0 { - latestBlockMeta = pipe.blockchain.BlockMeta(latestHeight) - latestBlockHash = latestBlockMeta.Header.Hash() - latestBlockTime = latestBlockMeta.Header.Time.UnixNano() - } - return &rpc_tm_types.ResultStatus{ - NodeInfo: pipe.consensusEngine.NodeInfo(), - GenesisHash: pipe.GenesisHash(), - PubKey: pipe.consensusEngine.PublicValidatorKey(), - LatestBlockHash: latestBlockHash, - LatestBlockHeight: latestHeight, - LatestBlockTime: latestBlockTime}, nil -} - -func (pipe *burrowMintPipe) ChainId() (*rpc_tm_types.ResultChainId, error) { - if pipe.blockchain == nil { - return nil, fmt.Errorf("Blockchain not initialised in burrowmint pipe.") - } - chainId := pipe.blockchain.ChainId() - - return &rpc_tm_types.ResultChainId{ - ChainName: chainId, // MARMOT: copy ChainId for ChainName as a placehodlder - ChainId: chainId, - GenesisHash: pipe.GenesisHash(), - }, nil -} - -func (pipe *burrowMintPipe) NetInfo() (*rpc_tm_types.ResultNetInfo, error) { - listening := pipe.consensusEngine.IsListening() - listeners := []string{} - for _, listener := range pipe.consensusEngine.Listeners() { - listeners = append(listeners, listener.String()) - } - peers := pipe.consensusEngine.Peers() - return &rpc_tm_types.ResultNetInfo{ - Listening: listening, - Listeners: listeners, - Peers: peers, - }, nil -} - -func (pipe *burrowMintPipe) Genesis() (*rpc_tm_types.ResultGenesis, error) { - return &rpc_tm_types.ResultGenesis{ - // TODO: [ben] sharing pointer to unmutated GenesisDoc, but is not immutable - Genesis: pipe.genesisDoc, - }, nil -} - -// Accounts -func (pipe *burrowMintPipe) GetAccount(address []byte) (*rpc_tm_types.ResultGetAccount, - error) { - cache := pipe.burrowMint.GetCheckCache() - account := cache.GetAccount(address) - return &rpc_tm_types.ResultGetAccount{Account: account}, nil -} - -func (pipe *burrowMintPipe) ListAccounts() (*rpc_tm_types.ResultListAccounts, error) { - var blockHeight int - var accounts []*account.Account - state := pipe.burrowMint.GetState() - blockHeight = state.LastBlockHeight - state.GetAccounts().Iterate(func(key []byte, value []byte) bool { - accounts = append(accounts, account.DecodeAccount(value)) - return false - }) - return &rpc_tm_types.ResultListAccounts{blockHeight, accounts}, nil -} - -func (pipe *burrowMintPipe) GetStorage(address, key []byte) (*rpc_tm_types.ResultGetStorage, - error) { - state := pipe.burrowMint.GetState() - // 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, exists := storageTree.Get( - word256.LeftPadWord256(key).Bytes()) - if !exists { - // value == nil { - return &rpc_tm_types.ResultGetStorage{key, nil}, nil - } - return &rpc_tm_types.ResultGetStorage{key, value}, nil -} - -func (pipe *burrowMintPipe) DumpStorage(address []byte) (*rpc_tm_types.ResultDumpStorage, - error) { - state := pipe.burrowMint.GetState() - account := state.GetAccount(address) - if account == nil { - return nil, fmt.Errorf("UnknownAddress: %X", address) - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - storageItems := []rpc_tm_types.StorageItem{} - storageTree.Iterate(func(key []byte, value []byte) bool { - storageItems = append(storageItems, rpc_tm_types.StorageItem{key, - value}) - return false - }) - return &rpc_tm_types.ResultDumpStorage{storageRoot, storageItems}, nil -} - -// Call -// NOTE: this function is used from 46657 and has sibling on 1337 -// in transactor.go -// TODO: [ben] resolve incompatibilities in byte representation for 0.12.0 release -func (pipe *burrowMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall, - error) { - if vm.RegisteredNativeContract(word256.LeftPadWord256(toAddress)) { - return nil, fmt.Errorf("Attempt to call native contract at address "+ - "%X, but native contracts can not be called directly. Use a deployed "+ - "contract that calls the native function instead.", toAddress) - } - st := pipe.burrowMint.GetState() - cache := state.NewBlockCache(st) - outAcc := cache.GetAccount(toAddress) - if outAcc == nil { - return nil, fmt.Errorf("Account %x does not exist", toAddress) - } - if fromAddress == nil { - fromAddress = []byte{} - } - callee := toVMAccount(outAcc) - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - gas := gasLimit - ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - // here return bytes are not hex encoded; on the sibling function - // they are - return &rpc_tm_types.ResultCall{Return: ret, GasUsed: gasUsed}, nil -} - -func (pipe *burrowMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tm_types.ResultCall, - error) { - st := pipe.burrowMint.GetState() - cache := pipe.burrowMint.GetCheckCache() - callee := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - gas := gasLimit - ret, err := vmach.Call(caller, callee, code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - return &rpc_tm_types.ResultCall{Return: ret, GasUsed: gasUsed}, nil -} - -// TODO: [ben] deprecate as we should not allow unsafe behaviour -// where a user is allowed to send a private key over the wire, -// especially unencrypted. -func (pipe *burrowMintPipe) SignTransaction(tx txs.Tx, - privAccounts []*account.PrivAccount) (*rpc_tm_types.ResultSignTx, - error) { - - for i, privAccount := range privAccounts { - if privAccount == nil || privAccount.PrivKey == nil { - return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) - } - } - switch tx.(type) { - case *txs.SendTx: - sendTx := tx.(*txs.SendTx) - for i, input := range sendTx.Inputs { - input.PubKey = privAccounts[i].PubKey - input.Signature = privAccounts[i].Sign(pipe.transactor.chainID, sendTx) - } - case *txs.CallTx: - callTx := tx.(*txs.CallTx) - callTx.Input.PubKey = privAccounts[0].PubKey - callTx.Input.Signature = privAccounts[0].Sign(pipe.transactor.chainID, callTx) - case *txs.BondTx: - bondTx := tx.(*txs.BondTx) - // the first privaccount corresponds to the BondTx pub key. - // the rest to the inputs - bondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, bondTx).(crypto.SignatureEd25519) - for i, input := range bondTx.Inputs { - input.PubKey = privAccounts[i+1].PubKey - input.Signature = privAccounts[i+1].Sign(pipe.transactor.chainID, bondTx) - } - case *txs.UnbondTx: - unbondTx := tx.(*txs.UnbondTx) - unbondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, unbondTx).(crypto.SignatureEd25519) - case *txs.RebondTx: - rebondTx := tx.(*txs.RebondTx) - rebondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, rebondTx).(crypto.SignatureEd25519) - } - return &rpc_tm_types.ResultSignTx{tx}, nil -} - -// Name registry -func (pipe *burrowMintPipe) GetName(name string) (*rpc_tm_types.ResultGetName, error) { - currentState := pipe.burrowMint.GetState() - entry := currentState.GetNameRegEntry(name) - if entry == nil { - return nil, fmt.Errorf("Name %s not found", name) - } - return &rpc_tm_types.ResultGetName{entry}, nil -} - -func (pipe *burrowMintPipe) ListNames() (*rpc_tm_types.ResultListNames, error) { - var blockHeight int - var names []*core_types.NameRegEntry - currentState := pipe.burrowMint.GetState() - blockHeight = currentState.LastBlockHeight - currentState.GetNames().Iterate(func(key []byte, value []byte) bool { - names = append(names, state.DecodeNameRegEntry(value)) - return false - }) - return &rpc_tm_types.ResultListNames{blockHeight, names}, nil -} - -func (pipe *burrowMintPipe) broadcastTx(tx txs.Tx, - callback func(res *abci_types.Response)) (*rpc_tm_types.ResultBroadcastTx, error) { - - txBytes, err := txs.EncodeTx(tx) - if err != nil { - return nil, fmt.Errorf("Error encoding transaction: %v", err) - } - err = pipe.consensusEngine.BroadcastTransaction(txBytes, callback) - if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) - } - return &rpc_tm_types.ResultBroadcastTx{}, nil -} - -// Memory pool -// NOTE: txs must be signed -func (pipe *burrowMintPipe) BroadcastTxAsync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) { - return pipe.broadcastTx(tx, nil) -} - -func (pipe *burrowMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) { - responseChannel := make(chan *abci_types.Response, 1) - _, err := pipe.broadcastTx(tx, - func(res *abci_types.Response) { - responseChannel <- res - }) - if err != nil { - return nil, err - } - // NOTE: [ben] This Response is set in /consensus/tendermint/local_client.go - // a call to Application, here implemented by BurrowMint, over local callback, - // or abci RPC call. Hence the result is determined by BurrowMint/burrowmint.go - // CheckTx() Result (Result converted to ReqRes into Response returned here) - // NOTE: [ben] BroadcastTx just calls CheckTx in Tendermint (oddly... [Silas]) - response := <-responseChannel - responseCheckTx := response.GetCheckTx() - if responseCheckTx == nil { - return nil, fmt.Errorf("Error, application did not return CheckTx response.") - } - resultBroadCastTx := &rpc_tm_types.ResultBroadcastTx{ - Code: responseCheckTx.Code, - Data: responseCheckTx.Data, - Log: responseCheckTx.Log, - } - switch responseCheckTx.Code { - case abci_types.CodeType_OK: - return resultBroadCastTx, nil - case abci_types.CodeType_EncodingError: - return resultBroadCastTx, fmt.Errorf(resultBroadCastTx.Log) - case abci_types.CodeType_InternalError: - return resultBroadCastTx, fmt.Errorf(resultBroadCastTx.Log) - default: - logging.InfoMsg(pipe.logger, "Unknown error returned from Tendermint CheckTx on BroadcastTxSync", - "application", "burrowmint", - "abci_code_type", responseCheckTx.Code, - "abci_log", responseCheckTx.Log, - ) - return resultBroadCastTx, fmt.Errorf("Unknown error returned: " + responseCheckTx.Log) - } -} - -func (pipe *burrowMintPipe) ListUnconfirmedTxs(maxTxs int) (*rpc_tm_types.ResultListUnconfirmedTxs, error) { - // Get all transactions for now - transactions, err := pipe.consensusEngine.ListUnconfirmedTxs(maxTxs) - if err != nil { - return nil, err - } - return &rpc_tm_types.ResultListUnconfirmedTxs{ - N: len(transactions), - Txs: transactions, - }, nil -} - -// Returns the current blockchain height and metadata for a range of blocks -// between minHeight and maxHeight. Only returns maxBlockLookback block metadata -// from the top of the range of blocks. -// Passing 0 for maxHeight sets the upper height of the range to the current -// blockchain height. -func (pipe *burrowMintPipe) BlockchainInfo(minHeight, maxHeight, - maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error) { - - latestHeight := pipe.blockchain.Height() - - if maxHeight < 1 { - maxHeight = latestHeight - } else { - maxHeight = imath.MinInt(latestHeight, maxHeight) - } - if minHeight < 1 { - minHeight = imath.MaxInt(1, maxHeight-maxBlockLookback) - } - - blockMetas := []*tm_types.BlockMeta{} - for height := maxHeight; height >= minHeight; height-- { - blockMeta := pipe.blockchain.BlockMeta(height) - blockMetas = append(blockMetas, blockMeta) - } - - return &rpc_tm_types.ResultBlockchainInfo{ - LastHeight: latestHeight, - BlockMetas: blockMetas, - }, nil -} - -func (pipe *burrowMintPipe) GetBlock(height int) (*rpc_tm_types.ResultGetBlock, error) { - return &rpc_tm_types.ResultGetBlock{ - Block: pipe.blockchain.Block(height), - BlockMeta: pipe.blockchain.BlockMeta(height), - }, nil -} - -func (pipe *burrowMintPipe) ListValidators() (*rpc_tm_types.ResultListValidators, error) { - validators := pipe.consensusEngine.ListValidators() - consensusState := pipe.consensusEngine.ConsensusState() - // TODO: when we reintroduce support for bonding and unbonding update this - // to reflect the mutable bonding state - return &rpc_tm_types.ResultListValidators{ - BlockHeight: consensusState.Height, - BondedValidators: validators, - UnbondingValidators: nil, - }, nil -} - -func (pipe *burrowMintPipe) DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) { - statesMap := pipe.consensusEngine.PeerConsensusStates() - peerStates := make([]*rpc_tm_types.ResultPeerConsensusState, len(statesMap)) - for key, peerState := range statesMap { - peerStates = append(peerStates, &rpc_tm_types.ResultPeerConsensusState{ - PeerKey: key, - PeerConsensusState: peerState, - }) - } - dump := rpc_tm_types.ResultDumpConsensusState{ - ConsensusState: pipe.consensusEngine.ConsensusState(), - PeerConsensusStates: peerStates, - } - return &dump, nil -} diff --git a/manager/burrow-mint/state/block_cache.go b/manager/burrow-mint/state/block_cache.go deleted file mode 100644 index 3ba8a456447db7e6256e13383ba68f6977297add..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/block_cache.go +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - "bytes" - "fmt" - "sort" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/sanity" - core_types "github.com/hyperledger/burrow/core/types" - . "github.com/hyperledger/burrow/word256" - - dbm "github.com/tendermint/go-db" - "github.com/tendermint/go-merkle" -) - -func makeStorage(db dbm.DB, root []byte) merkle.Tree { - storage := merkle.NewIAVLTree(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 { - sanity.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 { - sanity.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 { - sanity.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_) - } - 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 { - sanity.PanicSanity("SetStorage() on a removed account") - } - cache.storages[Tuple256{addr, key}] = storageInfo{value, true} -} - -// BlockCache.storage -//------------------------------------- -// BlockCache.names - -func (cache *BlockCache) GetNameRegEntry(name string) *core_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 *core_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 { - sanity.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 { - sanity.PanicCrisis(fmt.Sprintf("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 { - sanity.PanicCrisis(fmt.Sprintf("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 *core_types.NameRegEntry - removed bool - dirty bool -} - -func (nInfo nameInfo) unpack() (*core_types.NameRegEntry, bool, bool) { - return nInfo.name, nInfo.removed, nInfo.dirty -} diff --git a/manager/burrow-mint/state/common.go b/manager/burrow-mint/state/common.go deleted file mode 100644 index 37cf6ca18e6ce7dec613448d32a81a2146d2feb6..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/common.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - . "github.com/hyperledger/burrow/word256" -) - -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 -} diff --git a/manager/burrow-mint/state/genesis_test.go b/manager/burrow-mint/state/genesis_test.go deleted file mode 100644 index 983b5cb58432e1d1603f7dd9c1de77483609b46d..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/genesis_test.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - "bytes" - "encoding/hex" - "fmt" - "sort" - "testing" - "time" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/random" - genesis "github.com/hyperledger/burrow/genesis" - ptypes "github.com/hyperledger/burrow/permission/types" - - tdb "github.com/tendermint/go-db" - "github.com/tendermint/tendermint/types" -) - -var chain_id = "lone_ranger" -var addr1, _ = hex.DecodeString("964B1493BBE3312278B7DEB94C39149F7899A345") -var send1 = 1 -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 := genesis.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.Equal(acc.Address, addr1) { - 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 := genesis.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) - } -} - -//------------------------------------------------------- - -func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*State, []*acm.PrivAccount, []*types.PrivValidator) { - db := tdb.NewMemDB() - genDoc, privAccounts, privValidators := RandGenesisDoc(numAccounts, randBalance, minBalance, numValidators, randBonded, minBonded) - s0 := MakeGenesisState(db, genDoc) - s0.Save() - return s0, privAccounts, privValidators -} - -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: random.RandInt(), - Balance: minBalance, - Permissions: perms, - } - if randBalance { - acc.Balance += int64(random.RandUint32()) - } - return acc, privAccount -} - -func RandGenesisDoc(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*genesis.GenesisDoc, []*acm.PrivAccount, []*types.PrivValidator) { - accounts := make([]genesis.GenesisAccount, numAccounts) - privAccounts := make([]*acm.PrivAccount, numAccounts) - defaultPerms := ptypes.DefaultAccountPermissions - for i := 0; i < numAccounts; i++ { - account, privAccount := RandAccount(randBalance, minBalance) - accounts[i] = genesis.GenesisAccount{ - Address: account.Address, - Amount: account.Balance, - Permissions: &defaultPerms, // This will get copied into each state.Account. - } - privAccounts[i] = privAccount - } - validators := make([]genesis.GenesisValidator, numValidators) - privValidators := make([]*types.PrivValidator, numValidators) - for i := 0; i < numValidators; i++ { - valInfo, privVal := types.RandValidator(randBonded, minBonded) - validators[i] = genesis.GenesisValidator{ - PubKey: valInfo.PubKey, - Amount: valInfo.VotingPower, - UnbondTo: []genesis.BasicAccount{ - { - Address: valInfo.PubKey.Address(), - Amount: valInfo.VotingPower, - }, - }, - } - privValidators[i] = privVal - } - sort.Sort(types.PrivValidatorsByAddress(privValidators)) - return &genesis.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: "tendermint_test", - Accounts: accounts, - Validators: validators, - }, privAccounts, privValidators - -} diff --git a/manager/burrow-mint/state/permissions_test.go b/manager/burrow-mint/state/permissions_test.go deleted file mode 100644 index 4f550efde98d9cb239d742c58cb84d84d0535334..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/permissions_test.go +++ /dev/null @@ -1,1314 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - "bytes" - "fmt" - "strconv" - "testing" - "time" - - acm "github.com/hyperledger/burrow/account" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/go-db" - "github.com/tendermint/go-events" - "github.com/tendermint/tendermint/config/tendermint_test" -) - -func init() { - tendermint_test.ResetConfig("permissions_test") -} - -var ( - dbBackend = "memdb" - dbDir = "" - permissionsContract = vm.SNativeContracts()["Permissions"] -) - -/* -Permission Tests: - -- SendTx: -x - 1 input, no perm, call perm, create perm -x - 1 input, perm -x - 2 inputs, one with perm one without - -- CallTx, CALL -x - 1 input, no perm, send perm, create perm -x - 1 input, perm -x - contract runs call but doesn't have call perm -x - contract runs call and has call perm -x - contract runs call (with perm), runs contract that runs call (without perm) -x - contract runs call (with perm), runs contract that runs call (with perm) - -- CallTx for Create, CREATE -x - 1 input, no perm, send perm, call perm -x - 1 input, perm -x - contract runs create but doesn't have create perm -x - contract runs create but has perm -x - contract runs call with empty address (has call and create perm) - -- NameTx - - no perm, send perm, call perm - - with perm - -- BondTx -x - 1 input, no perm -x - 1 input, perm -x - 1 bonder with perm, input without send or bond -x - 1 bonder with perm, input with send -x - 1 bonder with perm, input with bond -x - 2 inputs, one with perm one without - -- SendTx for new account -x - 1 input, 1 unknown ouput, input with send, not create (fail) -x - 1 input, 1 unknown ouput, input with send and create (pass) -x - 2 inputs, 1 unknown ouput, both inputs with send, one with create, one without (fail) -x - 2 inputs, 1 known output, 1 unknown ouput, one input with create, one without (fail) -x - 2 inputs, 1 unknown ouput, both inputs with send, both inputs with create (pass ) -x - 2 inputs, 1 known output, 1 unknown ouput, both inputs with create, (pass) - - -- CALL for new account -x - unknown output, without create (fail) -x - unknown output, with create (pass) - - -- SNative (CallTx, CALL): - - for each of CallTx, Call -x - call each snative without permission, fails -x - call each snative with permission, pass - - list: -x - base: has,set,unset -x - globals: set -x - roles: has, add, rm - - -*/ - -// keys -var user = makeUsers(10) -var chainID = "testchain" -var logger, _ = lifecycle.NewStdErrLogger() - -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 -} - -var ( - PermsAllFalse = ptypes.ZeroAccountPermissions -) - -func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.GenesisDoc { - genAccounts := []genesis.GenesisAccount{} - for _, u := range user[:5] { - accountPermCopy := accountPerm // Create new instance for custom overridability. - genAccounts = append(genAccounts, genesis.GenesisAccount{ - Address: u.Address, - Amount: 1000000, - Permissions: &accountPermCopy, - }) - } - - return genesis.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: chainID, - Params: &genesis.GenesisParams{ - GlobalPermissions: &globalPerm, - }, - Accounts: genAccounts, - Validators: []genesis.GenesisValidator{ - genesis.GenesisValidator{ - PubKey: user[0].PubKey.(crypto.PubKeyEd25519), - Amount: 10, - UnbondTo: []genesis.BasicAccount{ - genesis.BasicAccount{ - Address: user[0].Address, - }, - }, - }, - }, - } -} - -func TestSendFails(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) - genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) - genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------- - // send txs - - // simple send tx should fail - tx := txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple send tx with call perm should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[4].Address, 5) - tx.SignInput(chainID, 0, user[2]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple send tx with create perm should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[4].Address, 5) - tx.SignInput(chainID, 0, user[3]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple send tx to unknown account without create_account perm should fail - acc := blockCache.GetAccount(user[3].Address) - acc.Permissions.Base.Set(ptypes.Send, true) - blockCache.UpdateAccount(acc) - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[6].Address, 5) - tx.SignInput(chainID, 0, user[3]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } -} - -func TestName(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) - genDoc.Accounts[1].Permissions.Base.Set(ptypes.Name, true) - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------- - // name txs - - // simple name tx without perm should fail - tx, err := txs.NewNameTx(st, user[0].PubKey, "somename", "somedata", 10000, 100) - if err != nil { - t.Fatal(err) - } - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple name tx with perm should pass - tx, err = txs.NewNameTx(st, user[1].PubKey, "somename", "somedata", 10000, 100) - if err != nil { - t.Fatal(err) - } - tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal(err) - } -} - -func TestCallFails(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) - genDoc.Accounts[2].Permissions.Base.Set(ptypes.Call, true) - genDoc.Accounts[3].Permissions.Base.Set(ptypes.CreateContract, true) - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------- - // call txs - - // simple call tx should fail - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, user[4].Address, nil, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call tx with send permission should fail - tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, user[4].Address, nil, 100, 100, 100) - tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call tx with create permission should fail - tx, _ = txs.NewCallTx(blockCache, user[3].PubKey, user[4].Address, nil, 100, 100, 100) - tx.Sign(chainID, user[3]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - //------------------- - // create txs - - // simple call create tx should fail - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, nil, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call create tx with send perm should fail - tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, nil, nil, 100, 100, 100) - tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call create tx with call perm should fail - tx, _ = txs.NewCallTx(blockCache, user[2].PubKey, nil, nil, 100, 100, 100) - tx.Sign(chainID, user[2]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } -} - -func TestSendPermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - // A single input, having the permission, should succeed - tx := txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - - // Two inputs, one with permission, one without, should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[2].Address, 10) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } -} - -func TestCallPermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------------------ - // call to simple contract - fmt.Println("\n##### SIMPLE CONTRACT") - - // create simple contract - simpleContractAddr := NewContractAddress(user[0].Address, 100) - simpleAcc := &acm.Account{ - Address: simpleContractAddr, - Balance: 0, - Code: []byte{0x60}, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - st.UpdateAccount(simpleAcc) - - // A single input, having the permission, should succeed - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, simpleContractAddr, nil, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - - //---------------------------------------------------------- - // call to contract that calls simple contract - without perm - fmt.Println("\n##### CALL TO SIMPLE CONTRACT (FAIL)") - - // create contract that calls the simple contract - contractCode := callContractCode(simpleContractAddr) - caller1ContractAddr := NewContractAddress(user[0].Address, 101) - caller1Acc := &acm.Account{ - Address: caller1ContractAddr, - Balance: 10000, - Code: contractCode, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - blockCache.UpdateAccount(caller1Acc) - - // A single input, having the permission, but the contract doesn't have permission - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } - - //---------------------------------------------------------- - // call to contract that calls simple contract - with perm - fmt.Println("\n##### CALL TO SIMPLE CONTRACT (PASS)") - - // A single input, having the permission, and the contract has permission - caller1Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception:", exception) - } - - //---------------------------------------------------------- - // call to contract that calls contract that calls simple contract - without perm - // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. - // caller1Contract does not have call perms, but caller2Contract does. - fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") - - contractCode2 := callContractCode(caller1ContractAddr) - caller2ContractAddr := NewContractAddress(user[0].Address, 102) - caller2Acc := &acm.Account{ - Address: caller2ContractAddr, - Balance: 1000, - Code: contractCode2, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - caller1Acc.Permissions.Base.Set(ptypes.Call, false) - caller2Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - blockCache.UpdateAccount(caller2Acc) - - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } - - //---------------------------------------------------------- - // call to contract that calls contract that calls simple contract - without perm - // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. - // both caller1 and caller2 have permission - fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") - - caller1Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } -} - -func TestCreatePermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateContract, true) // give the 0 account permission - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------------------ - // create a simple contract - fmt.Println("\n##### CREATE SIMPLE CONTRACT") - - contractCode := []byte{0x60} - createCode := wrapContractForCreate(contractCode) - - // A single input, having the permission, should succeed - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, nil, createCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - // ensure the contract is there - contractAddr := NewContractAddress(tx.Input.Address, tx.Input.Sequence) - contractAcc := blockCache.GetAccount(contractAddr) - if contractAcc == nil { - t.Fatalf("failed to create contract %X", contractAddr) - } - if !bytes.Equal(contractAcc.Code, contractCode) { - t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, contractCode) - } - - //------------------------------ - // create contract that uses the CREATE op - fmt.Println("\n##### CREATE FACTORY") - - contractCode = []byte{0x60} - createCode = wrapContractForCreate(contractCode) - factoryCode := createContractCode() - createFactoryCode := wrapContractForCreate(factoryCode) - - // A single input, having the permission, should succeed - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, createFactoryCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - // ensure the contract is there - contractAddr = NewContractAddress(tx.Input.Address, tx.Input.Sequence) - contractAcc = blockCache.GetAccount(contractAddr) - if contractAcc == nil { - t.Fatalf("failed to create contract %X", contractAddr) - } - if !bytes.Equal(contractAcc.Code, factoryCode) { - t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, factoryCode) - } - - //------------------------------ - // call the contract (should FAIL) - fmt.Println("\n###### CALL THE FACTORY (FAIL)") - - // A single input, having the permission, should succeed - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(contractAddr)) // - if exception == "" { - t.Fatal("expected exception") - } - - //------------------------------ - // call the contract (should PASS) - fmt.Println("\n###### CALL THE FACTORY (PASS)") - - contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) - blockCache.UpdateAccount(contractAcc) - - // A single input, having the permission, should succeed - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(contractAddr)) // - if exception != "" { - t.Fatal("unexpected exception", exception) - } - - //-------------------------------- - fmt.Println("\n##### CALL to empty address") - zeroAddr := LeftPadBytes([]byte{}, 20) - code := callContractCode(zeroAddr) - - contractAddr = NewContractAddress(user[0].Address, 110) - contractAcc = &acm.Account{ - Address: contractAddr, - Balance: 1000, - Code: code, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - contractAcc.Permissions.Base.Set(ptypes.Call, true) - contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) - blockCache.UpdateAccount(contractAcc) - - // this should call the 0 address but not create ... - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 10000, 100) - tx.Sign(chainID, user[0]) - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(zeroAddr)) // - if exception != "" { - t.Fatal("unexpected exception", exception) - } - zeroAcc := blockCache.GetAccount(zeroAddr) - if len(zeroAcc.Code) != 0 { - t.Fatal("the zero account was given code from a CALL!") - } -} - -/* TODO -func TestBondPermission(t *testing.T) { - stateDB := dbm.NewDB("state",dbBackend,dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - var bondAcc *acm.Account - - //------------------------------ - // one bonder without permission should fail - tx, _ := txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[1]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - //------------------------------ - // one bonder with permission should pass - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Unexpected error", err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input without send should fail - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input with send should pass - sendAcc := blockCache.GetAccount(user[2].Address) - sendAcc.Permissions.Base.Set(ptypes.Send, true) - blockCache.UpdateAccount(sendAcc) - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Unexpected error", err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input with bond should pass - sendAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(sendAcc) - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Unexpected error", err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input from that bonder and an input without send or bond should fail - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[1]) - tx.SignInput(chainID, 1, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { - t.Fatal("Expected error") - } -} -*/ - -func TestCreateAccountPermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission - genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission - genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateAccount, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //---------------------------------------------------------- - // SendTx to unknown account - - // A single input, having the permission, should succeed - tx := txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[6].Address, 5) - tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - - // Two inputs, both with send, one with create, one without, should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 10) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // Two inputs, both with send, one with create, one without, two ouputs (one known, one unknown) should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 4) - tx.AddOutput(user[4].Address, 6) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // Two inputs, both with send, both with create, should pass - acc := blockCache.GetAccount(user[1].Address) - acc.Permissions.Base.Set(ptypes.CreateAccount, true) - blockCache.UpdateAccount(acc) - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 10) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Unexpected error", err) - } - - // Two inputs, both with send, both with create, two outputs (one known, one unknown) should pass - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 7) - tx.AddOutput(user[4].Address, 3) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Unexpected error", err) - } - - //---------------------------------------------------------- - // CALL to unknown account - - acc = blockCache.GetAccount(user[0].Address) - acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(acc) - - // call to contract that calls unknown account - without create_account perm - // create contract that calls the simple contract - contractCode := callContractCode(user[9].Address) - caller1ContractAddr := NewContractAddress(user[4].Address, 101) - caller1Acc := &acm.Account{ - Address: caller1ContractAddr, - Balance: 0, - Code: contractCode, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - blockCache.UpdateAccount(caller1Acc) - - // A single input, having the permission, but the contract doesn't have permission - txCall, _ := txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - txCall.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, blockCache, txCall, txs.EventStringAccCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } - - // NOTE: for a contract to be able to CreateAccount, it must be able to call - // NOTE: for a user to be able to CreateAccount, it must be able to send! - caller1Acc.Permissions.Base.Set(ptypes.CreateAccount, true) - caller1Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - // A single input, having the permission, but the contract doesn't have permission - txCall, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - txCall.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, txCall, txs.EventStringAccCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } - -} - -// holla at my boy -var DougAddress = append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte("THISISDOUG")...) - -func TestSNativeCALL(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission - genDoc.Accounts[3].Permissions.Base.Set(ptypes.Bond, true) // some arbitrary permission to play with - genDoc.Accounts[3].Permissions.AddRole("bumble") - genDoc.Accounts[3].Permissions.AddRole("bee") - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //---------------------------------------------------------- - // Test CALL to SNative contracts - - // make the main contract once - doug := &acm.Account{ - Address: DougAddress, - Balance: 0, - Code: nil, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - doug.Permissions.Base.Set(ptypes.Call, true) - //doug.Permissions.Base.Set(ptypes.HasBase, true) - blockCache.UpdateAccount(doug) - - fmt.Println("\n#### HasBase") - // HasBase - snativeAddress, pF, data := snativePermTestInputCALL("hasBase", user[3], ptypes.Bond, false) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - // return value should be true or false as a 32 byte array... - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### SetBase") - // SetBase - snativeAddress, pF, data = snativePermTestInputCALL("setBase", user[3], ptypes.Bond, false) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.Bond, false) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - // return value should be true or false as a 32 byte array... - if !IsZeros(ret) { - return fmt.Errorf("Expected 0. Got %X", ret) - } - return nil - }) - snativeAddress, pF, data = snativePermTestInputCALL("setBase", user[3], ptypes.CreateContract, true) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - // return value should be true or false as a 32 byte array... - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### UnsetBase") - // UnsetBase - snativeAddress, pF, data = snativePermTestInputCALL("unsetBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret) { - return fmt.Errorf("Expected 0. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### SetGlobal") - // SetGlobalPerm - snativeAddress, pF, data = snativePermTestInputCALL("setGlobal", user[3], ptypes.CreateContract, true) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - // return value should be true or false as a 32 byte array... - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### HasRole") - // HasRole - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "bumble") - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### AddRole") - // AddRole - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret) { - return fmt.Errorf("Expected 0. Got %X", ret) - } - return nil - }) - snativeAddress, pF, data = snativeRoleTestInputCALL("addRole", user[3], "chuck") - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### RmRole") - // RmRole - snativeAddress, pF, data = snativeRoleTestInputCALL("removeRole", user[3], "chuck") - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret) { - return fmt.Errorf("Expected 0. Got %X", ret) - } - return nil - }) -} - -func TestSNativeTx(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission - genDoc.Accounts[3].Permissions.Base.Set(ptypes.Bond, true) // some arbitrary permission to play with - genDoc.Accounts[3].Permissions.AddRole("bumble") - genDoc.Accounts[3].Permissions.AddRole("bee") - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //---------------------------------------------------------- - // Test SNativeTx - - fmt.Println("\n#### SetBase") - // SetBase - snativeArgs := snativePermTestInputTx("setBase", user[3], ptypes.Bond, false) - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) - acc := blockCache.GetAccount(user[3].Address) - if v, _ := acc.Permissions.Base.Get(ptypes.Bond); v { - t.Fatal("expected permission to be set false") - } - snativeArgs = snativePermTestInputTx("setBase", user[3], ptypes.CreateContract, true) - testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { - t.Fatal("expected permission to be set true") - } - - fmt.Println("\n#### UnsetBase") - // UnsetBase - snativeArgs = snativePermTestInputTx("unsetBase", user[3], ptypes.CreateContract, false) - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.UnsetBase, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); v { - t.Fatal("expected permission to be set false") - } - - fmt.Println("\n#### SetGlobal") - // SetGlobalPerm - snativeArgs = snativePermTestInputTx("setGlobal", user[3], ptypes.CreateContract, true) - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.SetGlobal, snativeArgs) - acc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress) - if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { - t.Fatal("expected permission to be set true") - } - - fmt.Println("\n#### AddRole") - // AddRole - snativeArgs = snativeRoleTestInputTx("addRole", user[3], "chuck") - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.AddRole, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v := acc.Permissions.HasRole("chuck"); !v { - t.Fatal("expected role to be added") - } - - fmt.Println("\n#### RmRole") - // RmRole - snativeArgs = snativeRoleTestInputTx("removeRole", user[3], "chuck") - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.RmRole, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v := acc.Permissions.HasRole("chuck"); v { - t.Fatal("expected role to be removed") - } -} - -//------------------------------------------------------------------------------------- -// helpers - -var ExceptionTimeOut = "timed out waiting for event" - -// run ExecTx and wait for the Call event on given addr -// returns the msg data and an error/exception -func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx txs.Tx, eventid string) (interface{}, string) { - evsw := events.NewEventSwitch() - evsw.Start() - ch := make(chan interface{}) - evsw.AddListenerForEvent("test", eventid, func(msg events.EventData) { - ch <- msg - }) - evc := events.NewEventCache(evsw) - go func() { - if err := ExecTx(blockCache, tx, true, evc, logger); err != nil { - ch <- err.Error() - } - evc.Flush() - }() - ticker := time.NewTicker(5 * time.Second) - var msg interface{} - select { - case msg = <-ch: - case <-ticker.C: - return nil, ExceptionTimeOut - } - - switch ev := msg.(type) { - case txs.EventDataTx: - return ev, ev.Exception - case txs.EventDataCall: - return ev, ev.Exception - case string: - return nil, ev - default: - return ev, "" - } -} - -// give a contract perms for an snative, call it, it calls the snative, but shouldn't have permission -func testSNativeCALLExpectFail(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte) { - testSNativeCALL(t, false, blockCache, doug, 0, snativeAddress, data, nil) -} - -// give a contract perms for an snative, call it, it calls the snative, ensure the check funciton (f) succeeds -func testSNativeCALLExpectPass(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativePerm ptypes.PermFlag, snativeAddress, data []byte, f func([]byte) error) { - testSNativeCALL(t, true, blockCache, doug, snativePerm, snativeAddress, data, f) -} - -func testSNativeCALL(t *testing.T, expectPass bool, blockCache *BlockCache, doug *acm.Account, snativePerm ptypes.PermFlag, snativeAddress, data []byte, f func([]byte) error) { - if expectPass { - doug.Permissions.Base.Set(snativePerm, true) - } - var addr []byte - contractCode := callContractCode(snativeAddress) - doug.Code = contractCode - blockCache.UpdateAccount(doug) - addr = doug.Address - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100) - tx.Sign(chainID, user[0]) - fmt.Println("subscribing to", txs.EventStringAccCall(snativeAddress)) - ev, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(snativeAddress)) - if exception == ExceptionTimeOut { - t.Fatal("Timed out waiting for event") - } - if expectPass { - if exception != "" { - t.Fatal("Unexpected exception", exception) - } - evv := ev.(txs.EventDataCall) - ret := evv.Return - if err := f(ret); err != nil { - t.Fatal(err) - } - } else { - if exception == "" { - t.Fatal("Expected exception") - } - } -} - -func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.PermArgs) { - testSNativeTx(t, false, blockCache, 0, snativeArgs) -} - -func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) { - testSNativeTx(t, true, blockCache, perm, snativeArgs) -} - -func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) { - if expectPass { - acc := blockCache.GetAccount(user[0].Address) - acc.Permissions.Base.Set(perm, true) - blockCache.UpdateAccount(acc) - } - tx, _ := txs.NewPermissionsTx(blockCache, user[0].PubKey, snativeArgs) - tx.Sign(chainID, user[0]) - err := ExecTx(blockCache, tx, true, nil, logger) - if expectPass { - if err != nil { - t.Fatal("Unexpected exception", err) - } - } else { - if err == nil { - t.Fatal("Expected exception") - } - } -} - -func boolToWord256(v bool) Word256 { - var vint byte - if v { - vint = 0x1 - } else { - vint = 0x0 - } - return LeftPadWord256([]byte{vint}) -} - -func permNameToFuncID(name string) []byte { - function, err := permissionsContract.FunctionByName(name) - if err != nil { - panic("didn't find snative function signature!") - } - id := function.ID() - return id[:] -} - -func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, pF ptypes.PermFlag, data []byte) { - addr = permissionsContract.AddressBytes() - switch name { - case "hasBase", "unsetBase": - data = LeftPadBytes(user.Address, 32) - data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) - case "setBase": - data = LeftPadBytes(user.Address, 32) - data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) - data = append(data, boolToWord256(val).Bytes()...) - case "setGlobal": - data = Uint64ToWord256(uint64(perm)).Bytes() - data = append(data, boolToWord256(val).Bytes()...) - } - data = append(permNameToFuncID(name), data...) - var err error - if pF, err = ptypes.PermStringToFlag(name); err != nil { - panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) - } - return -} - -func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.PermArgs) { - switch name { - case "hasBase": - snativeArgs = &ptypes.HasBaseArgs{user.Address, perm} - case "unsetBase": - snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm} - case "setBase": - snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val} - case "setGlobal": - snativeArgs = &ptypes.SetGlobalArgs{perm, val} - } - return -} - -func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) (addr []byte, pF ptypes.PermFlag, data []byte) { - addr = permissionsContract.AddressBytes() - data = LeftPadBytes(user.Address, 32) - data = append(data, RightPadBytes([]byte(role), 32)...) - data = append(permNameToFuncID(name), data...) - - var err error - if pF, err = ptypes.PermStringToFlag(name); err != nil { - panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) - } - return -} - -func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.PermArgs) { - switch name { - case "hasRole": - snativeArgs = &ptypes.HasRoleArgs{user.Address, role} - case "addRole": - snativeArgs = &ptypes.AddRoleArgs{user.Address, role} - case "removeRole": - snativeArgs = &ptypes.RmRoleArgs{user.Address, role} - } - return -} - -// convenience function for contract that calls a given address -func callContractCode(contractAddr []byte) []byte { - // calldatacopy into mem and use as input to call - memOff, inputOff := byte(0x0), byte(0x0) - value := byte(0x1) - inOff := byte(0x0) - retOff, retSize := byte(0x0), byte(0x20) - - // this is the code we want to run (call a contract and return) - return Bytecode(CALLDATASIZE, PUSH1, inputOff, PUSH1, memOff, - CALLDATACOPY, PUSH1, retSize, PUSH1, retOff, CALLDATASIZE, PUSH1, inOff, - PUSH1, value, PUSH20, contractAddr, - // Zeno loves us - call with half of the available gas each time we CALL - PUSH1, 2, GAS, DIV, CALL, - PUSH1, 32, PUSH1, 0, RETURN) -} - -// convenience function for contract that is a factory for the code that comes as call data -func createContractCode() []byte { - // TODO: gas ... - - // calldatacopy the calldatasize - memOff, inputOff := byte(0x0), byte(0x0) - contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} - - // create - value := byte(0x1) - contractCode = append(contractCode, []byte{0x60, value, 0x36, 0x60, memOff, 0xf0}...) - return contractCode -} - -// wrap a contract in create code -func wrapContractForCreate(contractCode []byte) []byte { - // 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{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(lenCode), 0x60, 0x0, 0xf3}...) - // return init code, contract code, expected return - return code -} diff --git a/manager/burrow-mint/state/tx_cache.go b/manager/burrow-mint/state/tx_cache.go deleted file mode 100644 index d892f46e520dd04ffbdc362747124bcb8db9cac1..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/tx_cache.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - "fmt" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/sanity" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - ptypes "github.com/hyperledger/burrow/permission/types" // for GlobalPermissionAddress ... - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-crypto" -) - -type TxCache struct { - backend *BlockCache - accounts map[Word256]vmAccountInfo - storages map[Tuple256]Word256 -} - -var _ vm.AppState = &TxCache{} - -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 { - sanity.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 { - sanity.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 - sanity.PanicSanity(fmt.Sprintf("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 { - sanity.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 txs.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 crypto.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 crypto.PubKey - StorageRoot []byte -} - -func (accOther vmAccountOther) unpack() (crypto.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 -} diff --git a/manager/burrow-mint/state/tx_cache_test.go b/manager/burrow-mint/state/tx_cache_test.go deleted file mode 100644 index 4b30fa4a866b18475d467d2be8b3555bbba32796..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/tx_cache_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - "bytes" - "testing" - - "github.com/tendermint/go-wire" -) - -func TestStateToFromVMAccount(t *testing.T) { - acmAcc1, _ := 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) - } - -} diff --git a/manager/burrow-mint/transactor.go b/manager/burrow-mint/transactor.go deleted file mode 100644 index b4f16d891dbeb2cf584cb91bcd2d89066d55fe43..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/transactor.go +++ /dev/null @@ -1,438 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package burrowmint - -import ( - "bytes" - "encoding/hex" - "fmt" - "sync" - "time" - - "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/manager/burrow-mint/state" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-crypto" - tEvents "github.com/tendermint/go-events" -) - -// Transactor is part of the pipe for BurrowMint and provides the implementation -// for the pipe to call into the BurrowMint application -type transactor struct { - chainID string - eventSwitch tEvents.Fireable - burrowMint *BurrowMint - eventEmitter event.EventEmitter - txMtx *sync.Mutex - txBroadcaster func(tx txs.Tx) error -} - -func newTransactor(chainID string, eventSwitch tEvents.Fireable, - burrowMint *BurrowMint, eventEmitter event.EventEmitter, - txBroadcaster func(tx txs.Tx) error) *transactor { - return &transactor{ - chainID, - eventSwitch, - burrowMint, - eventEmitter, - &sync.Mutex{}, - txBroadcaster, - } -} - -// Run a contract's code on an isolated and unpersisted state -// Cannot be used to create new contracts -// NOTE: this function is used from 1337 and has sibling on 46657 -// in pipe.go -// TODO: [ben] resolve incompatibilities in byte representation for 0.12.0 release -func (this *transactor) Call(fromAddress, toAddress, data []byte) ( - *core_types.Call, error) { - - st := this.burrowMint.GetState() - cache := state.NewBlockCache(st) // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - outAcc := cache.GetAccount(toAddress) - if outAcc == nil { - return nil, fmt.Errorf("Account %X does not exist", toAddress) - } - if fromAddress == nil { - fromAddress = []byte{} - } - callee := toVMAccount(outAcc) - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - vmach.SetFireable(this.eventSwitch) - gas := gasLimit - ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - // here return bytes are hex encoded; on the sibling function - // they are not - return &core_types.Call{Return: hex.EncodeToString(ret), GasUsed: gasUsed}, nil -} - -// Run the given code on an isolated and unpersisted state -// Cannot be used to create new contracts. -func (this *transactor) CallCode(fromAddress, code, data []byte) ( - *core_types.Call, error) { - if fromAddress == nil { - fromAddress = []byte{} - } - cache := this.burrowMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - callee := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - st := this.burrowMint.GetState() // for block height, time - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - gas := gasLimit - ret, err := vmach.Call(caller, callee, code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - // here return bytes are hex encoded; on the sibling function - // they are not - return &core_types.Call{Return: hex.EncodeToString(ret), GasUsed: gasUsed}, nil -} - -// Broadcast a transaction. -func (this *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { - err := this.txBroadcaster(tx) - - if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) - } - - txHash := txs.TxHash(this.chainID, tx) - var createsContract uint8 - var contractAddr []byte - // check if creates new contract - if callTx, ok := tx.(*txs.CallTx); ok { - if len(callTx.Address) == 0 { - createsContract = 1 - contractAddr = state.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence) - } - } - return &txs.Receipt{txHash, createsContract, contractAddr}, nil -} - -// Orders calls to BroadcastTx using lock (waits for response from core before releasing) -func (this *transactor) Transact(privKey, address, data []byte, gasLimit, - fee int64) (*txs.Receipt, error) { - var addr []byte - if len(address) == 0 { - addr = nil - } else if len(address) != 20 { - return nil, fmt.Errorf("Address is not of the right length: %d\n", len(address)) - } else { - addr = address - } - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) - } - this.txMtx.Lock() - defer this.txMtx.Unlock() - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - cache := this.burrowMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - acc := cache.GetAccount(pa.Address) - var sequence int - if acc == nil { - sequence = 1 - } else { - sequence = acc.Sequence + 1 - } - // TODO: [Silas] we should consider revising this method and removing fee, or - // possibly adding an amount parameter. It is non-sensical to just be able to - // set the fee. Our support of fees in general is questionable since at the - // moment all we do is deduct the fee effectively leaking token. It is possible - // someone may be using the sending of native token to payable functions but - // they can be served by broadcasting a token. - - // We hard-code the amount to be equal to the fee which means the CallTx we - // generate transfers 0 value, which is the most sensible default since in - // recent solidity compilers the EVM generated will throw an error if value - // is transferred to a non-payable function. - txInput := &txs.TxInput{ - Address: pa.Address, - Amount: fee, - Sequence: sequence, - PubKey: pa.PubKey, - } - tx := &txs.CallTx{ - Input: txInput, - Address: addr, - GasLimit: gasLimit, - Fee: fee, - Data: data, - } - - // Got ourselves a tx. - txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) - if errS != nil { - return nil, errS - } - return this.BroadcastTx(txS) -} - -func (this *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*txs.EventDataCall, error) { - rec, tErr := this.Transact(privKey, address, data, gasLimit, fee) - if tErr != nil { - return nil, tErr - } - var addr []byte - if rec.CreatesContract == 1 { - addr = rec.ContractAddr - } else { - addr = address - } - // We want non-blocking on the first event received (but buffer the value), - // after which we want to block (and then discard the value - see below) - wc := make(chan *txs.EventDataCall, 1) - subId := fmt.Sprintf("%X", rec.TxHash) - this.eventEmitter.Subscribe(subId, txs.EventStringAccCall(addr), - func(evt txs.EventData) { - eventDataCall := evt.(txs.EventDataCall) - if bytes.Equal(eventDataCall.TxID, rec.TxHash) { - // Beware the contract of go-events subscribe is that we must not be - // blocking in an event callback when we try to unsubscribe! - // We work around this by using a non-blocking send. - select { - // This is a non-blocking send, but since we are using a buffered - // channel of size 1 we will always grab our first event even if we - // haven't read from the channel at the time we receive the first event. - case wc <- &eventDataCall: - default: - } - } - }) - - timer := time.NewTimer(300 * time.Second) - toChan := timer.C - - var ret *txs.EventDataCall - var rErr error - - select { - case <-toChan: - rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) - case e := <-wc: - timer.Stop() - if e.Exception != "" { - rErr = fmt.Errorf("Error when transacting: " + e.Exception) - } else { - ret = e - } - } - this.eventEmitter.Unsubscribe(subId) - return ret, rErr -} - -func (this *transactor) Send(privKey, toAddress []byte, - amount int64) (*txs.Receipt, error) { - var toAddr []byte - if len(toAddress) == 0 { - toAddr = nil - } else if len(toAddress) != 20 { - return nil, fmt.Errorf("To-address is not of the right length: %d\n", - len(toAddress)) - } else { - toAddr = toAddress - } - - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", - len(privKey)) - } - - pk := &[64]byte{} - copy(pk[:], privKey) - this.txMtx.Lock() - defer this.txMtx.Unlock() - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - cache := this.burrowMint.GetState() - acc := cache.GetAccount(pa.Address) - var sequence int - if acc == nil { - sequence = 1 - } else { - sequence = acc.Sequence + 1 - } - - tx := txs.NewSendTx() - - txInput := &txs.TxInput{ - Address: pa.Address, - Amount: amount, - Sequence: sequence, - PubKey: pa.PubKey, - } - - tx.Inputs = append(tx.Inputs, txInput) - - txOutput := &txs.TxOutput{toAddr, amount} - - tx.Outputs = append(tx.Outputs, txOutput) - - // Got ourselves a tx. - txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) - if errS != nil { - return nil, errS - } - return this.BroadcastTx(txS) -} - -func (this *transactor) SendAndHold(privKey, toAddress []byte, - amount int64) (*txs.Receipt, error) { - rec, tErr := this.Send(privKey, toAddress, amount) - if tErr != nil { - return nil, tErr - } - - wc := make(chan *txs.SendTx) - subId := fmt.Sprintf("%X", rec.TxHash) - - this.eventEmitter.Subscribe(subId, txs.EventStringAccOutput(toAddress), - func(evt txs.EventData) { - event := evt.(txs.EventDataTx) - tx := event.Tx.(*txs.SendTx) - wc <- tx - }) - - timer := time.NewTimer(300 * time.Second) - toChan := timer.C - - var rErr error - - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - - select { - case <-toChan: - rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) - case e := <-wc: - if bytes.Equal(e.Inputs[0].Address, pa.Address) && e.Inputs[0].Amount == amount { - timer.Stop() - this.eventEmitter.Unsubscribe(subId) - return rec, rErr - } - } - return nil, rErr -} - -func (this *transactor) TransactNameReg(privKey []byte, name, data string, - amount, fee int64) (*txs.Receipt, error) { - - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) - } - this.txMtx.Lock() - defer this.txMtx.Unlock() - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - cache := this.burrowMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - acc := cache.GetAccount(pa.Address) - var sequence int - if acc == nil { - sequence = 1 - } else { - sequence = acc.Sequence + 1 - } - tx := txs.NewNameTxWithNonce(pa.PubKey, name, data, amount, fee, sequence) - // Got ourselves a tx. - txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) - if errS != nil { - return nil, errS - } - return this.BroadcastTx(txS) -} - -// Sign a transaction -func (this *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, 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 *txs.NameTx: - nameTx := tx.(*txs.NameTx) - nameTx.Input.PubKey = privAccounts[0].PubKey - nameTx.Input.Signature = privAccounts[0].Sign(this.chainID, nameTx) - case *txs.SendTx: - sendTx := tx.(*txs.SendTx) - for i, input := range sendTx.Inputs { - input.PubKey = privAccounts[i].PubKey - input.Signature = privAccounts[i].Sign(this.chainID, sendTx) - } - case *txs.CallTx: - callTx := tx.(*txs.CallTx) - callTx.Input.PubKey = privAccounts[0].PubKey - callTx.Input.Signature = privAccounts[0].Sign(this.chainID, callTx) - case *txs.BondTx: - bondTx := tx.(*txs.BondTx) - // the first privaccount corresponds to the BondTx pub key. - // the rest to the inputs - bondTx.Signature = privAccounts[0].Sign(this.chainID, bondTx).(crypto.SignatureEd25519) - for i, input := range bondTx.Inputs { - input.PubKey = privAccounts[i+1].PubKey - input.Signature = privAccounts[i+1].Sign(this.chainID, bondTx) - } - case *txs.UnbondTx: - unbondTx := tx.(*txs.UnbondTx) - unbondTx.Signature = privAccounts[0].Sign(this.chainID, unbondTx).(crypto.SignatureEd25519) - case *txs.RebondTx: - rebondTx := tx.(*txs.RebondTx) - rebondTx.Signature = privAccounts[0].Sign(this.chainID, rebondTx).(crypto.SignatureEd25519) - default: - return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) - } - return tx, nil -} - -// No idea what this does. -func toVMAccount(acc *account.Account) *vm.Account { - return &vm.Account{ - Address: word256.LeftPadWord256(acc.Address), - Balance: acc.Balance, - Code: acc.Code, - Nonce: int64(acc.Sequence), - Other: acc.PubKey, - } -} diff --git a/manager/burrow-mint/version.go b/manager/burrow-mint/version.go deleted file mode 100644 index 888de79cbca1c81d032954b4c9fc78bf34237bdc..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/version.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package burrowmint - -import ( - "fmt" - - version "github.com/hyperledger/burrow/version" -) - -const ( - // Client identifier to advertise over the network - burrowMintClientIdentifier = "burrowmint" - // Major version component of the current release - burrowMintVersionMajor = 0 - // Minor version component of the current release - burrowMintVersionMinor = 17 - // Patch version component of the current release - burrowMintVersionPatch = 0 -) - -// Define the compatible consensus engines this application manager -// is compatible and has been tested with. -var compatibleConsensus = [...]string{ - "tendermint-0.9", -} - -func GetBurrowMintVersion() *version.VersionIdentifier { - return version.New(burrowMintClientIdentifier, burrowMintVersionMajor, - burrowMintVersionMinor, burrowMintVersionPatch) -} - -func AssertCompatibleConsensus(consensusMinorVersion string) error { - for _, supported := range compatibleConsensus { - if consensusMinorVersion == supported { - return nil - } - } - return fmt.Errorf("BurrowMint (%s) is not compatible with consensus engine %s", - GetBurrowMintVersion(), consensusMinorVersion) -} diff --git a/manager/manager.go b/manager/manager.go deleted file mode 100644 index 2724b12645714d68fd3cc0388a5367fe54453e6e..0000000000000000000000000000000000000000 --- a/manager/manager.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package manager - -import ( - "fmt" - - events "github.com/tendermint/go-events" - - config "github.com/hyperledger/burrow/config" - definitions "github.com/hyperledger/burrow/definitions" - burrowmint "github.com/hyperledger/burrow/manager/burrow-mint" - - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" -) - -// NewApplicationPipe returns an initialised Pipe interface -// based on the loaded module configuration file. -// NOTE: [ben] Currently we only have a single `generic` definition -// of an application. It is feasible this will be insufficient to support -// different types of applications later down the line. -func NewApplicationPipe(moduleConfig *config.ModuleConfig, - evsw events.EventSwitch, - logger logging_types.InfoTraceLogger) (definitions.Pipe, error) { - switch moduleConfig.Name { - case "burrowmint": - logging.InfoMsg(logger, "Loading BurrowMint") - return burrowmint.NewBurrowMintPipe(moduleConfig, evsw, logger) - } - return nil, fmt.Errorf("Failed to return Pipe for %s", moduleConfig.Name) -} diff --git a/manager/types/application.go b/manager/types/application.go deleted file mode 100644 index 2025742bbd5cee5b62b95f348478d382f0acab31..0000000000000000000000000000000000000000 --- a/manager/types/application.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - // TODO: [ben] this is currently only used for abci result type; but should - // be removed as abci dependencies shouldn't feature in the application - // manager - consensus_types "github.com/hyperledger/burrow/consensus/types" - abci "github.com/tendermint/abci/types" -) - -// NOTE: [ben] this interface is likely to be changed. Currently it is taken -// from the tendermint socket protocol application interface; - -// Application interface applies transactions to the state. -type Application interface { - - // Info returns application information as a string - // NOTE: [ben] likely to move - Info() (info abci.ResponseInfo) - - // Set application option (e.g. mode=mempool, mode=consensus) - // NOTE: [ben] taken from tendermint, but it is unclear what the use is, - // specifically, when will tendermint call this over abci ? - SetOption(key string, value string) (log string) - - // Append transaction applies a transaction to the state regardless of - // whether the transaction is valid or not. - // Currently AppendTx is taken from abci, and returns a result. - // This will be altered, as AppendTransaction needs to more strongly reflect - // the theoretical logic: - // Append(StateN, Transaction) = StateN+1 - // here invalid transactions are allowed, but should act as the identity on - // the state: - // Append(StateN, InvalidTransaction) = StateN - // TODO: implementation notes: - // 1. at this point the transaction should already be strongly typed - // 2. - DeliverTx(tx []byte) abci.Result - - // Check Transaction validates a transaction before being allowed into the - // consensus' engine memory pool. This is the original defintion and - // intention as taken from abci, but should be remapped to the more - // general concept of basic, cheap verification; - // Check Transaction does not alter the state, but does require an immutable - // copy of the state. In particular there is no consensus on ordering yet. - // TODO: implementation notes: - // 1. at this point the transaction should already be strongly typed - // 2. - CheckTx(tx []byte) abci.Result - - // Commit returns the root hash of the current application state - // NOTE: [ben] Because the concept of the block has been erased here - // the commit root hash is a fully implict stateful function; - // the opposit the principle of explicit stateless functions. - // This will be amended when we introduce the concept of (streaming) - // blocks in the pipe. - Commit() abci.Result - - // Query for state. This query request is not passed over the p2p network - // and is called from Tenderpmint rpc directly up to the application. - // NOTE: [ben] burrow will give preference to queries from the local client - // directly over the burrow rpc. - // We will support this for Tendermint compatibility. - Query(reqQuery abci.RequestQuery) abci.ResponseQuery - - // Tendermint acbi_types.Application extends our base definition of an - // Application with a parenthetical (begin/end) streaming block interface - - // Initialise the blockchain - // When Tendermint initialises the genesis validators from tendermint core - // are passed in as validators - InitChain(validators []*abci.Validator) - - // Signals the beginning of communicating a block (all transactions have been - // closed into the block already - BeginBlock(hash []byte, header *abci.Header) - - // Signals the end of a blockchain - // ResponseEndBlock wraps a slice of Validators with the Diff field. A Validator - // is a public key and a voting power. Returning a Validator within this slice - // asks Tendermint to set that validator's voting power to the Power provided. - // Note: although the field is named 'Diff' the intention is that it declares - // the what the new voting power should be (for validators specified, - // those omitted are left alone) it is not an relative increment to - // be added (or subtracted) from voting power. - EndBlock(height uint64) abci.ResponseEndBlock - - // Is this the passed ConsensusEngine compatible with this manager - CompatibleConsensus(consensusEngine consensus_types.ConsensusEngine) bool -} diff --git a/permission/constants.go b/permission/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..c795fda0f50cd608b65e3a3b22d7ba0975cebd80 --- /dev/null +++ b/permission/constants.go @@ -0,0 +1,10 @@ +package permission + +import ( + "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" +) + +var ( + GlobalPermissionsAddress = account.Address(binary.Zero160) +) diff --git a/consensus/types/peer.go b/permission/errors.go similarity index 78% rename from consensus/types/peer.go rename to permission/errors.go index c123cf5c022e43d7a0224c55c14623913a4f12c4..795b621cac31d5a52159c6a16bafa24ce4106e68 100644 --- a/consensus/types/peer.go +++ b/permission/errors.go @@ -12,11 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package types +package permission -import "github.com/tendermint/go-p2p" - -type Peer struct { - NodeInfo *p2p.NodeInfo `json:"node_info"` - IsOutbound bool `json:"is_outbound"` -} +//------------------------------------------------------------------------------------------------ +// Some errors diff --git a/permission/permissions.go b/permission/permissions.go new file mode 100644 index 0000000000000000000000000000000000000000..599be2bf1a871553b448736e428ca3709d52f76c --- /dev/null +++ b/permission/permissions.go @@ -0,0 +1,190 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package permission + +import ( + "fmt" + "strings" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission/types" +) + +//------------------------------------------------------------------------------------------------ + +// Base permission references are like unix (the index is already bit shifted) +const ( + // chain permissions + Root types.PermFlag = 1 << iota // 1 + Send // 2 + Call // 4 + CreateContract // 8 + CreateAccount // 16 + Bond // 32 + Name // 64 + + // moderator permissions + HasBase + SetBase + UnsetBase + SetGlobal + HasRole + AddRole + RemoveRole + + NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64 + + TopPermFlag types.PermFlag = 1 << (NumPermissions - 1) + AllPermFlags types.PermFlag = TopPermFlag | (TopPermFlag - 1) + DefaultPermFlags types.PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole + + RootString string = "root" + SendString = "send" + CallString = "call" + CreateContractString = "createContract" + CreateAccountString = "createAccount" + BondString = "bond" + NameString = "name" + + // moderator permissions + HasBaseString = "hasBase" + SetBaseString = "setBase" + UnsetBaseString = "unsetBase" + SetGlobalString = "setGlobal" + HasRoleString = "hasRole" + AddRoleString = "addRole" + RemoveRoleString = "removeRole" + UnknownString = "#-UNKNOWN-#" + + AllString = "all" +) + +var ( + ZeroBasePermissions = types.BasePermissions{0, 0} + ZeroAccountPermissions = types.AccountPermissions{ + Base: ZeroBasePermissions, + } + DefaultAccountPermissions = types.AccountPermissions{ + Base: types.BasePermissions{ + Perms: DefaultPermFlags, + SetBit: AllPermFlags, + }, + Roles: []string{}, + } + AllAccountPermissions = types.AccountPermissions{ + Base: types.BasePermissions{ + Perms: AllPermFlags, + SetBit: AllPermFlags, + }, + Roles: []string{}, + } +) + +//--------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------- +// string utilities + +// PermFlagToString assumes the permFlag is valid, else returns "#-UNKNOWN-#" +func PermFlagToString(pf types.PermFlag) (perm string) { + switch pf { + case AllPermFlags: + perm = AllString + case Root: + perm = RootString + case Send: + perm = SendString + case Call: + perm = CallString + case CreateContract: + perm = CreateContractString + case CreateAccount: + perm = CreateAccountString + case Bond: + perm = BondString + case Name: + perm = NameString + case HasBase: + perm = HasBaseString + case SetBase: + perm = SetBaseString + case UnsetBase: + perm = UnsetBaseString + case SetGlobal: + perm = SetGlobalString + case HasRole: + perm = HasRoleString + case AddRole: + perm = AddRoleString + case RemoveRole: + perm = RemoveRoleString + default: + perm = UnknownString + } + return +} + +// PermStringToFlag maps camel- and snake case strings to the +// the corresponding permission flag. +func PermStringToFlag(perm string) (pf types.PermFlag, err error) { + switch strings.ToLower(perm) { + case AllString: + pf = AllPermFlags + case RootString: + pf = Root + case SendString: + pf = Send + case CallString: + pf = Call + case CreateContractString, "createcontract", "create_contract": + pf = CreateContract + case CreateAccountString, "createaccount", "create_account": + pf = CreateAccount + case BondString: + pf = Bond + case NameString: + pf = Name + case HasBaseString, "hasbase", "has_base": + pf = HasBase + case SetBaseString, "setbase", "set_base": + pf = SetBase + case UnsetBaseString, "unsetbase", "unset_base": + pf = UnsetBase + case SetGlobalString, "setglobal", "set_global": + pf = SetGlobal + case HasRoleString, "hasrole", "has_role": + pf = HasRole + case AddRoleString, "addrole", "add_role": + pf = AddRole + case RemoveRoleString, "removerole", "rmrole", "rm_role": + pf = RemoveRole + default: + err = fmt.Errorf("unknown permission %s", perm) + } + return +} + +func GlobalPermissionsAccount(state acm.Getter) acm.Account { + acc, err := state.GetAccount(GlobalPermissionsAddress) + if err != nil { + panic("Could not get global permission account, but this must exist") + } + return acc +} + +// Get global permissions from the account at GlobalPermissionsAddress +func GlobalAccountPermissions(state acm.Getter) types.AccountPermissions { + return GlobalPermissionsAccount(state).Permissions() +} diff --git a/permission/snatives.go b/permission/snatives.go new file mode 100644 index 0000000000000000000000000000000000000000..53fa0eeaff466ab238529d4c32b3647ee43b48cd --- /dev/null +++ b/permission/snatives.go @@ -0,0 +1,88 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package permission + +import ( + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission/types" +) + +//--------------------------------------------------------------------------------------------------- +// PermissionsTx.PermArgs interface and argument encoding + +type PermArgs struct { + PermFlag types.PermFlag + Address acm.Address `json:",omitempty"` + Permission types.PermFlag `json:",omitempty"` + Role string `json:",omitempty"` + Value bool `json:",omitempty"` +} + +func HasBaseArgs(address acm.Address, permission types.PermFlag) *PermArgs { + return &PermArgs{ + PermFlag: HasBase, + Address: address, + Permission: permission, + } +} + +func SetBaseArgs(address acm.Address, permission types.PermFlag, value bool) *PermArgs { + return &PermArgs{ + PermFlag: SetBase, + Address: address, + Permission: permission, + Value: value, + } +} + +func UnsetBaseArgs(address acm.Address, permission types.PermFlag) *PermArgs { + return &PermArgs{ + PermFlag: UnsetBase, + Address: address, + Permission: permission, + } +} + +func SetGlobalArgs(permission types.PermFlag, value bool) *PermArgs { + return &PermArgs{ + PermFlag: SetGlobal, + Permission: permission, + Value: value, + } +} + +func HasRoleArgs(address acm.Address, role string) *PermArgs { + return &PermArgs{ + PermFlag: HasRole, + Address: address, + Role: role, + } +} + +func AddRoleArgs(address acm.Address, role string) *PermArgs { + return &PermArgs{ + PermFlag: AddRole, + Address: address, + Role: role, + } +} + +func RemoveRoleArgs(address acm.Address, role string) *PermArgs { + return &PermArgs{ + PermFlag: RemoveRole, + Address: address, + Role: role, + } +} diff --git a/permission/types/account_permissions.go b/permission/types/account_permissions.go new file mode 100644 index 0000000000000000000000000000000000000000..91bee5f818dde74fd8f5a351ff84085da6ccbe17 --- /dev/null +++ b/permission/types/account_permissions.go @@ -0,0 +1,62 @@ +package types + +import "github.com/hyperledger/burrow/binary" + +type AccountPermissions struct { + Base BasePermissions + Roles []string +} + +// Returns true if the role is found +func (ap AccountPermissions) HasRole(role string) bool { + role = string(binary.RightPadBytes([]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(binary.RightPadBytes([]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(binary.RightPadBytes([]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 +} + +// Clone clones the account permissions +func (ap *AccountPermissions) Clone() AccountPermissions { + // clone base permissions + basePermissionsClone := ap.Base + // clone roles []string + rolesClone := make([]string, len(ap.Roles)) + // strings are immutable so copy suffices + copy(rolesClone, ap.Roles) + + return AccountPermissions{ + Base: basePermissionsClone, + Roles: rolesClone, + } +} diff --git a/permission/types/base_permissions.go b/permission/types/base_permissions.go new file mode 100644 index 0000000000000000000000000000000000000000..bc9d70397b76aac4e8de193e36ce3e86504c9c86 --- /dev/null +++ b/permission/types/base_permissions.go @@ -0,0 +1,86 @@ +package types + +import "fmt" + +// A particular permission +type PermFlag uint64 + +// 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) +} + +// Base chain permissions struct +type BasePermissions struct { + // bit array with "has"/"doesn't have" for each permission + Perms PermFlag + + // bit array with "set"/"not set" for each permission (not-set should fall back to global) + SetBit PermFlag +} + +// 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 +} + +// Returns the Perms PermFlag masked with SetBit bit field to give the resultant +// permissions enabled by this BasePermissions +func (p BasePermissions) ResultantPerms() PermFlag { + return p.Perms & p.SetBit +} + +func (p BasePermissions) String() string { + return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit) +} + +//--------------------------------------------------------------------------------------------- diff --git a/permission/types/errors.go b/permission/types/errors.go deleted file mode 100644 index fe023ca8e4a2a4e0817a9a85961981b5f8dba745..0000000000000000000000000000000000000000 --- a/permission/types/errors.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -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) -} diff --git a/permission/types/permissions.go b/permission/types/permissions.go deleted file mode 100644 index c414a85dfb18ddeccbfef80553592a231d0f124c..0000000000000000000000000000000000000000 --- a/permission/types/permissions.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "fmt" - "strings" - - "github.com/hyperledger/burrow/word256" -) - -//------------------------------------------------------------------------------------------------ - -var ( - GlobalPermissionsAddress = word256.Zero256[:20] - GlobalPermissionsAddress256 = word256.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 -} - -// Returns the Perms PermFlag masked with SetBit bit field to give the resultant -// permissions enabled by this BasePermissions -func (p *BasePermissions) ResultantPerms() PermFlag { - return p.Perms & p.SetBit -} - -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(word256.RightPadBytes([]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(word256.RightPadBytes([]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(word256.RightPadBytes([]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 -} - -// Clone clones the account permissions -func (accountPermissions *AccountPermissions) Clone() AccountPermissions { - // clone base permissions - basePermissionsClone := accountPermissions.Base - // clone roles []string - rolesClone := make([]string, len(accountPermissions.Roles)) - // strings are immutable so copy suffices - copy(rolesClone, accountPermissions.Roles) - - return AccountPermissions{ - Base: basePermissionsClone, - Roles: rolesClone, - } -} - -//-------------------------------------------------------------------------------- -// 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 = "hasBase" - case SetBase: - perm = "setBase" - case UnsetBase: - perm = "unsetBase" - case SetGlobal: - perm = "setGlobal" - case HasRole: - perm = "hasRole" - case AddRole: - perm = "addRole" - case RmRole: - perm = "removeRole" - default: - perm = "#-UNKNOWN-#" - } - return -} - -// PermStringToFlag maps camel- and snake case strings to the -// the corresponding permission flag. -func PermStringToFlag(perm string) (pf PermFlag, err error) { - switch strings.ToLower(perm) { - case "root": - pf = Root - case "send": - pf = Send - case "call": - pf = Call - case "createcontract", "create_contract": - pf = CreateContract - case "createaccount", "create_account": - pf = CreateAccount - case "bond": - pf = Bond - case "name": - pf = Name - case "hasbase", "has_base": - pf = HasBase - case "setbase", "set_base": - pf = SetBase - case "unsetbase", "unset_base": - pf = UnsetBase - case "setglobal", "set_global": - pf = SetGlobal - case "hasrole", "has_role": - pf = HasRole - case "addrole", "add_role": - pf = AddRole - case "removerole", "rmrole", "rm_role": - pf = RmRole - default: - err = fmt.Errorf("Unknown permission %s", perm) - } - return -} diff --git a/permission/types/snatives.go b/permission/types/snatives.go deleted file mode 100644 index f7fe543affd37a0435192f10490db99aa89ffdfd..0000000000000000000000000000000000000000 --- a/permission/types/snatives.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -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) -) - -// TODO: [ben] this registration needs to be lifted up -// and centralised in core; here it pulls in go-wire dependency -// while it suffices to have the type bytes defined; -// --- -// 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 -} diff --git a/permission/types/util.go b/permission/util.go similarity index 75% rename from permission/types/util.go rename to permission/util.go index c7ceae90bd1f97e30793551cb04f73104df629eb..7942eb92d0947c390e328ee3aab153f1bde4d324 100644 --- a/permission/types/util.go +++ b/permission/util.go @@ -12,16 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package types +package permission + +import "github.com/hyperledger/burrow/permission/types" // ConvertMapStringIntToPermissions converts a map of string-bool pairs and a slice of // strings for the roles to an AccountPermissions type. If the value in the // permissions map is true for a particular permission string then the permission // will be set in the AccountsPermissions. For all unmentioned permissions the // ZeroBasePermissions is defaulted to. -func ConvertPermissionsMapAndRolesToAccountPermissions(permissions map[string]bool, roles []string) (*AccountPermissions, error) { +func ConvertPermissionsMapAndRolesToAccountPermissions(permissions map[string]bool, + roles []string) (*types.AccountPermissions, error) { var err error - accountPermissions := &AccountPermissions{} + accountPermissions := &types.AccountPermissions{} accountPermissions.Base, err = convertPermissionsMapStringIntToBasePermissions(permissions) if err != nil { return nil, err @@ -32,7 +35,7 @@ func ConvertPermissionsMapAndRolesToAccountPermissions(permissions map[string]bo // convertPermissionsMapStringIntToBasePermissions converts a map of string-bool // pairs to BasePermissions. -func convertPermissionsMapStringIntToBasePermissions(permissions map[string]bool) (BasePermissions, error) { +func convertPermissionsMapStringIntToBasePermissions(permissions map[string]bool) (types.BasePermissions, error) { // initialise basePermissions as ZeroBasePermissions basePermissions := ZeroBasePermissions @@ -47,3 +50,18 @@ func convertPermissionsMapStringIntToBasePermissions(permissions map[string]bool return basePermissions, nil } + +func BasePermissionsFromStringList(permissions []string) (types.BasePermissions, error) { + var permFlag types.PermFlag + for _, perm := range permissions { + flag, err := PermStringToFlag(perm) + if err != nil { + return ZeroBasePermissions, err + } + permFlag |= flag + } + return types.BasePermissions{ + Perms: permFlag, + SetBit: permFlag, + }, nil +} diff --git a/permission/util_test.go b/permission/util_test.go new file mode 100644 index 0000000000000000000000000000000000000000..54dad9541637d3e72817983af0adfb92575e4408 --- /dev/null +++ b/permission/util_test.go @@ -0,0 +1,16 @@ +package permission + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBasePermissionsFromStringList(t *testing.T) { + basePerms, err := BasePermissionsFromStringList([]string{HasRoleString, CreateContractString, SendString}) + require.NoError(t, err) + permFlag := HasRole | CreateContract | Send + assert.Equal(t, permFlag, basePerms.Perms) + assert.Equal(t, permFlag, basePerms.SetBit) +} diff --git a/rpc/config.go b/rpc/config.go new file mode 100644 index 0000000000000000000000000000000000000000..230c2987fe0262b9fb8d7c82b1d0e1414cf1c7ea --- /dev/null +++ b/rpc/config.go @@ -0,0 +1,25 @@ +package rpc + +type RPCConfig struct { + V0 *TMConfig `json:",omitempty" toml:",omitempty"` + TM *TMConfig `json:",omitempty" toml:",omitempty"` +} + +type TMConfig struct { + ListenAddress string +} + +type V0Config struct { +} + +func DefaultRPCConfig() *RPCConfig { + return &RPCConfig{ + TM: DefaultTMConfig(), + } +} + +func DefaultTMConfig() *TMConfig { + return &TMConfig{ + ListenAddress: ":46657", + } +} diff --git a/rpc/result.go b/rpc/result.go new file mode 100644 index 0000000000000000000000000000000000000000..cee5d57e1508584143062b3975f87cef6d3a5404 --- /dev/null +++ b/rpc/result.go @@ -0,0 +1,229 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rpc + +import ( + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/txs" + "github.com/tendermint/go-wire/data" + ctypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/p2p" + tm_types "github.com/tendermint/tendermint/types" +) + +type Result struct { + ResultInner `json:"unwrap"` +} + +type ResultInner interface { +} + +func (res Result) Unwrap() ResultInner { + return res.ResultInner +} + +func (br Result) MarshalJSON() ([]byte, error) { + return mapper.ToJSON(br.ResultInner) +} + +func (br *Result) UnmarshalJSON(data []byte) (err error) { + parsed, err := mapper.FromJSON(data) + if err == nil && parsed != nil { + br.ResultInner = parsed.(ResultInner) + } + return err +} + +var mapper = data.NewMapper(Result{}). + // Transact + RegisterImplementation(&ResultBroadcastTx{}, "result_broadcast_tx", biota()). + // Events + RegisterImplementation(&ResultSubscribe{}, "result_subscribe", biota()). + RegisterImplementation(&ResultUnsubscribe{}, "result_unsubscribe", biota()). + RegisterImplementation(&ResultEvent{}, "result_event", biota()). + // Status + RegisterImplementation(&ResultStatus{}, "result_status", biota()). + RegisterImplementation(&ResultNetInfo{}, "result_net_info", biota()). + // Accounts + RegisterImplementation(&ResultGetAccount{}, "result_get_account", biota()). + RegisterImplementation(&ResultListAccounts{}, "result_list_account", biota()). + RegisterImplementation(&ResultGetStorage{}, "result_get_storage", biota()). + RegisterImplementation(&ResultDumpStorage{}, "result_dump_storage", biota()). + // Simulated call + RegisterImplementation(&ResultCall{}, "result_call", biota()). + // Blockchain + RegisterImplementation(&ResultGenesis{}, "result_genesis", biota()). + RegisterImplementation(&ResultChainId{}, "result_chain_id", biota()). + RegisterImplementation(&ResultBlockchainInfo{}, "result_blockchain_info", biota()). + RegisterImplementation(&ResultGetBlock{}, "result_get_block", biota()). + // Consensus + RegisterImplementation(&ResultListUnconfirmedTxs{}, "result_list_unconfirmed_txs", biota()). + RegisterImplementation(&ResultListValidators{}, "result_list_validators", biota()). + RegisterImplementation(&ResultDumpConsensusState{}, "result_dump_consensus_state", biota()). + RegisterImplementation(&ResultPeers{}, "result_peers", biota()). + // Names + RegisterImplementation(&ResultGetName{}, "result_get_name", biota()). + RegisterImplementation(&ResultListNames{}, "result_list_names", biota()). + // Private keys and signing + RegisterImplementation(&ResultSignTx{}, "result_sign_tx", biota()). + RegisterImplementation(&ResultGeneratePrivateAccount{}, "result_generate_private_account", biota()) + +type ResultGetStorage struct { + Key []byte `json:"key"` + Value []byte `json:"value"` +} + +func (br Result) ResultGetStorage() *ResultGetStorage { + if res, ok := br.ResultInner.(*ResultGetStorage); ok { + return res + } + return nil +} + +type ResultCall struct { + *execution.Call `json:"unwrap"` +} + +type ResultListAccounts struct { + BlockHeight uint64 `json:"block_height"` + Accounts []*acm.ConcreteAccount `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 uint64 `json:"last_height"` + BlockMetas []*tm_types.BlockMeta `json:"block_metas"` +} + +type ResultGetBlock struct { + BlockMeta *tm_types.BlockMeta `json:"block_meta"` + Block *tm_types.Block `json:"block"` +} + +type ResultStatus struct { + NodeInfo *p2p.NodeInfo `json:"node_info"` + GenesisHash []byte `json:"genesis_hash"` + PubKey acm.PublicKey `json:"pub_key"` + LatestBlockHash []byte `json:"latest_block_hash"` + LatestBlockHeight uint64 `json:"latest_block_height"` + LatestBlockTime int64 `json:"latest_block_time"` // nano +} + +type ResultChainId struct { + ChainName string `json:"chain_name"` + ChainId string `json:"chain_id"` + GenesisHash []byte `json:"genesis_hash"` +} + +type ResultSubscribe struct { + Event string `json:"event"` + SubscriptionId string `json:"subscription_id"` +} + +type ResultUnsubscribe struct { + SubscriptionId string `json:"subscription_id"` +} + +type Peer struct { + NodeInfo *p2p.NodeInfo `json:"node_info"` + IsOutbound bool `json:"is_outbound"` +} + +type ResultNetInfo struct { + Listening bool `json:"listening"` + Listeners []string `json:"listeners"` + Peers []*Peer `json:"peers"` +} + +type ResultListValidators struct { + BlockHeight uint64 `json:"block_height"` + BondedValidators []*acm.ConcreteValidator `json:"bonded_validators"` + UnbondingValidators []*acm.ConcreteValidator `json:"unbonding_validators"` +} + +type ResultDumpConsensusState struct { + RoundState *ctypes.RoundState `json:"consensus_state"` + PeerRoundStates []*ctypes.PeerRoundState `json:"peer_round_states"` +} + +type ResultPeers struct { + Peers []*Peer `json:"peers"` +} + +type ResultListNames struct { + BlockHeight uint64 `json:"block_height"` + Names []*execution.NameRegEntry `json:"names"` +} + +type ResultGeneratePrivateAccount struct { + PrivAccount *acm.ConcretePrivateAccount `json:"priv_account"` +} + +type ResultGetAccount struct { + Account *acm.ConcreteAccount `json:"account"` +} + +type ResultBroadcastTx struct { + *txs.Receipt `json:"unwrap"` +} + +type ResultListUnconfirmedTxs struct { + N int `json:"n_txs"` + Txs []txs.Wrapper `json:"txs"` +} + +type ResultGetName struct { + Entry *execution.NameRegEntry `json:"entry"` +} + +type ResultGenesis struct { + Genesis genesis.GenesisDoc `json:"genesis"` +} + +type ResultSignTx struct { + Tx txs.Wrapper `json:"tx"` +} + +type ResultEvent struct { + Event string `json:"event"` + event.AnyEventData `json:"data"` +} + +func (re ResultEvent) Wrap() Result { + return Result{ + ResultInner: &re, + } +} + +// Type byte helper +var nextByte byte = 1 + +func biota() (b byte) { + b = nextByte + nextByte++ + return +} diff --git a/rpc/result_test.go b/rpc/result_test.go new file mode 100644 index 0000000000000000000000000000000000000000..983f7cf7258167bb13672e910ea32d954b2ed4df --- /dev/null +++ b/rpc/result_test.go @@ -0,0 +1,84 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rpc + +import ( + "testing" + + "fmt" + + "encoding/json" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/go-wire" +) + +func TestResultBroadcastTx(t *testing.T) { + // Make sure these are unpacked as expected + res := ResultBroadcastTx{ + Receipt: &txs.Receipt{ + ContractAddr: acm.Address{0, 2, 3}, + CreatesContract: true, + TxHash: []byte("foo"), + }, + } + + assert.Equal(t, `{"tx_hash":"666F6F","creates_contract":true,"contract_addr":"0002030000000000000000000000000000000000"}`, + string(wire.JSONBytes(res))) + + res2 := new(ResultBroadcastTx) + wire.ReadBinaryBytes(wire.BinaryBytes(res), res2) + assert.Equal(t, res, *res2) +} + +func TestListUnconfirmedTxs(t *testing.T) { + res := &ResultListUnconfirmedTxs{ + N: 3, + Txs: []txs.Tx{ + &txs.CallTx{ + Address: &acm.Address{1}, + }, + }, + } + fmt.Println(string(wire.JSONBytes(res))) + +} + +func TestResultListAccounts(t *testing.T) { + concreteAcc := acm.AsConcreteAccount(acm.FromAddressable( + acm.GeneratePrivateAccountFromSecret("Super Semi Secret"))) + acc := concreteAcc + res := ResultListAccounts{ + Accounts: []*acm.ConcreteAccount{acc}, + BlockHeight: 2, + } + bs, err := json.Marshal(res) + require.NoError(t, err) + resOut := new(ResultListAccounts) + json.Unmarshal(bs, resOut) + bsOut, err := json.Marshal(resOut) + require.NoError(t, err) + assert.Equal(t, string(bs), string(bsOut)) +} + +func TestResultEvent(t *testing.T) { + res := ResultEvent{ + Event: "a", + } + fmt.Println(res) +} diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go deleted file mode 100644 index fbdfca374c8cd4a764ea064d06e74ce5b5ca0e4b..0000000000000000000000000000000000000000 --- a/rpc/rpc_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rpc - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// ... -func TestNewJsonRpcResponse(t *testing.T) { - id := "testId" - data := "a string" - resp := RPCResponse(&RPCResultResponse{ - Result: data, - Id: id, - JSONRPC: "2.0", - }) - respGen := NewRPCResponse(id, data) - assert.Equal(t, respGen, resp) -} - -// ... -func TestNewJsonRpcErrorResponse(t *testing.T) { - id := "testId" - code := 100 - message := "the error" - resp := RPCResponse(&RPCErrorResponse{ - Error: &RPCError{code, message}, - Id: id, - JSONRPC: "2.0", - }) - respGen := NewRPCErrorResponse(id, code, message) - assert.Equal(t, respGen, resp) -} diff --git a/rpc/service.go b/rpc/service.go new file mode 100644 index 0000000000000000000000000000000000000000..8dfcbdd5a85b8801fd97849950bd5e8caa353f62 --- /dev/null +++ b/rpc/service.go @@ -0,0 +1,405 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rpc + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/consensus/tendermint/query" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/txs" + tm_types "github.com/tendermint/tendermint/types" +) + +// Magic! Should probably be configurable, but not shouldn't be so huge we +// end up DoSing ourselves. +const MaxBlockLookback = 100 + +// Base service that provides implementation for all underlying RPC methods +type Service interface { + // Transact + BroadcastTx(tx txs.Tx) (*ResultBroadcastTx, error) + // Events + Subscribe(eventId string, callback func(eventData event.AnyEventData)) (*ResultSubscribe, error) + Unsubscribe(subscriptionId string) (*ResultUnsubscribe, error) + // Status + Status() (*ResultStatus, error) + NetInfo() (*ResultNetInfo, error) + // Accounts + GetAccount(address acm.Address) (*ResultGetAccount, error) + ListAccounts() (*ResultListAccounts, error) + GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) + DumpStorage(address acm.Address) (*ResultDumpStorage, error) + // Simulated call + Call(fromAddress, toAddress acm.Address, data []byte) (*ResultCall, error) + CallCode(fromAddress acm.Address, code, data []byte) (*ResultCall, error) + // Blockchain + Genesis() (*ResultGenesis, error) + ChainId() (*ResultChainId, error) + BlockchainInfo(minHeight, maxHeight uint64) (*ResultBlockchainInfo, error) + GetBlock(height uint64) (*ResultGetBlock, error) + // Consensus + ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) + ListValidators() (*ResultListValidators, error) + DumpConsensusState() (*ResultDumpConsensusState, error) + Peers() (*ResultPeers, error) + // Names + GetName(name string) (*ResultGetName, error) + ListNames() (*ResultListNames, error) + // Private keys and signing + SignTx(tx txs.Tx, concretePrivateAccounts []*acm.ConcretePrivateAccount) (*ResultSignTx, error) + GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) +} + +type service struct { + state acm.StateIterable + eventEmitter event.Emitter + nameReg execution.NameRegIterable + blockchain bcm.Blockchain + transactor execution.Transactor + nodeView query.NodeView + logger logging_types.InfoTraceLogger +} + +var _ Service = &service{} + +func NewService(state acm.StateIterable, eventEmitter event.Emitter, nameReg execution.NameRegIterable, + blockchain bcm.Blockchain, transactor execution.Transactor, nodeView query.NodeView, + logger logging_types.InfoTraceLogger) *service { + + return &service{ + state: state, + eventEmitter: eventEmitter, + nameReg: nameReg, + blockchain: blockchain, + transactor: transactor, + nodeView: nodeView, + logger: logger.With(structure.ComponentKey, "Service"), + } +} + +// All methods in this file return (Result*, error) which is the return +// signature assumed by go-rpc + +func (s *service) Subscribe(eventId string, callback func(event.AnyEventData)) (*ResultSubscribe, error) { + subscriptionId, err := event.GenerateSubId() + + logging.InfoMsg(s.logger, "Subscribing to event", + "eventId", eventId, "subscriptionId", subscriptionId) + if err != nil { + return nil, err + } + err = s.eventEmitter.Subscribe(subscriptionId, eventId, callback) + if err != nil { + return nil, err + } + return &ResultSubscribe{ + SubscriptionId: subscriptionId, + Event: eventId, + }, nil +} + +func (s *service) Unsubscribe(subscriptionId string) (*ResultUnsubscribe, error) { + err := s.eventEmitter.Unsubscribe(subscriptionId) + if err != nil { + return nil, err + } else { + return &ResultUnsubscribe{SubscriptionId: subscriptionId}, nil + } +} + +func (s *service) Status() (*ResultStatus, error) { + tip := s.blockchain.Tip() + latestHeight := tip.LastBlockHeight() + var ( + latestBlockMeta *tm_types.BlockMeta + latestBlockHash []byte + latestBlockTime int64 + ) + if latestHeight != 0 { + latestBlockMeta = s.nodeView.BlockStore().LoadBlockMeta(int(latestHeight)) + latestBlockHash = latestBlockMeta.Header.Hash() + latestBlockTime = latestBlockMeta.Header.Time.UnixNano() + } + return &ResultStatus{ + NodeInfo: s.nodeView.NodeInfo(), + GenesisHash: s.blockchain.GenesisHash(), + PubKey: s.nodeView.PrivValidatorPublicKey(), + LatestBlockHash: latestBlockHash, + LatestBlockHeight: latestHeight, + LatestBlockTime: latestBlockTime}, nil +} + +func (s *service) ChainId() (*ResultChainId, error) { + return &ResultChainId{ + ChainName: s.blockchain.GenesisDoc().ChainName, + ChainId: s.blockchain.ChainID(), + GenesisHash: s.blockchain.GenesisHash(), + }, nil +} + +func (s *service) Peers() (*ResultPeers, error) { + peers := make([]*Peer, s.nodeView.Peers().Size()) + for i, peer := range s.nodeView.Peers().List() { + peers[i] = &Peer{ + NodeInfo: peer.NodeInfo(), + IsOutbound: peer.IsOutbound(), + } + } + return &ResultPeers{ + Peers: peers, + }, nil +} + +func (s *service) NetInfo() (*ResultNetInfo, error) { + listening := s.nodeView.IsListening() + listeners := []string{} + for _, listener := range s.nodeView.Listeners() { + listeners = append(listeners, listener.String()) + } + peers, err := s.Peers() + if err != nil { + return nil, err + } + return &ResultNetInfo{ + Listening: listening, + Listeners: listeners, + Peers: peers.Peers, + }, nil +} + +func (s *service) Genesis() (*ResultGenesis, error) { + return &ResultGenesis{ + Genesis: s.blockchain.GenesisDoc(), + }, nil +} + +// Accounts +func (s *service) GetAccount(address acm.Address) (*ResultGetAccount, error) { + acc, err := s.state.GetAccount(address) + if err != nil { + return nil, err + } + return &ResultGetAccount{Account: acm.AsConcreteAccount(acc)}, nil +} + +func (s *service) ListAccounts() (*ResultListAccounts, error) { + accounts := make([]*acm.ConcreteAccount, 0) + s.state.IterateAccounts(func(account acm.Account) (stop bool) { + accounts = append(accounts, acm.AsConcreteAccount(account)) + return + }) + + return &ResultListAccounts{ + BlockHeight: s.blockchain.Tip().LastBlockHeight(), + Accounts: accounts, + }, nil +} + +func (s *service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) { + account, err := s.state.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("UnknownAddress: %s", address) + } + + value, err := s.state.GetStorage(address, binary.LeftPadWord256(key)) + if err != nil { + return nil, err + } + if value == binary.Zero256 { + return &ResultGetStorage{Key: key, Value: nil}, nil + } + return &ResultGetStorage{Key: key, Value: value.UnpadLeft()}, nil +} + +func (s *service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { + account, err := s.state.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("UnknownAddress: %X", address) + } + storageItems := []StorageItem{} + s.state.IterateStorage(address, func(key, value binary.Word256) (stop bool) { + storageItems = append(storageItems, StorageItem{Key: key.UnpadLeft(), Value: value.UnpadLeft()}) + return + }) + return &ResultDumpStorage{ + StorageRoot: account.StorageRoot(), + StorageItems: storageItems, + }, nil +} + +func (s *service) Call(fromAddress, toAddress acm.Address, data []byte) (*ResultCall, error) { + call, err := s.transactor.Call(fromAddress, toAddress, data) + if err != nil { + return nil, err + } + return &ResultCall{ + Call: call, + }, nil +} + +func (s *service) CallCode(fromAddress acm.Address, code, data []byte) (*ResultCall, error) { + call, err := s.transactor.CallCode(fromAddress, code, data) + if err != nil { + return nil, err + } + return &ResultCall{ + Call: call, + }, nil +} + +// Name registry +func (s *service) GetName(name string) (*ResultGetName, error) { + entry := s.nameReg.GetNameRegEntry(name) + if entry == nil { + return nil, fmt.Errorf("name %s not found", name) + } + return &ResultGetName{Entry: entry}, nil +} + +func (s *service) ListNames() (*ResultListNames, error) { + var names []*execution.NameRegEntry + s.nameReg.IterateNameRegEntries(func(entry *execution.NameRegEntry) (stop bool) { + names = append(names, entry) + return false + }) + return &ResultListNames{ + BlockHeight: s.blockchain.Tip().LastBlockHeight(), + Names: names, + }, nil +} + +func (s *service) BroadcastTx(tx txs.Tx) (*ResultBroadcastTx, error) { + receipt, err := s.transactor.BroadcastTx(tx) + if err != nil { + return nil, err + } + return &ResultBroadcastTx{ + Receipt: receipt, + }, nil +} + +func (s *service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) { + // Get all transactions for now + transactions, err := s.nodeView.MempoolTransactions(maxTxs) + if err != nil { + return nil, err + } + wrappedTxs := make([]txs.Wrapper, len(transactions)) + for i, tx := range transactions { + wrappedTxs[i] = txs.Wrap(tx) + } + return &ResultListUnconfirmedTxs{ + N: len(transactions), + Txs: wrappedTxs, + }, nil +} + +// Returns the current blockchain height and metadata for a range of blocks +// between minHeight and maxHeight. Only returns maxBlockLookback block metadata +// from the top of the range of blocks. +// Passing 0 for maxHeight sets the upper height of the range to the current +// blockchain height. +func (s *service) BlockchainInfo(minHeight, maxHeight uint64) (*ResultBlockchainInfo, error) { + latestHeight := s.blockchain.Tip().LastBlockHeight() + + if minHeight == 0 { + minHeight = 1 + } + if maxHeight == 0 || latestHeight < maxHeight { + maxHeight = latestHeight + } + if maxHeight > minHeight && maxHeight-minHeight > MaxBlockLookback { + minHeight = maxHeight - MaxBlockLookback + } + + blockMetas := []*tm_types.BlockMeta{} + for height := maxHeight; height >= minHeight; height-- { + blockMeta := s.nodeView.BlockStore().LoadBlockMeta(int(height)) + blockMetas = append(blockMetas, blockMeta) + } + + return &ResultBlockchainInfo{ + LastHeight: latestHeight, + BlockMetas: blockMetas, + }, nil +} + +func (s *service) GetBlock(height uint64) (*ResultGetBlock, error) { + return &ResultGetBlock{ + Block: s.nodeView.BlockStore().LoadBlock(int(height)), + BlockMeta: s.nodeView.BlockStore().LoadBlockMeta(int(height)), + }, nil +} + +func (s *service) ListValidators() (*ResultListValidators, error) { + // TODO: when we reintroduce support for bonding and unbonding update this + // to reflect the mutable bonding state + validators := s.blockchain.Validators() + concreteValidators := make([]*acm.ConcreteValidator, len(validators)) + for i, validator := range validators { + concreteValidators[i] = acm.AsConcreteValidator(validator) + } + return &ResultListValidators{ + BlockHeight: s.blockchain.Tip().LastBlockHeight(), + BondedValidators: concreteValidators, + UnbondingValidators: nil, + }, nil +} + +func (s *service) DumpConsensusState() (*ResultDumpConsensusState, error) { + peerRoundState, err := s.nodeView.PeerRoundStates() + if err != nil { + return nil, err + } + return &ResultDumpConsensusState{ + RoundState: s.nodeView.RoundState(), + PeerRoundStates: peerRoundState, + }, nil +} + +// TODO: Either deprecate this or ensure it can only happen over secure transport +func (s *service) SignTx(tx txs.Tx, concretePrivateAccounts []*acm.ConcretePrivateAccount) (*ResultSignTx, error) { + privateAccounts := make([]acm.PrivateAccount, len(concretePrivateAccounts)) + for i, cpa := range concretePrivateAccounts { + privateAccounts[i] = cpa.PrivateAccount() + } + + tx, err := s.transactor.SignTx(tx, privateAccounts) + return &ResultSignTx{Tx: txs.Wrap(tx)}, err +} + +func (s *service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) { + privateAccount, err := acm.GeneratePrivateAccount() + if err != nil { + return nil, err + } + return &ResultGeneratePrivateAccount{ + PrivAccount: acm.AsConcretePrivateAccount(privateAccount), + }, nil +} diff --git a/rpc/tendermint/client/client.go b/rpc/tendermint/client/client.go deleted file mode 100644 index fe9f8ca55e774e61098074c89a94b9f2e2b957e5..0000000000000000000000000000000000000000 --- a/rpc/tendermint/client/client.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "errors" - "fmt" - - acm "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - rpc_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" - "github.com/tendermint/go-wire" -) - -type RPCClient interface { - Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) -} - -func Status(client RPCClient) (*rpc_types.ResultStatus, error) { - res, err := call(client, "status") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultStatus), nil -} - -func ChainId(client RPCClient) (*rpc_types.ResultChainId, error) { - res, err := call(client, "chain_id") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultChainId), nil -} - -func GenPrivAccount(client RPCClient) (*acm.PrivAccount, error) { - res, err := call(client, "unsafe/gen_priv_account") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGenPrivAccount).PrivAccount, nil -} - -func GetAccount(client RPCClient, address []byte) (*acm.Account, error) { - res, err := call(client, "get_account", - "address", address) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetAccount).Account, nil -} - -func SignTx(client RPCClient, tx txs.Tx, - privAccounts []*acm.PrivAccount) (txs.Tx, error) { - res, err := call(client, "unsafe/sign_tx", - "tx", wrappedTx{tx}, - "privAccounts", privAccounts) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultSignTx).Tx, nil -} - -func BroadcastTx(client RPCClient, - tx txs.Tx) (txs.Receipt, error) { - res, err := call(client, "broadcast_tx", - "tx", wrappedTx{tx}) - if err != nil { - return txs.Receipt{}, err - } - receiptBytes := res.(*rpc_types.ResultBroadcastTx).Data - receipt := txs.Receipt{} - err = wire.ReadBinaryBytes(receiptBytes, &receipt) - return receipt, err - -} - -func DumpStorage(client RPCClient, - address []byte) (*rpc_types.ResultDumpStorage, error) { - res, err := call(client, "dump_storage", - "address", address) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultDumpStorage), err -} - -func GetStorage(client RPCClient, address, key []byte) ([]byte, error) { - res, err := call(client, "get_storage", - "address", address, - "key", key) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetStorage).Value, nil -} - -func CallCode(client RPCClient, fromAddress, code, - data []byte) (*rpc_types.ResultCall, error) { - res, err := call(client, "call_code", - "fromAddress", fromAddress, - "code", code, - "data", data) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultCall), err -} - -func Call(client RPCClient, fromAddress, toAddress, - data []byte) (*rpc_types.ResultCall, error) { - res, err := call(client, "call", - "fromAddress", fromAddress, - "toAddress", toAddress, - "data", data) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultCall), err -} - -func GetName(client RPCClient, name string) (*core_types.NameRegEntry, error) { - res, err := call(client, "get_name", - "name", name) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetName).Entry, nil -} - -func BlockchainInfo(client RPCClient, minHeight, - maxHeight int) (*rpc_types.ResultBlockchainInfo, error) { - res, err := call(client, "blockchain", - "minHeight", minHeight, - "maxHeight", maxHeight) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultBlockchainInfo), err -} - -func GetBlock(client RPCClient, height int) (*rpc_types.ResultGetBlock, error) { - res, err := call(client, "get_block", - "height", height) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetBlock), err -} - -func ListUnconfirmedTxs(client RPCClient) (*rpc_types.ResultListUnconfirmedTxs, error) { - res, err := call(client, "list_unconfirmed_txs") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultListUnconfirmedTxs), err -} - -func ListValidators(client RPCClient) (*rpc_types.ResultListValidators, error) { - res, err := call(client, "list_validators") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultListValidators), err -} - -func DumpConsensusState(client RPCClient) (*rpc_types.ResultDumpConsensusState, error) { - res, err := call(client, "dump_consensus_state") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultDumpConsensusState), err -} - -func call(client RPCClient, method string, - paramKeyVals ...interface{}) (res rpc_types.BurrowResult, err error) { - pMap, err := paramsMap(paramKeyVals...) - if err != nil { - return - } - _, err = client.Call(method, pMap, &res) - return -} - -func paramsMap(orderedKeyVals ...interface{}) (map[string]interface{}, error) { - if len(orderedKeyVals)%2 != 0 { - return nil, fmt.Errorf("mapAndValues requires a even length list of"+ - " keys and values but got: %v (length %v)", - orderedKeyVals, len(orderedKeyVals)) - } - paramsMap := make(map[string]interface{}) - for i := 0; i < len(orderedKeyVals); i += 2 { - key, ok := orderedKeyVals[i].(string) - if !ok { - return nil, errors.New("mapAndValues requires every even element" + - " of orderedKeyVals to be a string key") - } - val := orderedKeyVals[i+1] - paramsMap[key] = val - } - return paramsMap, nil -} - -type wrappedTx struct { - txs.Tx `json:"unwrap"` -} diff --git a/rpc/tendermint/client/client_test.go b/rpc/tendermint/client/client_test.go deleted file mode 100644 index 13a5c98e48e668d53accfb113e91bf06c1ef72d1..0000000000000000000000000000000000000000 --- a/rpc/tendermint/client/client_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParamsMap(t *testing.T) { - type aStruct struct { - Baz int - } - dict, err := paramsMap("Foo", aStruct{5}, - "Bar", "Nibbles") - assert.NoError(t, err, "Should not be a paramsMaperror") - assert.Equal(t, map[string]interface{}{ - "Foo": aStruct{5}, - "Bar": "Nibbles", - }, dict) - - // Empty map - dict, err = paramsMap() - assert.Equal(t, map[string]interface{}{}, dict) - assert.NoError(t, err, "Empty mapsAndValues call should be fine") - - // Invalid maps - assert.NoError(t, err, "Empty mapsAndValues call should be fine") - _, err = paramsMap("Foo", 4, "Bar") - assert.Error(t, err, "Should be an error to get an odd number of arguments") - - _, err = paramsMap("Foo", 4, 4, "Bar") - assert.Error(t, err, "Should be an error to provide non-string keys") -} diff --git a/rpc/tendermint/client/websocket_client.go b/rpc/tendermint/client/websocket_client.go deleted file mode 100644 index 4749517b72febf7b62929fe4fa7b18c986567bdb..0000000000000000000000000000000000000000 --- a/rpc/tendermint/client/websocket_client.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import "github.com/tendermint/go-rpc/types" - -type WebsocketClient interface { - WriteJSON(v interface{}) error -} - -func Subscribe(websocketClient WebsocketClient, eventId string) error { - return websocketClient.WriteJSON(rpctypes.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "subscribe", - Params: map[string]interface{}{"eventId": eventId}, - }) -} - -func Unsubscribe(websocketClient WebsocketClient, subscriptionId string) error { - return websocketClient.WriteJSON(rpctypes.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "unsubscribe", - Params: map[string]interface{}{"subscriptionId": subscriptionId}, - }) -} diff --git a/rpc/tendermint/core/routes.go b/rpc/tendermint/core/routes.go deleted file mode 100644 index b295b3a6de5d12b5b71dccb8b274f35aad7143c3..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/routes.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package core - -import ( - "fmt" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/definitions" - ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" - rpc "github.com/tendermint/go-rpc/server" - rpctypes "github.com/tendermint/go-rpc/types" -) - -// TODO: [ben] encapsulate Routes into a struct for a given TendermintPipe - -// Magic! Should probably be configurable, but not shouldn't be so huge we -// end up DoSing ourselves. -const maxBlockLookback = 20 - -// TODO: eliminate redundancy between here and reading code from core/ -type TendermintRoutes struct { - tendermintPipe definitions.TendermintPipe -} - -func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc { - var routes = map[string]*rpc.RPCFunc{ - "subscribe": rpc.NewWSRPCFunc(tmRoutes.Subscribe, "eventId"), - "unsubscribe": rpc.NewWSRPCFunc(tmRoutes.Unsubscribe, "subscriptionId"), - "status": rpc.NewRPCFunc(tmRoutes.StatusResult, ""), - "net_info": rpc.NewRPCFunc(tmRoutes.NetInfoResult, ""), - "genesis": rpc.NewRPCFunc(tmRoutes.GenesisResult, ""), - "chain_id": rpc.NewRPCFunc(tmRoutes.ChainIdResult, ""), - "get_account": rpc.NewRPCFunc(tmRoutes.GetAccountResult, "address"), - "get_storage": rpc.NewRPCFunc(tmRoutes.GetStorageResult, "address,key"), - "call": rpc.NewRPCFunc(tmRoutes.CallResult, "fromAddress,toAddress,data"), - "call_code": rpc.NewRPCFunc(tmRoutes.CallCodeResult, "fromAddress,code,data"), - "dump_storage": rpc.NewRPCFunc(tmRoutes.DumpStorageResult, "address"), - "list_accounts": rpc.NewRPCFunc(tmRoutes.ListAccountsResult, ""), - "get_name": rpc.NewRPCFunc(tmRoutes.GetNameResult, "name"), - "list_names": rpc.NewRPCFunc(tmRoutes.ListNamesResult, ""), - "broadcast_tx": rpc.NewRPCFunc(tmRoutes.BroadcastTxResult, "tx"), - "blockchain": rpc.NewRPCFunc(tmRoutes.BlockchainInfo, "minHeight,maxHeight"), - "get_block": rpc.NewRPCFunc(tmRoutes.GetBlock, "height"), - "list_unconfirmed_txs": rpc.NewRPCFunc(tmRoutes.ListUnconfirmedTxs, ""), - "list_validators": rpc.NewRPCFunc(tmRoutes.ListValidators, ""), - "dump_consensus_state": rpc.NewRPCFunc(tmRoutes.DumpConsensusState, ""), - "unsafe/gen_priv_account": rpc.NewRPCFunc(tmRoutes.GenPrivAccountResult, ""), - "unsafe/sign_tx": rpc.NewRPCFunc(tmRoutes.SignTxResult, "tx,privAccounts"), - // TODO: [Silas] do we also carry forward "consensus_state" as in v0? - } - return routes -} - -func (tmRoutes *TendermintRoutes) Subscribe(wsCtx rpctypes.WSRPCContext, - eventId string) (ctypes.BurrowResult, error) { - // NOTE: RPCResponses of subscribed events have id suffix "#event" - // TODO: we really ought to allow multiple subscriptions from the same client address - // to the same event. The code as it stands reflects the somewhat broken tendermint - // implementation. We can use GenerateSubId to randomize the subscriptions id - // and return it in the result. This would require clients to hang on to a - // subscription id if they wish to unsubscribe, but then again they can just - // drop their connection - result, err := tmRoutes.tendermintPipe.Subscribe(eventId, - func(result ctypes.BurrowResult) { - wsCtx.GetRemoteAddr() - // NOTE: EventSwitch callbacks must be nonblocking - wsCtx.TryWriteRPCResponse( - rpctypes.NewRPCResponse(wsCtx.Request.ID+"#event", &result, "")) - }) - if err != nil { - return nil, err - } else { - return result, nil - } -} - -func (tmRoutes *TendermintRoutes) Unsubscribe(wsCtx rpctypes.WSRPCContext, - subscriptionId string) (ctypes.BurrowResult, error) { - result, err := tmRoutes.tendermintPipe.Unsubscribe(subscriptionId) - if err != nil { - return nil, err - } else { - return result, nil - } -} - -func (tmRoutes *TendermintRoutes) StatusResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.Status(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) NetInfoResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.NetInfo(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GenesisResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.Genesis(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ChainIdResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.ChainId(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GetAccountResult(address []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.GetAccount(address); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GetStorageResult(address, key []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.GetStorage(address, key); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) CallResult(fromAddress, toAddress, - data []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.Call(fromAddress, toAddress, data); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) CallCodeResult(fromAddress, code, - data []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.CallCode(fromAddress, code, data); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) DumpStorageResult(address []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.DumpStorage(address); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ListAccountsResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.ListAccounts(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GetNameResult(name string) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.GetName(name); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ListNamesResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.ListNames(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GenPrivAccountResult() (ctypes.BurrowResult, error) { - //if r, err := tmRoutes.tendermintPipe.GenPrivAccount(); err != nil { - // return nil, err - //} else { - // return r, nil - //} - return nil, fmt.Errorf("Unimplemented as poor practice to generate private account over unencrypted RPC") -} - -func (tmRoutes *TendermintRoutes) SignTxResult(tx txs.Tx, - privAccounts []*acm.PrivAccount) (ctypes.BurrowResult, error) { - // if r, err := tmRoutes.tendermintPipe.SignTx(tx, privAccounts); err != nil { - // return nil, err - // } else { - // return r, nil - // } - return nil, fmt.Errorf("Unimplemented as poor practice to pass private account over unencrypted RPC") -} - -func (tmRoutes *TendermintRoutes) BroadcastTxResult(tx txs.Tx) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.BroadcastTxSync(tx); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) BlockchainInfo(minHeight, - maxHeight int) (ctypes.BurrowResult, error) { - r, err := tmRoutes.tendermintPipe.BlockchainInfo(minHeight, maxHeight, - maxBlockLookback) - if err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ListUnconfirmedTxs() (ctypes.BurrowResult, error) { - // Get all Txs for now - r, err := tmRoutes.tendermintPipe.ListUnconfirmedTxs(-1) - if err != nil { - return nil, err - } else { - return r, nil - } -} -func (tmRoutes *TendermintRoutes) GetBlock(height int) (ctypes.BurrowResult, error) { - r, err := tmRoutes.tendermintPipe.GetBlock(height) - if err != nil { - return nil, err - } else { - return r, nil - } -} -func (tmRoutes *TendermintRoutes) ListValidators() (ctypes.BurrowResult, error) { - r, err := tmRoutes.tendermintPipe.ListValidators() - if err != nil { - return nil, err - } else { - return r, nil - } -} -func (tmRoutes *TendermintRoutes) DumpConsensusState() (ctypes.BurrowResult, error) { - return tmRoutes.tendermintPipe.DumpConsensusState() -} diff --git a/rpc/tendermint/core/types/responses.go b/rpc/tendermint/core/types/responses.go deleted file mode 100644 index 33b46d4265e23f7f7831fc2ca74effe45b0a2c15..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/types/responses.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - acm "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/txs" - tendermint_types "github.com/tendermint/tendermint/types" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - abcitypes "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-p2p" - "github.com/tendermint/go-rpc/types" - "github.com/tendermint/go-wire" -) - -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 []*tendermint_types.BlockMeta `json:"block_metas"` -} - -type ResultGetBlock struct { - BlockMeta *tendermint_types.BlockMeta `json:"block_meta"` - Block *tendermint_types.Block `json:"block"` -} - -type ResultStatus struct { - 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 ResultChainId struct { - ChainName string `json:"chain_name"` - ChainId string `json:"chain_id"` - GenesisHash []byte `json:"genesis_hash"` -} - -type ResultSubscribe struct { - Event string `json:"event"` - SubscriptionId string `json:"subscription_id"` -} - -type ResultUnsubscribe struct { - SubscriptionId string `json:"subscription_id"` -} - -type ResultNetInfo struct { - Listening bool `json:"listening"` - Listeners []string `json:"listeners"` - Peers []*consensus_types.Peer `json:"peers"` -} - -type ResultListValidators struct { - BlockHeight int `json:"block_height"` - BondedValidators []consensus_types.Validator `json:"bonded_validators"` - UnbondingValidators []consensus_types.Validator `json:"unbonding_validators"` -} - -type ResultDumpConsensusState struct { - ConsensusState *consensus_types.ConsensusState `json:"consensus_state"` - PeerConsensusStates []*ResultPeerConsensusState `json:"peer_consensus_states"` -} - -type ResultPeerConsensusState struct { - PeerKey string `json:"peer_key"` - PeerConsensusState string `json:"peer_consensus_state"` -} - -type ResultListNames struct { - BlockHeight int `json:"block_height"` - Names []*core_types.NameRegEntry `json:"names"` -} - -type ResultGenPrivAccount struct { - PrivAccount *acm.PrivAccount `json:"priv_account"` -} - -type ResultGetAccount struct { - Account *acm.Account `json:"account"` -} - -type ResultBroadcastTx struct { - Code abcitypes.CodeType `json:"code"` - Data []byte `json:"data"` - Log string `json:"log"` -} - -type ResultListUnconfirmedTxs struct { - N int `json:"n_txs"` - Txs []txs.Tx `json:"txs"` -} - -type ResultGetName struct { - Entry *core_types.NameRegEntry `json:"entry"` -} - -type ResultGenesis struct { - Genesis *genesis.GenesisDoc `json:"genesis"` -} - -type ResultSignTx struct { - Tx txs.Tx `json:"tx"` -} - -type ResultEvent struct { - Event string `json:"event"` - Data txs.EventData `json:"data"` -} - -//---------------------------------------- -// result types - -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 - ResultTypeSubscribe = byte(0x14) - ResultTypeUnsubscribe = byte(0x15) - ResultTypePeerConsensusState = byte(0x16) - ResultTypeChainId = byte(0x17) -) - -type BurrowResult interface { - rpctypes.Result -} - -func ConcreteTypes() []wire.ConcreteType { - return []wire.ConcreteType{ - {&ResultGetStorage{}, ResultTypeGetStorage}, - {&ResultCall{}, ResultTypeCall}, - {&ResultListAccounts{}, ResultTypeListAccounts}, - {&ResultDumpStorage{}, ResultTypeDumpStorage}, - {&ResultBlockchainInfo{}, ResultTypeBlockchainInfo}, - {&ResultGetBlock{}, ResultTypeGetBlock}, - {&ResultStatus{}, ResultTypeStatus}, - {&ResultNetInfo{}, ResultTypeNetInfo}, - {&ResultListValidators{}, ResultTypeListValidators}, - {&ResultDumpConsensusState{}, ResultTypeDumpConsensusState}, - {&ResultDumpConsensusState{}, ResultTypePeerConsensusState}, - {&ResultListNames{}, ResultTypeListNames}, - {&ResultGenPrivAccount{}, ResultTypeGenPrivAccount}, - {&ResultGetAccount{}, ResultTypeGetAccount}, - {&ResultBroadcastTx{}, ResultTypeBroadcastTx}, - {&ResultListUnconfirmedTxs{}, ResultTypeListUnconfirmedTxs}, - {&ResultGetName{}, ResultTypeGetName}, - {&ResultGenesis{}, ResultTypeGenesis}, - {&ResultSignTx{}, ResultTypeSignTx}, - {&ResultEvent{}, ResultTypeEvent}, - {&ResultSubscribe{}, ResultTypeSubscribe}, - {&ResultUnsubscribe{}, ResultTypeUnsubscribe}, - {&ResultChainId{}, ResultTypeChainId}, - } -} - -var _ = wire.RegisterInterface(struct{ BurrowResult }{}, ConcreteTypes()...) diff --git a/rpc/tendermint/core/types/responses_test.go b/rpc/tendermint/core/types/responses_test.go deleted file mode 100644 index c745017c8d74a8bc9d695667c5dec01a1abbb983..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/types/responses_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "testing" - - "time" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - "github.com/tendermint/go-wire" - tendermint_types "github.com/tendermint/tendermint/types" -) - -func TestResultDumpConsensusState(t *testing.T) { - result := ResultDumpConsensusState{ - ConsensusState: &consensus_types.ConsensusState{ - Height: 3, - Round: 1, - Step: uint8(1), - StartTime: time.Now().Add(-time.Second * 100), - CommitTime: time.Now().Add(-time.Second * 10), - Validators: []consensus_types.Validator{ - &consensus_types.TendermintValidator{}, - }, - Proposal: &tendermint_types.Proposal{}, - }, - PeerConsensusStates: []*ResultPeerConsensusState{ - { - PeerKey: "Foo", - PeerConsensusState: "Bar", - }, - }, - } - wire.JSONBytes(result) -} diff --git a/rpc/tendermint/core/types/responses_util.go b/rpc/tendermint/core/types/responses_util.go deleted file mode 100644 index cd3e15e8c6675dd92aaf19610bc6f52608c87e8c..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/types/responses_util.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "github.com/hyperledger/burrow/core/types" -) - -// UnwrapResultDumpStorage does a deep copy to remove /rpc/tendermint/core/types -// and expose /core/types instead. This is largely an artefact to be removed once -// go-wire and go-rpc are deprecated. -// This is not an efficient code, especially given Storage can be big. -func UnwrapResultDumpStorage(result *ResultDumpStorage) *types.Storage { - storageRoot := make([]byte, len(result.StorageRoot)) - copy(storageRoot, result.StorageRoot) - storageItems := make([]types.StorageItem, len(result.StorageItems)) - for i, item := range result.StorageItems { - key := make([]byte, len(item.Key)) - value := make([]byte, len(item.Value)) - copy(key, item.Key) - copy(value, item.Value) - storageItems[i] = types.StorageItem{ - Key: key, - Value: value, - } - } - return &types.Storage{ - StorageRoot: storageRoot, - StorageItems: storageItems, - } -} diff --git a/rpc/tendermint/core/websocket.go b/rpc/tendermint/core/websocket.go deleted file mode 100644 index 2a4feffe1ff25787d298a4e51c2cd6232782e7ad..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/websocket.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package core - -import ( - "fmt" - "net" - "net/http" - "strings" - - events "github.com/tendermint/go-events" - rpcserver "github.com/tendermint/go-rpc/server" - - definitions "github.com/hyperledger/burrow/definitions" - server "github.com/hyperledger/burrow/server" -) - -type TendermintWebsocketServer struct { - routes TendermintRoutes - listeners []net.Listener -} - -func NewTendermintWebsocketServer(config *server.ServerConfig, - tendermintPipe definitions.TendermintPipe, evsw events.EventSwitch) ( - *TendermintWebsocketServer, error) { - - if tendermintPipe == nil { - return nil, fmt.Errorf("No Tendermint pipe provided.") - } - tendermintRoutes := TendermintRoutes{ - tendermintPipe: tendermintPipe, - } - routes := tendermintRoutes.GetRoutes() - listenerAddresses := strings.Split(config.Tendermint.RpcLocalAddress, ",") - if len(listenerAddresses) == 0 { - return nil, fmt.Errorf("No RPC listening addresses provided in [servers.tendermint.rpc_local_address] in configuration file: %s", - listenerAddresses) - } - listeners := make([]net.Listener, len(listenerAddresses)) - for i, listenerAddress := range listenerAddresses { - mux := http.NewServeMux() - wm := rpcserver.NewWebsocketManager(routes, evsw) - mux.HandleFunc(config.Tendermint.Endpoint, wm.WebsocketHandler) - rpcserver.RegisterRPCFuncs(mux, routes) - listener, err := rpcserver.StartHTTPServer(listenerAddress, mux) - if err != nil { - return nil, err - } - listeners[i] = listener - } - return &TendermintWebsocketServer{ - routes: tendermintRoutes, - listeners: listeners, - }, nil -} - -func (tmServer *TendermintWebsocketServer) Shutdown() { - for _, listener := range tmServer.listeners { - listener.Close() - } -} diff --git a/rpc/tendermint/test/runner/main.go b/rpc/tendermint/test/runner/main.go deleted file mode 100644 index e17ee5b8184791e65df67ec8a0ccceac216a0a29..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/runner/main.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build integration - -// Space above here matters -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - - rpctest "github.com/hyperledger/burrow/rpc/tendermint/test" - "github.com/hyperledger/burrow/util" -) - -func main() { - //fmt.Printf("%s", util.IsAddress("hello")) - fmt.Printf("%s", util.IsAddress("hello"), rpctest.Successor(2)) - //defer os.Exit(0) -} diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go deleted file mode 100644 index 4ca904ddea2817bcffe61c0e16f5e11966c85692..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/shared.go +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -import ( - "bytes" - "errors" - "fmt" - "hash/fnv" - "path" - "strconv" - "testing" - - "time" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/config" - "github.com/hyperledger/burrow/core" - core_types "github.com/hyperledger/burrow/core/types" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/rpc/tendermint/client" - edbcli "github.com/hyperledger/burrow/rpc/tendermint/client" - rpc_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/server" - "github.com/hyperledger/burrow/test/fixtures" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" - "github.com/spf13/viper" - "github.com/tendermint/go-crypto" - rpcclient "github.com/tendermint/go-rpc/client" - "github.com/tendermint/tendermint/types" -) - -const chainID = "RPC_Test_Chain" - -// global variables for use across all tests -var ( - serverConfig *server.ServerConfig - rootWorkDir string - mempoolCount = 0 - websocketAddr string - genesisDoc *genesis.GenesisDoc - websocketEndpoint string - users = makeUsers(5) // make keys - jsonRpcClient client.RPCClient - httpClient client.RPCClient - clients map[string]client.RPCClient - testCore *core.Core -) - -// We use this to wrap tests -func TestWrapper(runner func() int) int { - fmt.Println("Running with integration TestWrapper (rpc/tendermint/test/shared_test.go)...") - ffs := fixtures.NewFileFixtures("burrow") - - defer func() { - // Tendermint likes to try and save to priv_validator.json after its been - // asked to shutdown so we pause to try and avoid collision - time.Sleep(time.Second) - ffs.RemoveAll() - }() - - vm.SetDebug(true) - err := initGlobalVariables(ffs) - - if err != nil { - panic(err) - } - - tmServer, err := testCore.NewGatewayTendermint(serverConfig) - defer func() { - // Shutdown -- make sure we don't hit a race on ffs.RemoveAll - tmServer.Shutdown() - testCore.Stop() - }() - - if err != nil { - panic(err) - } - - return runner() -} - -// initialize config and create new node -func initGlobalVariables(ffs *fixtures.FileFixtures) error { - configBytes, err := config.GetConfigurationFileBytes(chainID, - "test_single_node", - "", - "burrow", - true, - "46657", - "burrow serve") - if err != nil { - return err - } - - genesisBytes, err := genesisFileBytesFromUsers(chainID, users) - if err != nil { - return err - } - - testConfigFile := ffs.AddFile("config.toml", string(configBytes)) - rootWorkDir = ffs.AddDir("rootWorkDir") - rootDataDir := ffs.AddDir("rootDataDir") - genesisFile := ffs.AddFile("rootWorkDir/genesis.json", string(genesisBytes)) - genesisDoc = genesis.GenesisDocFromJSON(genesisBytes) - - if ffs.Error != nil { - return ffs.Error - } - - testConfig := viper.New() - testConfig.SetConfigFile(testConfigFile) - err = testConfig.ReadInConfig() - - if err != nil { - return err - } - - sconf, err := core.LoadServerConfig(chainID, testConfig) - if err != nil { - return err - } - serverConfig = sconf - - rpcAddr := serverConfig.Tendermint.RpcLocalAddress - websocketAddr = rpcAddr - websocketEndpoint = "/websocket" - - consensusConfig, err := core.LoadModuleConfig(testConfig, rootWorkDir, - rootDataDir, genesisFile, chainID, "consensus") - if err != nil { - return err - } - - managerConfig, err := core.LoadModuleConfig(testConfig, rootWorkDir, - rootDataDir, genesisFile, chainID, "manager") - if err != nil { - return err - } - - // Set up priv_validator.json before we start tendermint (otherwise it will - // create its own one. - saveNewPriv() - logger, _ := lifecycle.NewStdErrLogger() - // To spill tendermint logs on the floor: - // lifecycle.CaptureTendermintLog15Output(loggers.NewNoopInfoTraceLogger()) - lifecycle.CaptureTendermintLog15Output(logger) - lifecycle.CaptureStdlibLogOutput(logger) - - testCore, err = core.NewCore("testCore", consensusConfig, managerConfig, - logger) - if err != nil { - return err - } - - jsonRpcClient = rpcclient.NewJSONRPCClient(rpcAddr) - httpClient = rpcclient.NewURIClient(rpcAddr) - - clients = map[string]client.RPCClient{ - "JSONRPC": jsonRpcClient, - "HTTP": httpClient, - } - return nil -} - -// Deterministic account generation helper. Pass number of accounts to make -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 -} - -func genesisFileBytesFromUsers(chainName string, accounts []*acm.PrivAccount) ([]byte, error) { - if len(accounts) < 1 { - return nil, errors.New("Please pass in at least 1 account to be the validator") - } - genesisValidators := make([]*genesis.GenesisValidator, 1) - genesisAccounts := make([]*genesis.GenesisAccount, len(accounts)) - genesisValidators[0] = genesisValidatorFromPrivAccount(accounts[0]) - - for i, acc := range accounts { - genesisAccounts[i] = genesisAccountFromPrivAccount(acc) - } - - return genesis.GenerateGenesisFileBytes(chainName, genesisAccounts, genesisValidators) -} - -func genesisValidatorFromPrivAccount(account *acm.PrivAccount) *genesis.GenesisValidator { - return &genesis.GenesisValidator{ - Amount: 1000000, - Name: fmt.Sprintf("full-account_%X", account.Address), - PubKey: account.PubKey, - UnbondTo: []genesis.BasicAccount{ - { - Address: account.Address, - Amount: 100, - }, - }, - } -} - -func genesisAccountFromPrivAccount(account *acm.PrivAccount) *genesis.GenesisAccount { - return genesis.NewGenesisAccount(account.Address, 100000, - fmt.Sprintf("account_%X", account.Address), &ptypes.DefaultAccountPermissions) -} - -func saveNewPriv() { - // Save new priv_validator file. - priv := &types.PrivValidator{ - Address: users[0].Address, - PubKey: crypto.PubKeyEd25519(users[0].PubKey.(crypto.PubKeyEd25519)), - PrivKey: crypto.PrivKeyEd25519(users[0].PrivKey.(crypto.PrivKeyEd25519)), - } - priv.SetFile(path.Join(rootWorkDir, "priv_validator.json")) - priv.Save() -} - -//------------------------------------------------------------------------------- -// some default transaction functions - -func makeDefaultSendTx(t *testing.T, client client.RPCClient, addr []byte, - amt int64) *txs.SendTx { - nonce := getNonce(t, client, users[0].Address) - tx := txs.NewSendTx() - tx.AddInputWithNonce(users[0].PubKey, amt, nonce+1) - tx.AddOutput(addr, amt) - return tx -} - -func makeDefaultSendTxSigned(t *testing.T, client client.RPCClient, addr []byte, - amt int64) *txs.SendTx { - tx := makeDefaultSendTx(t, client, addr, amt) - tx.SignInput(chainID, 0, users[0]) - return tx -} - -func makeDefaultCallTx(t *testing.T, client client.RPCClient, addr, code []byte, amt, gasLim, - fee int64) *txs.CallTx { - nonce := getNonce(t, client, users[0].Address) - tx := txs.NewCallTxWithNonce(users[0].PubKey, addr, code, amt, gasLim, fee, - nonce+1) - tx.Sign(chainID, users[0]) - return tx -} - -func makeDefaultNameTx(t *testing.T, client client.RPCClient, name, value string, amt, - fee int64) *txs.NameTx { - nonce := getNonce(t, client, users[0].Address) - tx := txs.NewNameTxWithNonce(users[0].PubKey, name, value, amt, fee, nonce+1) - tx.Sign(chainID, users[0]) - return tx -} - -//------------------------------------------------------------------------------- -// rpc call wrappers (fail on err) - -// get an account's nonce -func getNonce(t *testing.T, client client.RPCClient, addr []byte) int { - ac, err := edbcli.GetAccount(client, addr) - if err != nil { - t.Fatal(err) - } - if ac == nil { - return 0 - } - return ac.Sequence -} - -// get the account -func getAccount(t *testing.T, client client.RPCClient, addr []byte) *acm.Account { - ac, err := edbcli.GetAccount(client, addr) - if err != nil { - t.Fatal(err) - } - return ac -} - -// sign transaction -func signTx(t *testing.T, client client.RPCClient, tx txs.Tx, - privAcc *acm.PrivAccount) txs.Tx { - signedTx, err := edbcli.SignTx(client, tx, []*acm.PrivAccount{privAcc}) - if err != nil { - t.Fatal(err) - } - return signedTx -} - -// broadcast transaction -func broadcastTx(t *testing.T, client client.RPCClient, tx txs.Tx) txs.Receipt { - rec, err := edbcli.BroadcastTx(client, tx) - if err != nil { - t.Fatal(err) - } - mempoolCount += 1 - return rec -} - -// dump all storage for an account. currently unused -func dumpStorage(t *testing.T, addr []byte) *rpc_types.ResultDumpStorage { - client := clients["HTTP"] - resp, err := edbcli.DumpStorage(client, addr) - if err != nil { - t.Fatal(err) - } - return resp -} - -func getStorage(t *testing.T, client client.RPCClient, addr, key []byte) []byte { - resp, err := edbcli.GetStorage(client, addr, key) - if err != nil { - t.Fatal(err) - } - return resp -} - -func callCode(t *testing.T, client client.RPCClient, fromAddress, code, data, - expected []byte) { - resp, err := edbcli.CallCode(client, 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, word256.LeftPadWord256(expected).Bytes()) != 0 { - t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) - } -} - -func callContract(t *testing.T, client client.RPCClient, fromAddress, toAddress, - data, expected []byte) { - resp, err := edbcli.Call(client, 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, word256.LeftPadWord256(expected).Bytes()) != 0 { - t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) - } -} - -// get the namereg entry -func getNameRegEntry(t *testing.T, client client.RPCClient, name string) *core_types.NameRegEntry { - entry, err := edbcli.GetName(client, name) - if err != nil { - t.Fatal(err) - } - return entry -} - -// Returns a positive int64 hash of text (consumers want int64 instead of uint64) -func hashString(text string) int64 { - hasher := fnv.New64() - hasher.Write([]byte(text)) - value := int64(hasher.Sum64()) - // Flip the sign if we wrapped - if value < 0 { - return -value - } - return value -} - -//-------------------------------------------------------------------------------- -// utility verification function - -// 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}, - word256.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, word256.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, word256.LeftPadBytes([]byte{0xb}, 32) -} diff --git a/rpc/tm/client/client.go b/rpc/tm/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..f1654899e0baf27e0189fb644d0e33991370d4bb --- /dev/null +++ b/rpc/tm/client/client.go @@ -0,0 +1,207 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "errors" + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc/tm/method" + "github.com/hyperledger/burrow/txs" +) + +type RPCClient interface { + Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) +} + +func Status(client RPCClient) (*rpc.ResultStatus, error) { + res := new(rpc.Result) + _, err := client.Call(method.Status, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultStatus), nil +} + +func ChainId(client RPCClient) (*rpc.ResultChainId, error) { + res := new(rpc.Result) + _, err := client.Call(method.ChainID, pmap(), &res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultChainId), nil +} + +func GenPrivAccount(client RPCClient) (*rpc.ResultGeneratePrivateAccount, error) { + res := new(rpc.Result) + _, err := client.Call(method.GeneratePrivateAccount, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGeneratePrivateAccount), nil +} + +func GetAccount(client RPCClient, address acm.Address) (acm.Account, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetAccount, pmap("address", address), res) + if err != nil { + return nil, err + } + concreteAccount := res.Unwrap().(*rpc.ResultGetAccount).Account + if concreteAccount == nil { + return nil, nil + } + return concreteAccount.Account(), nil +} + +func SignTx(client RPCClient, tx txs.Tx, privAccounts []*acm.ConcretePrivateAccount) (txs.Tx, error) { + res := new(rpc.Result) + _, err := client.Call(method.SignTx, pmap("tx", tx, "privAccounts", privAccounts), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultSignTx).Tx, nil +} + +func BroadcastTx(client RPCClient, tx txs.Tx) (*txs.Receipt, error) { + res := new(rpc.Result) + _, err := client.Call(method.BroadcastTx, pmap("tx", txs.Wrap(tx)), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultBroadcastTx).Receipt, nil +} + +func DumpStorage(client RPCClient, address acm.Address) (*rpc.ResultDumpStorage, error) { + res := new(rpc.Result) + _, err := client.Call(method.DumpStorage, pmap("address", address), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultDumpStorage), nil +} + +func GetStorage(client RPCClient, address acm.Address, key []byte) ([]byte, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetStorage, pmap("address", address, "key", key), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGetStorage).Value, nil +} + +func CallCode(client RPCClient, fromAddress acm.Address, code, data []byte) (*rpc.ResultCall, error) { + res := new(rpc.Result) + _, err := client.Call(method.CallCode, pmap("fromAddress", fromAddress, "code", code, "data", data), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultCall), nil +} + +func Call(client RPCClient, fromAddress, toAddress acm.Address, data []byte) (*rpc.ResultCall, error) { + res := new(rpc.Result) + _, err := client.Call(method.Call, pmap("fromAddress", fromAddress, "toAddress", toAddress, + "data", data), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultCall), nil +} + +func GetName(client RPCClient, name string) (*execution.NameRegEntry, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetName, pmap("name", name), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGetName).Entry, nil +} + +func BlockchainInfo(client RPCClient, minHeight, maxHeight int) (*rpc.ResultBlockchainInfo, error) { + res := new(rpc.Result) + _, err := client.Call(method.Blockchain, pmap("minHeight", minHeight, "maxHeight", maxHeight), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultBlockchainInfo), nil +} + +func GetBlock(client RPCClient, height int) (*rpc.ResultGetBlock, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetBlock, pmap("height", height), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGetBlock), nil +} + +func ListUnconfirmedTxs(client RPCClient, maxTxs int) (*rpc.ResultListUnconfirmedTxs, error) { + res := new(rpc.Result) + _, err := client.Call(method.ListUnconfirmedTxs, pmap("maxTxs", maxTxs), res) + if err != nil { + return nil, err + } + resCon := res.Unwrap().(*rpc.ResultListUnconfirmedTxs) + return resCon, nil +} + +func ListValidators(client RPCClient) (*rpc.ResultListValidators, error) { + res := new(rpc.Result) + _, err := client.Call(method.ListValidators, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultListValidators), nil +} + +func DumpConsensusState(client RPCClient) (*rpc.ResultDumpConsensusState, error) { + res := new(rpc.Result) + _, err := client.Call(method.DumpConsensusState, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultDumpConsensusState), nil +} + +func pmap(keyvals ...interface{}) map[string]interface{} { + pm, err := paramsMap(keyvals...) + if err != nil { + panic(err) + } + return pm +} + +func paramsMap(orderedKeyVals ...interface{}) (map[string]interface{}, error) { + if len(orderedKeyVals)%2 != 0 { + return nil, fmt.Errorf("mapAndValues requires a even length list of"+ + " keys and values but got: %v (length %v)", + orderedKeyVals, len(orderedKeyVals)) + } + paramsMap := make(map[string]interface{}) + for i := 0; i < len(orderedKeyVals); i += 2 { + key, ok := orderedKeyVals[i].(string) + if !ok { + return nil, errors.New("mapAndValues requires every even element" + + " of orderedKeyVals to be a string key") + } + val := orderedKeyVals[i+1] + paramsMap[key] = val + } + return paramsMap, nil +} diff --git a/rpc/tendermint/test/rpc_client_test.go b/rpc/tm/client/client_test.go similarity index 66% rename from rpc/tendermint/test/rpc_client_test.go rename to rpc/tm/client/client_test.go index 3f7da59afb790e358e2d0dda423ae7e2291067cc..fb3e37a9ff0de2031b2391e54d8f7c6e641b8b23 100644 --- a/rpc/tendermint/test/rpc_client_test.go +++ b/rpc/tm/client/client_test.go @@ -1,6 +1,3 @@ -// +build integration - -// Space above here matters // Copyright 2017 Monax Industries Limited // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,23 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "bytes" - "fmt" "testing" "time" - "golang.org/x/crypto/ripemd160" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - burrow_client "github.com/hyperledger/burrow/rpc/tendermint/client" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" - "github.com/stretchr/testify/assert" - _ "github.com/tendermint/tendermint/config/tendermint_test" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/consensus/types" + tm_types "github.com/tendermint/tendermint/types" + "golang.org/x/crypto/ripemd160" ) // When run with `-test.short` we only run: @@ -40,8 +36,7 @@ import ( // Note: the reason that we have tests implemented in tests.go is I believe // due to weirdness with go-wire's interface registration, and those global // registrations not being available within a *_test.go runtime context. -func testWithAllClients(t *testing.T, - testFunction func(*testing.T, string, burrow_client.RPCClient)) { +func testWithAllClients(t *testing.T, testFunction func(*testing.T, string, RPCClient)) { for clientName, client := range clients { testFunction(t, clientName, client) } @@ -49,28 +44,26 @@ func testWithAllClients(t *testing.T, //-------------------------------------------------------------------------------- func TestStatus(t *testing.T) { - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - resp, err := burrow_client.Status(client) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + resp, err := Status(client) assert.NoError(t, err) - fmt.Println(resp) - if resp.NodeInfo.Network != chainID { - t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s", - resp.NodeInfo.Network, chainID)) - } + assert.Equal(t, genesisDoc.ChainID(), resp.NodeInfo.Network, "ChainID should match NodeInfo.Network") }) } func TestBroadcastTx(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { // Avoid duplicate Tx in mempool amt := hashString(clientName) % 1000 - toAddr := users[1].Address + toAddr := privateAccounts[1].Address() tx := makeDefaultSendTxSigned(t, client, toAddr, amt) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) - assert.NoError(t, err) - if receipt.CreatesContract > 0 { - t.Fatal("This tx does not create a contract") + if err != nil { + t.Fatal(err) + } + if receipt.CreatesContract { + t.Fatal("This tx should not create a contract") } if len(receipt.TxHash) == 0 { t.Fatal("Failed to compute tx hash") @@ -78,7 +71,7 @@ func TestBroadcastTx(t *testing.T) { n, errp := new(int), new(error) buf := new(bytes.Buffer) hasher := ripemd160.New() - tx.WriteSignBytes(chainID, buf, n, errp) + tx.WriteSignBytes(genesisDoc.ChainID(), buf, n, errp) assert.NoError(t, *errp) txSignBytes := buf.Bytes() hasher.Write(txSignBytes) @@ -95,14 +88,14 @@ func TestGetAccount(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - acc := getAccount(t, client, users[0].Address) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + acc := getAccount(t, client, privateAccounts[0].Address()) if acc == nil { t.Fatal("Account was nil") } - if bytes.Compare(acc.Address, users[0].Address) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, - users[0].Address) + if acc.Address() != privateAccounts[0].Address() { + t.Fatalf("Failed to get correct account. Got %s, expected %s", acc.Address(), + privateAccounts[0].Address()) } }) } @@ -112,23 +105,21 @@ func TestGetStorage(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - defer func() { - wsc.Stop() - }() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - eid := txs.EventStringNewBlock() + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + eid := tm_types.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) }() - amt, gasLim, fee := int64(1100), int64(1000), int64(1000) + amt, gasLim, fee := uint64(1100), uint64(1000), uint64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} // Call with nil address will create a contract tx := makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.NoError(t, err) - assert.Equal(t, uint8(1), receipt.CreatesContract, "This transaction should"+ + assert.Equal(t, true, receipt.CreatesContract, "This transaction should"+ " create a contract") assert.NotEqual(t, 0, len(receipt.TxHash), "Receipt should contain a"+ " transaction hash") @@ -137,8 +128,8 @@ func TestGetStorage(t *testing.T) { " created a contract but the contract address is empty") v := getStorage(t, client, contractAddr, []byte{0x1}) - got := word256.LeftPadWord256(v) - expected := word256.LeftPadWord256([]byte{0x5}) + got := binary.LeftPadWord256(v) + expected := binary.LeftPadWord256([]byte{0x5}) if got.Compare(expected) != 0 { t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes()) @@ -151,21 +142,21 @@ func TestCallCode(t *testing.T) { t.Skip("skipping test in short mode.") } - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { // 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, users[0].PubKey.Address(), code, data, expected) + callCode(t, client, privateAccounts[0].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(word256.LeftPadWord256([]byte{0x5}).Bytes(), - word256.LeftPadWord256([]byte{0x6}).Bytes()...) + data = append(binary.LeftPadWord256([]byte{0x5}).Bytes(), + binary.LeftPadWord256([]byte{0x6}).Bytes()...) expected = []byte{0xb} - callCode(t, client, users[0].PubKey.Address(), code, data, expected) + callCode(t, client, privateAccounts[0].Address(), code, data, expected) }) } @@ -174,18 +165,16 @@ func TestCallContract(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - defer func() { - wsc.Stop() - }() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - eid := txs.EventStringNewBlock() + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + eid := tm_types.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) }() // create the contract - amt, gasLim, fee := int64(6969), int64(1000), int64(1000) + amt, gasLim, fee := uint64(6969), uint64(1000), uint64(1000) code, _, _ := simpleContract() tx := makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) @@ -193,7 +182,7 @@ func TestCallContract(t *testing.T) { if err != nil { t.Fatalf("Problem broadcasting transaction: %v", err) } - assert.Equal(t, uint8(1), receipt.CreatesContract, "This transaction should"+ + assert.Equal(t, true, receipt.CreatesContract, "This transaction should"+ " create a contract") assert.NotEqual(t, 0, len(receipt.TxHash), "Receipt should contain a"+ " transaction hash") @@ -204,7 +193,7 @@ func TestCallContract(t *testing.T) { // run a call through the contract data := []byte{} expected := []byte{0xb} - callContract(t, client, users[0].PubKey.Address(), contractAddr, data, expected) + callContract(t, client, privateAccounts[0].Address(), contractAddr, data, expected) }) } @@ -213,26 +202,27 @@ func TestNameReg(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { txs.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 client name := "ye_old_domain_name_" + clientName const data = "if not now, when" - fee := int64(1000) - numDesiredBlocks := int64(2) + fee := uint64(1000) + numDesiredBlocks := uint64(2) amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) tx := makeDefaultNameTx(t, client, name, data, amt, fee) // verify the name by both using the event and by checking get_name - subscribeAndWaitForNext(t, wsc, txs.EventStringNameReg(name), + subscribeAndWaitForNext(t, wsc, exe_events.EventStringNameReg(name), func() { broadcastTxAndWaitForBlock(t, client, wsc, tx) }, - func(eid string, eventData txs.EventData) (bool, error) { - eventDataTx := asEventDataTx(t, eventData) + func(eid string, eventData event.AnyEventData) (bool, error) { + eventDataTx := eventData.EventDataTx() + assert.NotNil(t, eventDataTx, "could not convert %s to EventDataTx", eventData) tx, ok := eventDataTx.Tx.(*txs.NameTx) if !ok { t.Fatalf("Could not convert %v to *NameTx", eventDataTx) @@ -241,14 +231,13 @@ func TestNameReg(t *testing.T) { assert.Equal(t, data, tx.Data) return true, nil }) - mempoolCount = 0 entry := getNameRegEntry(t, client, name) assert.Equal(t, data, entry.Data) - assert.Equal(t, users[0].Address, entry.Owner) + assert.Equal(t, privateAccounts[0].Address(), entry.Owner) // update the data as the owner, make sure still there - numDesiredBlocks = int64(5) + numDesiredBlocks = uint64(5) const updatedData = "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" @@ -256,15 +245,14 @@ func TestNameReg(t *testing.T) { txs.NameBlockCostMultiplier*txs.NameBaseCost(name, updatedData) tx = makeDefaultNameTx(t, client, name, updatedData, amt, fee) broadcastTxAndWaitForBlock(t, client, wsc, tx) - mempoolCount = 0 entry = getNameRegEntry(t, client, name) assert.Equal(t, updatedData, entry.Data) // try to update as non owner, should fail - tx = txs.NewNameTxWithNonce(users[1].PubKey, name, "never mind", amt, fee, - getNonce(t, client, users[1].Address)+1) - tx.Sign(chainID, users[1]) + tx = txs.NewNameTxWithSequence(privateAccounts[1].PublicKey(), name, "never mind", amt, fee, + getSequence(t, client, privateAccounts[1].Address())+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[1]) _, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.Error(t, err, "Expected error when updating someone else's unexpired"+ @@ -279,37 +267,44 @@ func TestNameReg(t *testing.T) { //now the entry should be expired, so we can update as non owner const data2 = "this is not my beautiful house" - tx = txs.NewNameTxWithNonce(users[1].PubKey, name, data2, amt, fee, - getNonce(t, client, users[1].Address)+1) - tx.Sign(chainID, users[1]) + tx = txs.NewNameTxWithSequence(privateAccounts[1].PublicKey(), name, data2, amt, fee, + getSequence(t, client, privateAccounts[1].Address())+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[1]) _, err = broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.NoError(t, err, "Should be able to update a previously expired name"+ " registry entry as a different address") - mempoolCount = 0 entry = getNameRegEntry(t, client, name) assert.Equal(t, data2, entry.Data) - assert.Equal(t, users[1].Address, entry.Owner) + assert.Equal(t, privateAccounts[1].Address(), entry.Owner) }) } +func TestWaitBlocks(t *testing.T) { + wsc := newWSClient() + defer stopWSClient(wsc) + waitNBlocks(t, wsc, 5) +} + func TestBlockchainInfo(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { // wait a mimimal number of blocks to ensure that the later query for block // headers has a non-trivial length nBlocks := 4 waitNBlocks(t, wsc, nBlocks) - resp, err := burrow_client.BlockchainInfo(client, 0, 0) + resp, err := BlockchainInfo(client, 0, 0) if err != nil { t.Fatalf("Failed to get blockchain info: %v", err) } lastBlockHeight := resp.LastHeight nMetaBlocks := len(resp.BlockMetas) - assert.True(t, nMetaBlocks <= lastBlockHeight, + assert.True(t, uint64(nMetaBlocks) <= lastBlockHeight, "Logically number of block metas should be equal or less than block height.") assert.True(t, nBlocks <= len(resp.BlockMetas), - "Should see at least 4 BlockMetas after waiting for 4 blocks") + "Should see at least %v BlockMetas after waiting for %v blocks but saw %v", + nBlocks, nBlocks, len(resp.BlockMetas)) // For the maximum number (default to 20) of retrieved block headers, // check that they correctly chain to each other. lastBlockHash := resp.BlockMetas[nMetaBlocks-1].Header.Hash() @@ -323,7 +318,7 @@ func TestBlockchainInfo(t *testing.T) { // Now retrieve only two blockheaders (h=1, and h=2) and check that we got // two results. - resp, err = burrow_client.BlockchainInfo(client, 1, 2) + resp, err = BlockchainInfo(client, 1, 2) assert.NoError(t, err) assert.Equal(t, 2, len(resp.BlockMetas), "Should see 2 BlockMetas after extracting 2 blocks") @@ -335,12 +330,13 @@ func TestListUnconfirmedTxs(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - amt, gasLim, fee := int64(1100), int64(1000), int64(1000) + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + amt, gasLim, fee := uint64(1100), uint64(1000), uint64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} // Call with nil address will create a contract - tx := makeDefaultCallTx(t, client, []byte{}, code, amt, gasLim, fee) - txChan := make(chan []txs.Tx) + tx := txs.Wrap(makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee)) + txChan := make(chan []txs.Wrapper) // We want to catch the Tx in mempool before it gets reaped by tendermint // consensus. We should be able to do this almost always if we broadcast our @@ -351,8 +347,11 @@ func TestListUnconfirmedTxs(t *testing.T) { go func() { for { - resp, err := burrow_client.ListUnconfirmedTxs(client) - assert.NoError(t, err) + resp, err := ListUnconfirmedTxs(client, -1) + if resp != nil { + + } + require.NoError(t, err) if resp.N > 0 { txChan <- resp.Txs } @@ -362,14 +361,12 @@ func TestListUnconfirmedTxs(t *testing.T) { runThenWaitForBlock(t, wsc, nextBlockPredicateFn(), func() { broadcastTx(t, client, tx) select { - case <-time.After(time.Second * timeoutSeconds): + case <-time.After(time.Second * timeoutSeconds * 10): t.Fatal("Timeout out waiting for unconfirmed transactions to appear") case transactions := <-txChan: - assert.Len(t, transactions, 1, - "There should only be a single transaction in the mempool during "+ - "this test (previous txs should have made it into a block)") - assert.Contains(t, transactions, tx, - "Transaction should be returned by ListUnconfirmedTxs") + assert.Len(t, transactions, 1, "There should only be a single transaction in the "+ + "mempool during this test (previous txs should have made it into a block)") + assert.Contains(t, transactions, tx, "Transaction should be returned by ListUnconfirmedTxs") } }) }) @@ -377,9 +374,9 @@ func TestListUnconfirmedTxs(t *testing.T) { func TestGetBlock(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := burrow_client.GetBlock(client, 2) + resp, err := GetBlock(client, 2) assert.NoError(t, err) assert.Equal(t, 2, resp.Block.Height) assert.Equal(t, 2, resp.BlockMeta.Header.Height) @@ -388,38 +385,54 @@ func TestGetBlock(t *testing.T) { func TestListValidators(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := burrow_client.ListValidators(client) + resp, err := ListValidators(client) assert.NoError(t, err) assert.Len(t, resp.BondedValidators, 1) - validator := resp.BondedValidators[0].(*consensus_types.TendermintValidator) - assert.Equal(t, genesisDoc.Validators[0].PubKey, validator.PubKey) + validator := resp.BondedValidators[0] + assert.Equal(t, genesisDoc.Validators[0].PublicKey, validator.PublicKey) }) } func TestDumpConsensusState(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + startTime := time.Now() + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := burrow_client.DumpConsensusState(client) + resp, err := DumpConsensusState(client) assert.NoError(t, err) - startTime := resp.ConsensusState.StartTime - // TODO: uncomment lines involving commitTime when - // https://github.com/tendermint/tendermint/issues/277 is fixed in Tendermint - //commitTime := resp.ConsensusState.CommitTime + commitTime := resp.RoundState.CommitTime assert.NotZero(t, startTime) - //assert.NotZero(t, commitTime) - //assert.True(t, commitTime.Unix() > startTime.Unix(), - // "Commit time %v should be later than start time %v", commitTime, startTime) - assert.Equal(t, uint8(1), resp.ConsensusState.Step) + assert.NotZero(t, commitTime) + assert.True(t, commitTime.Unix() > startTime.Unix(), + "Commit time %v should be later than start time %v", commitTime, startTime) + assert.Equal(t, types.RoundStepNewHeight, resp.RoundState.Step) }) } -func asEventDataTx(t *testing.T, eventData txs.EventData) txs.EventDataTx { - eventDataTx, ok := eventData.(txs.EventDataTx) - if !ok { - t.Fatalf("Expected eventData to be EventDataTx was %v", eventData) +func TestParamsMap(t *testing.T) { + type aStruct struct { + Baz int } - return eventDataTx + dict, err := paramsMap("Foo", aStruct{5}, + "Bar", "Nibbles") + assert.NoError(t, err, "Should not be a paramsMaperror") + assert.Equal(t, map[string]interface{}{ + "Foo": aStruct{5}, + "Bar": "Nibbles", + }, dict) + + // Empty map + dict, err = paramsMap() + assert.Equal(t, map[string]interface{}{}, dict) + assert.NoError(t, err, "Empty mapsAndValues call should be fine") + + // Invalid maps + assert.NoError(t, err, "Empty mapsAndValues call should be fine") + _, err = paramsMap("Foo", 4, "Bar") + assert.Error(t, err, "Should be an error to get an odd number of arguments") + + _, err = paramsMap("Foo", 4, 4, "Bar") + assert.Error(t, err, "Should be an error to provide non-string keys") } diff --git a/rpc/tm/client/shared.go b/rpc/tm/client/shared.go new file mode 100644 index 0000000000000000000000000000000000000000..c0b946f2edbef52f39f50c8c70e06f403f5d1f07 --- /dev/null +++ b/rpc/tm/client/shared.go @@ -0,0 +1,308 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "bytes" + "fmt" + "hash/fnv" + "strconv" + "testing" + + "os" + + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/consensus/tendermint/validator" + "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/require" + tm_config "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/rpc/lib/client" +) + +const ( + chainName = "RPC_Test_Chain" + rpcAddr = "0.0.0.0:46657" + websocketAddr = rpcAddr + websocketEndpoint = "/websocket" + testDir = "./test_scratch/tm_test" +) + +// global variables for use across all tests +var ( + privateAccounts = makePrivateAccounts(5) // make keys + jsonRpcClient = rpcclient.NewJSONRPCClient(rpcAddr) + httpClient = rpcclient.NewURIClient(rpcAddr) + clients = map[string]RPCClient{ + "JSONRPC": jsonRpcClient, + "HTTP": httpClient, + } + // Initialised in initGlobalVariables + genesisDoc = new(genesis.GenesisDoc) + kernel = new(core.Kernel) +) + +// We use this to wrap tests +func TestWrapper(runner func() int) int { + fmt.Println("Running with integration TestWrapper (rpc/tendermint/test/shared_test.go)...") + + err := initGlobalVariables() + if err != nil { + panic(err) + } + + err = kernel.Boot() + if err != nil { + panic(err) + } + + defer kernel.Shutdown() + + return runner() +} + +func initGlobalVariables() error { + var err error + os.RemoveAll(testDir) + os.MkdirAll(testDir, 0777) + os.Chdir(testDir) + tmConf := tm_config.DefaultConfig() + //logger, _ := lifecycle.NewStdErrLogger() + logger := loggers.NewNoopInfoTraceLogger() + privValidator := validator.NewPrivValidatorMemory(privateAccounts[0], privateAccounts[0]) + genesisDoc = testGenesisDoc() + kernel, err = core.NewKernel(privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), logger) + return err +} + +func testGenesisDoc() *genesis.GenesisDoc { + accounts := make(map[string]acm.Account, len(privateAccounts)) + for i, pa := range privateAccounts { + account := acm.FromAddressable(pa) + account.AddToBalance(1 << 32) + account.SetPermissions(permission.AllAccountPermissions.Clone()) + accounts[fmt.Sprintf("user_%v", i)] = account + } + genesisTime, err := time.Parse("02-01-2006", "27-10-2017") + if err != nil { + panic("could not parse test genesis time") + } + return genesis.MakeGenesisDocFromAccounts(chainName, nil, genesisTime, accounts, + map[string]acm.Validator{ + "genesis_validator": acm.AsValidator(accounts["user_0"]), + }) +} + +// Deterministic account generation helper. Pass number of accounts to make +func makePrivateAccounts(n int) []acm.PrivateAccount { + accounts := make([]acm.PrivateAccount, n) + for i := 0; i < n; i++ { + accounts[i] = acm.GeneratePrivateAccountFromSecret("mysecret" + strconv.Itoa(i)) + } + return accounts +} + +//------------------------------------------------------------------------------- +// some default transaction functions + +func makeDefaultSendTx(t *testing.T, client RPCClient, addr acm.Address, amt uint64) *txs.SendTx { + sequence := getSequence(t, client, privateAccounts[0].Address()) + tx := txs.NewSendTx() + tx.AddInputWithSequence(privateAccounts[0].PublicKey(), amt, sequence+1) + tx.AddOutput(addr, amt) + return tx +} + +func makeDefaultSendTxSigned(t *testing.T, client RPCClient, addr acm.Address, amt uint64) *txs.SendTx { + tx := makeDefaultSendTx(t, client, addr, amt) + tx.SignInput(genesisDoc.ChainID(), 0, privateAccounts[0]) + return tx +} + +func makeDefaultCallTx(t *testing.T, client RPCClient, addr *acm.Address, code []byte, amt, gasLim, + fee uint64) *txs.CallTx { + sequence := getSequence(t, client, privateAccounts[0].Address()) + tx := txs.NewCallTxWithSequence(privateAccounts[0].PublicKey(), addr, code, amt, gasLim, fee, + sequence+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + return tx +} + +func makeDefaultNameTx(t *testing.T, client RPCClient, name, value string, amt, fee uint64) *txs.NameTx { + sequence := getSequence(t, client, privateAccounts[0].Address()) + tx := txs.NewNameTxWithSequence(privateAccounts[0].PublicKey(), name, value, amt, fee, sequence+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + return tx +} + +//------------------------------------------------------------------------------- +// rpc call wrappers (fail on err) + +// get an account's sequence number +func getSequence(t *testing.T, client RPCClient, addr acm.Address) uint64 { + acc, err := GetAccount(client, addr) + if err != nil { + t.Fatal(err) + } + if acc == nil { + return 0 + } + return acc.Sequence() +} + +// get the account +func getAccount(t *testing.T, client RPCClient, addr acm.Address) acm.Account { + ac, err := GetAccount(client, addr) + if err != nil { + t.Fatal(err) + } + return ac +} + +// sign transaction +func signTx(t *testing.T, client RPCClient, tx txs.Tx, + privAcc *acm.ConcretePrivateAccount) txs.Tx { + signedTx, err := SignTx(client, tx, []*acm.ConcretePrivateAccount{privAcc}) + if err != nil { + t.Fatal(err) + } + return signedTx +} + +// broadcast transaction +func broadcastTx(t *testing.T, client RPCClient, tx txs.Tx) *txs.Receipt { + rec, err := BroadcastTx(client, tx) + require.NoError(t, err) + return rec +} + +// dump all storage for an account. currently unused +func dumpStorage(t *testing.T, addr acm.Address) *rpc.ResultDumpStorage { + client := clients["HTTP"] + resp, err := DumpStorage(client, addr) + if err != nil { + t.Fatal(err) + } + return resp +} + +func getStorage(t *testing.T, client RPCClient, addr acm.Address, key []byte) []byte { + resp, err := GetStorage(client, addr, key) + if err != nil { + t.Fatal(err) + } + return resp +} + +func callCode(t *testing.T, client RPCClient, fromAddress acm.Address, code, data, + expected []byte) { + resp, err := CallCode(client, 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, binary.LeftPadWord256(expected).Bytes()) != 0 { + t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) + } +} + +func callContract(t *testing.T, client RPCClient, fromAddress, toAddress acm.Address, + data, expected []byte) { + resp, err := Call(client, 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, binary.LeftPadWord256(expected).Bytes()) != 0 { + t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) + } +} + +// get the namereg entry +func getNameRegEntry(t *testing.T, client RPCClient, name string) *execution.NameRegEntry { + entry, err := GetName(client, name) + if err != nil { + t.Fatal(err) + } + return entry +} + +// Returns a positive int64 hash of text (consumers want int64 instead of uint64) +func hashString(text string) uint64 { + hasher := fnv.New64() + hasher.Write([]byte(text)) + return uint64(hasher.Sum64()) +} + +//-------------------------------------------------------------------------------- +// utility verification function + +// 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}, + binary.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, binary.LeftPadBytes([]byte{0xb}, 32) +} + +// simple call contract calls another contract +func simpleCallContract(addr acm.Address) ([]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.Bytes()...) + 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, binary.LeftPadBytes([]byte{0xb}, 32) +} diff --git a/rpc/tendermint/test/shared_test.go b/rpc/tm/client/shared_test.go similarity index 92% rename from rpc/tendermint/test/shared_test.go rename to rpc/tm/client/shared_test.go index 5b2a1455c161aa75320800e5c89180554b91e1f5..46c939cf89dd282fa278cbec3ed83512ee3a3a39 100644 --- a/rpc/tendermint/test/shared_test.go +++ b/rpc/tm/client/shared_test.go @@ -1,6 +1,3 @@ -// +build integration - -// Space above here matters // Copyright 2017 Monax Industries Limited // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "os" diff --git a/rpc/tm/client/websocket_client.go b/rpc/tm/client/websocket_client.go new file mode 100644 index 0000000000000000000000000000000000000000..d20eeb737395b377b40eb63adb391e9872c71fe2 --- /dev/null +++ b/rpc/tm/client/websocket_client.go @@ -0,0 +1,47 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "context" + "fmt" + + "github.com/tendermint/tendermint/rpc/lib/types" +) + +type WebsocketClient interface { + Send(ctx context.Context, request rpctypes.RPCRequest) error +} + +func Subscribe(wsc WebsocketClient, eventId string) error { + req, err := rpctypes.MapToRequest(fmt.Sprintf("wsclient_subscribe?eventId=%s", eventId), + "subscribe", map[string]interface{}{"eventId": eventId}) + if err != nil { + return err + } + return wsc.Send(context.Background(), req) + + //return wsc.Call(context.Background(), "subscribe", + // map[string]interface{}{"eventId": eventId}) +} + +func Unsubscribe(websocketClient WebsocketClient, subscriptionId string) error { + req, err := rpctypes.MapToRequest(fmt.Sprintf("wsclient_unsubscribe?subId=%s", subscriptionId), + "unsubscribe", map[string]interface{}{"subscriptionId": subscriptionId}) + if err != nil { + return err + } + return websocketClient.Send(context.Background(), req) +} diff --git a/rpc/tendermint/test/websocket_client_test.go b/rpc/tm/client/websocket_client_test.go similarity index 66% rename from rpc/tendermint/test/websocket_client_test.go rename to rpc/tm/client/websocket_client_test.go index 4bb2609269bfdbb85978cc15f23fbea6d2292f3b..d5ce845a72e60a17b1987b2755137e9aaea9a5dc 100644 --- a/rpc/tendermint/test/websocket_client_test.go +++ b/rpc/tm/client/websocket_client_test.go @@ -1,6 +1,3 @@ -// +build integration - -// Space above here matters // Copyright 2017 Monax Industries Limited // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,18 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "fmt" "testing" - "time" - core_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/txs" "github.com/stretchr/testify/assert" - _ "github.com/tendermint/tendermint/config/tendermint_test" + "github.com/stretchr/testify/require" + tm_types "github.com/tendermint/tendermint/types" ) //-------------------------------------------------------------------------------- @@ -35,21 +36,21 @@ import ( // make a simple connection to the server func TestWSConnect(t *testing.T) { wsc := newWSClient() - wsc.Stop() + stopWSClient(wsc) } // receive a new block message func TestWSNewBlock(t *testing.T) { wsc := newWSClient() - eid := txs.EventStringNewBlock() + eid := tm_types.EventStringNewBlock() subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) - wsc.Stop() + stopWSClient(wsc) }() waitForEvent(t, wsc, eid, func() {}, - func(eid string, eventData txs.EventData) (bool, error) { - fmt.Println("Check: ", eventData.(txs.EventDataNewBlock).Block) + func(eid string, eventData event.AnyEventData) (bool, error) { + fmt.Println("Check: ", eventData.EventDataNewBlock().Block) return true, nil }) } @@ -60,19 +61,19 @@ func TestWSBlockchainGrowth(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid := txs.EventStringNewBlock() + eid := tm_types.EventStringNewBlock() subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) - wsc.Stop() + stopWSClient(wsc) }() // listen for NewBlock, ensure height increases by 1 var initBlockN int for i := 0; i < 2; i++ { waitForEvent(t, wsc, eid, func() {}, - func(eid string, eventData txs.EventData) (bool, error) { - eventDataNewBlock, ok := eventData.(txs.EventDataNewBlock) - if !ok { + func(eid string, eventData event.AnyEventData) (bool, error) { + eventDataNewBlock := eventData.EventDataNewBlock() + if eventDataNewBlock == nil { t.Fatalf("Was expecting EventDataNewBlock but got %v", eventData) } block := eventDataNewBlock.Block @@ -93,16 +94,16 @@ func TestWSBlockchainGrowth(t *testing.T) { // send a transaction and validate the events from listening for both sender and receiver func TestWSSend(t *testing.T) { wsc := newWSClient() - toAddr := users[1].Address - amt := int64(100) - eidInput := txs.EventStringAccInput(users[0].Address) - eidOutput := txs.EventStringAccOutput(toAddr) + toAddr := privateAccounts[1].Address() + amt := uint64(100) + eidInput := exe_events.EventStringAccInput(privateAccounts[0].Address()) + eidOutput := exe_events.EventStringAccOutput(toAddr) subIdInput := subscribeAndGetSubscriptionId(t, wsc, eidInput) subIdOutput := subscribeAndGetSubscriptionId(t, wsc, eidOutput) defer func() { unsubscribe(t, wsc, subIdInput) unsubscribe(t, wsc, subIdOutput) - wsc.Stop() + stopWSClient(wsc) }() waitForEvent(t, wsc, eidInput, func() { tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) @@ -119,25 +120,25 @@ func TestWSDoubleFire(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid := txs.EventStringAccInput(users[0].Address) + eid := exe_events.EventStringAccInput(privateAccounts[0].Address()) subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) - wsc.Stop() + stopWSClient(wsc) }() - amt := int64(100) - toAddr := users[1].Address + amt := uint64(100) + toAddr := privateAccounts[1].Address() // broadcast the transaction, wait to hear about it waitForEvent(t, wsc, eid, func() { tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) broadcastTx(t, jsonRpcClient, tx) - }, func(eid string, b txs.EventData) (bool, error) { + }, func(eid string, b event.AnyEventData) (bool, error) { return true, nil }) // but make sure we don't hear about it twice err := waitForEvent(t, wsc, eid, func() {}, - func(eid string, b txs.EventData) (bool, error) { + func(eid string, b event.AnyEventData) (bool, error) { return false, nil }) assert.True(t, err.Timeout(), "We should have timed out waiting for second"+ @@ -150,15 +151,15 @@ func TestWSCallWait(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid1 := txs.EventStringAccInput(users[0].Address) + eid1 := exe_events.EventStringAccInput(privateAccounts[0].Address()) subId1 := subscribeAndGetSubscriptionId(t, wsc, eid1) defer func() { unsubscribe(t, wsc, subId1) - wsc.Stop() + stopWSClient(wsc) }() - amt, gasLim, fee := int64(10000), int64(1000), int64(1000) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, returnCode, returnVal := simpleContract() - var contractAddr []byte + var contractAddr acm.Address // wait for the contract to be created waitForEvent(t, wsc, eid1, func() { tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) @@ -167,8 +168,8 @@ func TestWSCallWait(t *testing.T) { }, unmarshalValidateTx(amt, returnCode)) // susbscribe to the new contract - amt = int64(10001) - eid2 := txs.EventStringAccOutput(contractAddr) + amt = uint64(10001) + eid2 := exe_events.EventStringAccOutput(contractAddr) subId2 := subscribeAndGetSubscriptionId(t, wsc, eid2) defer func() { unsubscribe(t, wsc, subId2) @@ -176,7 +177,7 @@ func TestWSCallWait(t *testing.T) { // get the return value from a call data := []byte{0x1} waitForEvent(t, wsc, eid2, func() { - tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr, data, amt, gasLim, fee) + tx := makeDefaultCallTx(t, jsonRpcClient, &contractAddr, data, amt, gasLim, fee) receipt := broadcastTx(t, jsonRpcClient, tx) contractAddr = receipt.ContractAddr }, unmarshalValidateTx(amt, returnVal)) @@ -189,25 +190,24 @@ func TestWSCallNoWait(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - amt, gasLim, fee := int64(10000), int64(1000), int64(1000) + defer stopWSClient(wsc) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, _, returnVal := simpleContract() tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, jsonRpcClient, tx) + receipt, err := broadcastTxAndWaitForBlock(t, jsonRpcClient, wsc, tx) + require.NoError(t, err) contractAddr := receipt.ContractAddr // susbscribe to the new contract - amt = int64(10001) - eid := txs.EventStringAccOutput(contractAddr) + amt = uint64(10001) + eid := exe_events.EventStringAccOutput(contractAddr) subId := subscribeAndGetSubscriptionId(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, subId) - wsc.Stop() - }() + defer unsubscribe(t, wsc, subId) // get the return value from a call data := []byte{0x1} waitForEvent(t, wsc, eid, func() { - tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr, data, amt, gasLim, fee) + tx := makeDefaultCallTx(t, jsonRpcClient, &contractAddr, data, amt, gasLim, fee) broadcastTx(t, jsonRpcClient, tx) }, unmarshalValidateTx(amt, returnVal)) } @@ -218,47 +218,52 @@ func TestWSCallCall(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - amt, gasLim, fee := int64(10000), int64(1000), int64(1000) + defer stopWSClient(wsc) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, _, returnVal := simpleContract() txid := new([]byte) // deploy the two contracts tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, jsonRpcClient, tx) + receipt, err := broadcastTxAndWaitForBlock(t, jsonRpcClient, wsc, tx) + require.NoError(t, err) contractAddr1 := receipt.ContractAddr + // subscribe to the new contracts + eid := evm_events.EventStringAccCall(contractAddr1) + subId := subscribeAndGetSubscriptionId(t, wsc, eid) + defer unsubscribe(t, wsc, subId) + // call contract2, which should call contract1, and wait for ev1 code, _, _ = simpleCallContract(contractAddr1) tx = makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) receipt = broadcastTx(t, jsonRpcClient, tx) contractAddr2 := receipt.ContractAddr - // subscribe to the new contracts - amt = int64(10001) - eid := txs.EventStringAccCall(contractAddr1) - subId := subscribeAndGetSubscriptionId(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, subId) - wsc.Stop() - }() - // call contract2, which should call contract1, and wait for ev1 - // let the contract get created first - waitForEvent(t, wsc, eid, func() { - }, func(eid string, b txs.EventData) (bool, error) { - return true, nil - }) + waitForEvent(t, wsc, eid, + // Runner + func() { + }, + // Event Checker + func(eid string, b event.AnyEventData) (bool, error) { + return true, nil + }) // call it - waitForEvent(t, wsc, eid, func() { - tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr2, nil, amt, gasLim, fee) - broadcastTx(t, jsonRpcClient, tx) - *txid = txs.TxHash(chainID, tx) - }, unmarshalValidateCall(users[0].Address, returnVal, txid)) + waitForEvent(t, wsc, eid, + // Runner + func() { + tx := makeDefaultCallTx(t, jsonRpcClient, &contractAddr2, nil, amt, gasLim, fee) + broadcastTx(t, jsonRpcClient, tx) + *txid = txs.TxHash(genesisDoc.ChainID(), tx) + }, + // Event checker + unmarshalValidateCall(privateAccounts[0].Address(), returnVal, txid)) } func TestSubscribe(t *testing.T) { wsc := newWSClient() var subId string - subscribe(t, wsc, txs.EventStringNewBlock()) + subscribe(t, wsc, tm_types.EventStringNewBlock()) // timeout to check subscription process is live timeout := time.After(timeoutSeconds * time.Second) @@ -268,13 +273,12 @@ Subscribe: case <-timeout: t.Fatal("Timed out waiting for subscription result") - case bs := <-wsc.ResultsCh: - resultSubscribe, ok := readResult(t, bs).(*core_types.ResultSubscribe) - if ok { - assert.Equal(t, txs.EventStringNewBlock(), resultSubscribe.Event) - subId = resultSubscribe.SubscriptionId - break Subscribe - } + case response := <-wsc.ResponsesCh: + require.Nil(t, response.Error) + res := readResult(t, *response.Result).(*rpc.ResultSubscribe) + assert.Equal(t, tm_types.EventStringNewBlock(), res.Event) + subId = res.SubscriptionId + break Subscribe } } @@ -289,11 +293,12 @@ Subscribe: } return - case bs := <-wsc.ResultsCh: - resultEvent, ok := readResult(t, bs).(*core_types.ResultEvent) - if ok { - _, ok := resultEvent.Data.(txs.EventDataNewBlock) - if ok { + case response := <-wsc.ResponsesCh: + require.Nil(t, response.Error) + if res, ok := readResult(t, *response.Result).(*rpc.ResultEvent); ok { + enb := res.EventDataNewBlock() + if enb != nil { + assert.Equal(t, genesisDoc.ChainID(), enb.Block.ChainID) if blocksSeen > 1 { t.Fatal("Continued to see NewBlock event after unsubscribing") } else { diff --git a/rpc/tendermint/test/websocket_helpers.go b/rpc/tm/client/websocket_helpers.go similarity index 56% rename from rpc/tendermint/test/websocket_helpers.go rename to rpc/tm/client/websocket_helpers.go index 1e013b5203dade9e0b76eac80e5f1361b88bac5b..05c0a8831d9a2dbcbdd7eaca9879808f439174a0 100644 --- a/rpc/tendermint/test/websocket_helpers.go +++ b/rpc/tm/client/websocket_helpers.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "bytes" @@ -20,18 +20,20 @@ import ( "testing" "time" - burrow_client "github.com/hyperledger/burrow/rpc/tendermint/client" - ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" + "encoding/json" - rpcclient "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-wire" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/rpc/lib/client" tm_types "github.com/tendermint/tendermint/types" ) const ( timeoutSeconds = 2 - expectBlockInSeconds = timeoutSeconds * 2 + expectBlockInSeconds = 2 ) //-------------------------------------------------------------------------------- @@ -47,16 +49,20 @@ func newWSClient() *rpcclient.WSClient { return wsc } +func stopWSClient(wsc *rpcclient.WSClient) { + wsc.Stop() +} + // subscribe to an event func subscribe(t *testing.T, wsc *rpcclient.WSClient, eventId string) { - if err := burrow_client.Subscribe(wsc, eventId); err != nil { + if err := Subscribe(wsc, eventId); err != nil { t.Fatal(err) } } func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, eventId string) string { - if err := burrow_client.Subscribe(wsc, eventId); err != nil { + if err := Subscribe(wsc, eventId); err != nil { t.Fatal(err) } @@ -65,10 +71,11 @@ func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, select { case <-timeout.C: t.Fatal("Timeout waiting for subscription result") - case bs := <-wsc.ResultsCh: - resultSubscribe, ok := readResult(t, bs).(*ctypes.ResultSubscribe) + case response := <-wsc.ResponsesCh: + require.Nil(t, response.Error, "got error response from websocket channel") + res, ok := readResult(t, *response.Result).(*rpc.ResultSubscribe) if ok { - return resultSubscribe.SubscriptionId + return res.SubscriptionId } } } @@ -76,20 +83,20 @@ func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, // unsubscribe from an event func unsubscribe(t *testing.T, wsc *rpcclient.WSClient, subscriptionId string) { - if err := burrow_client.Unsubscribe(wsc, subscriptionId); err != nil { + if err := Unsubscribe(wsc, subscriptionId); err != nil { t.Fatal(err) } } // broadcast transaction and wait for new block -func broadcastTxAndWaitForBlock(t *testing.T, client burrow_client.RPCClient, - wsc *rpcclient.WSClient, tx txs.Tx) (txs.Receipt, error) { - var rec txs.Receipt +func broadcastTxAndWaitForBlock(t *testing.T, client RPCClient, wsc *rpcclient.WSClient, + tx txs.Tx) (*txs.Receipt, error) { + + var rec *txs.Receipt var err error runThenWaitForBlock(t, wsc, nextBlockPredicateFn(), func() { - rec, err = burrow_client.BroadcastTx(client, tx) - mempoolCount += 1 + rec, err = BroadcastTx(client, tx) }) return rec, err } @@ -121,40 +128,37 @@ func waitNBlocks(t *testing.T, wsc *rpcclient.WSClient, n int) { func() {}) } -func runThenWaitForBlock(t *testing.T, wsc *rpcclient.WSClient, - predicate blockPredicate, runner func()) { - subscribeAndWaitForNext(t, wsc, txs.EventStringNewBlock(), - runner, - func(event string, eventData txs.EventData) (bool, error) { - return predicate(eventData.(txs.EventDataNewBlock).Block), nil - }) +func runThenWaitForBlock(t *testing.T, wsc *rpcclient.WSClient, predicate blockPredicate, runner func()) { + eventDataChecker := func(event string, eventData event.AnyEventData) (bool, error) { + eventDataNewBlock := eventData.EventDataNewBlock() + if eventDataNewBlock == nil { + return false, fmt.Errorf("could not convert %#v to EventDataNewBlock", eventData) + } + return predicate(eventDataNewBlock.Block), nil + } + subscribeAndWaitForNext(t, wsc, tm_types.EventStringNewBlock(), runner, eventDataChecker) } -func subscribeAndWaitForNext(t *testing.T, wsc *rpcclient.WSClient, event string, - runner func(), - eventDataChecker func(string, txs.EventData) (bool, error)) { +func subscribeAndWaitForNext(t *testing.T, wsc *rpcclient.WSClient, event string, runner func(), + eventDataChecker func(string, event.AnyEventData) (bool, error)) { + subId := subscribeAndGetSubscriptionId(t, wsc, event) defer unsubscribe(t, wsc, subId) - waitForEvent(t, - wsc, - event, - runner, - eventDataChecker) + waitForEvent(t, wsc, event, runner, eventDataChecker) } // waitForEvent executes runner that is expected to trigger events. It then // waits for any events on the supplies WSClient and checks the eventData with // the eventDataChecker which is a function that is passed the event name -// and the EventData and returns the pair of stopWaiting, err. Where if +// and the Data and returns the pair of stopWaiting, err. Where if // stopWaiting is true waitForEvent will return or if stopWaiting is false // waitForEvent will keep listening for new events. If an error is returned // waitForEvent will fail the test. -func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, - runner func(), - eventDataChecker func(string, txs.EventData) (bool, error)) waitForEventResult { +func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventID string, runner func(), + eventDataChecker func(string, event.AnyEventData) (bool, error)) waitForEventResult { // go routine to wait for websocket msg - eventsCh := make(chan txs.EventData) + eventsCh := make(chan event.AnyEventData) shutdownEventsCh := make(chan bool, 1) errCh := make(chan error) @@ -163,27 +167,21 @@ func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, // Read message go func() { - var err error LOOP: for { select { case <-shutdownEventsCh: break LOOP - case r := <-wsc.ResultsCh: - result := new(ctypes.BurrowResult) - wire.ReadJSONPtr(result, r, &err) - if err != nil { - errCh <- err + case r := <-wsc.ResponsesCh: + if r.Error != nil { + errCh <- r.Error break LOOP } - event, ok := (*result).(*ctypes.ResultEvent) - if ok && event.Event == eventid { + resultEvent, _ := readResult(t, *r.Result).(*rpc.ResultEvent) + if resultEvent != nil && resultEvent.Event == eventID { // Keep feeding events - eventsCh <- event.Data + eventsCh <- resultEvent.AnyEventData } - case err := <-wsc.ErrorsCh: - errCh <- err - break LOOP case <-wsc.Quit: break LOOP } @@ -200,10 +198,8 @@ func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, return waitForEventResult{timeout: true} case eventData := <-eventsCh: // run the check - stopWaiting, err := eventDataChecker(eventid, eventData) - if err != nil { - t.Fatal(err) // Show the stack trace. - } + stopWaiting, err := eventDataChecker(eventID, eventData) + require.NoError(t, err) if stopWaiting { return waitForEventResult{} } @@ -224,38 +220,44 @@ func (err waitForEventResult) Timeout() bool { //-------------------------------------------------------------------------------- -func unmarshalValidateSend(amt int64, - toAddr []byte) func(string, txs.EventData) (bool, error) { - return func(eid string, eventData txs.EventData) (bool, error) { - var data = eventData.(txs.EventDataTx) +func unmarshalValidateSend(amt uint64, toAddr acm.Address) func(string, event.AnyEventData) (bool, error) { + return func(eid string, eventData event.AnyEventData) (bool, error) { + data := eventData.EventDataTx() + if data == nil { + return true, fmt.Errorf("event data %s is not EventDataTx", eventData) + } if data.Exception != "" { return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.SendTx) - if !bytes.Equal(tx.Inputs[0].Address, users[0].Address) { - return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, users[0].Address) + if tx.Inputs[0].Address != privateAccounts[0].Address() { + return true, fmt.Errorf("senders do not match up! Got %s, expected %s", tx.Inputs[0].Address, + privateAccounts[0].Address) } if tx.Inputs[0].Amount != amt { - return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) + return true, 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 true, fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, users[0].Address) + if tx.Outputs[0].Address != toAddr { + return true, fmt.Errorf("receivers do not match up! Got %s, expected %s", tx.Outputs[0].Address, + privateAccounts[0].Address) } return true, nil } } -func unmarshalValidateTx(amt int64, - returnCode []byte) func(string, txs.EventData) (bool, error) { - return func(eid string, eventData txs.EventData) (bool, error) { - var data = eventData.(txs.EventDataTx) +func unmarshalValidateTx(amt uint64, returnCode []byte) func(string, event.AnyEventData) (bool, error) { + return func(eid string, eventData event.AnyEventData) (bool, error) { + data := eventData.EventDataTx() + if data == nil { + return true, fmt.Errorf("event data %s is not EventDataTx", eventData) + } if data.Exception != "" { return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.CallTx) - if !bytes.Equal(tx.Input.Address, users[0].Address) { + if tx.Input.Address != privateAccounts[0].Address() { return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", - tx.Input.Address, users[0].Address) + tx.Input.Address, privateAccounts[0].Address) } if tx.Input.Amount != amt { return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", @@ -269,20 +271,21 @@ func unmarshalValidateTx(amt int64, } } -func unmarshalValidateCall(origin, - returnCode []byte, txid *[]byte) func(string, txs.EventData) (bool, error) { - return func(eid string, eventData txs.EventData) (bool, error) { - var data = eventData.(txs.EventDataCall) +func unmarshalValidateCall(origin acm.Address, returnCode []byte, txid *[]byte) func(string, event.AnyEventData) (bool, error) { + return func(eid string, eventData event.AnyEventData) (bool, error) { + data := eventData.EventDataCall() + if data == nil { + return true, fmt.Errorf("event data %s is not EventDataTx", eventData) + } if data.Exception != "" { return true, fmt.Errorf(data.Exception) } - if !bytes.Equal(data.Origin, origin) { - return true, fmt.Errorf("Origin does not match up! Got %x, expected %x", - data.Origin, origin) + if data.Origin != origin { + return true, fmt.Errorf("origin does not match up! Got %s, expected %s", data.Origin, origin) } ret := data.Return if !bytes.Equal(ret, returnCode) { - return true, fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnCode) + return true, fmt.Errorf("call did not return correctly. Got %x, expected %x", ret, returnCode) } if !bytes.Equal(data.TxID, *txid) { return true, fmt.Errorf("TxIDs do not match up! Got %x, expected %x", @@ -292,12 +295,11 @@ func unmarshalValidateCall(origin, } } -func readResult(t *testing.T, bs []byte) ctypes.BurrowResult { - var err error - result := new(ctypes.BurrowResult) - wire.ReadJSONPtr(result, bs, &err) +func readResult(t *testing.T, bs []byte) rpc.ResultInner { + result := new(rpc.Result) + err := json.Unmarshal(bs, result) if err != nil { - t.Fatal(err) + require.NoError(t, err) } - return *result + return result.ResultInner } diff --git a/rpc/tm/method/method.go b/rpc/tm/method/method.go new file mode 100644 index 0000000000000000000000000000000000000000..927c2b4fd3529df075aa449a97b18892c2f4b8e1 --- /dev/null +++ b/rpc/tm/method/method.go @@ -0,0 +1,40 @@ +package method + +const ( + Subscribe = "subscribe" + Unsubscribe = "unsubscribe" + + // Status + Status = "status" + NetInfo = "net_info" + + // Accounts + ListAccounts = "list_accounts" + GetAccount = "get_account" + GetStorage = "get_storage" + DumpStorage = "dump_storage" + + // Simulated call + Call = "call" + CallCode = "call_code" + + // Names + GetName = "get_name" + ListNames = "list_names" + BroadcastTx = "broadcast_tx" + + // Blockchain + Genesis = "genesis" + ChainID = "chain_id" + Blockchain = "blockchain" + GetBlock = "get_block" + + // Consensus + ListUnconfirmedTxs = "list_unconfirmed_txs" + ListValidators = "list_validators" + DumpConsensusState = "dump_consensus_state" + + // Private keys and signing + GeneratePrivateAccount = "unsafe/gen_priv_account" + SignTx = "unsafe/sign_tx" +) diff --git a/rpc/tm/routes.go b/rpc/tm/routes.go new file mode 100644 index 0000000000000000000000000000000000000000..8223795f7b6efb2c4da03b4c55a82b1ae9439209 --- /dev/null +++ b/rpc/tm/routes.go @@ -0,0 +1,141 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tm + +import ( + "fmt" + "reflect" + + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc/tm/method" + "github.com/hyperledger/burrow/txs" + gorpc "github.com/tendermint/tendermint/rpc/lib/server" + "github.com/tendermint/tendermint/rpc/lib/types" +) + +func GetRoutes(service rpc.Service) map[string]*gorpc.RPCFunc { + return map[string]*gorpc.RPCFunc{ + // Transact + method.BroadcastTx: gorpc.NewRPCFunc(func(tx txs.Wrapper) (rpc.Result, error) { + return wrapReturnBurrowResult(service.BroadcastTx(tx.Unwrap())) + }, "tx"), + // Events + method.Subscribe: gorpc.NewWSRPCFunc(func(wsCtx rpctypes.WSRPCContext, + eventId string) (rpc.Result, error) { + return wrapReturnBurrowResult(service.Subscribe(eventId, + func(eventData event.AnyEventData) { + // NOTE: EventSwitch callbacks must be nonblocking + wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Request.ID+"#event", + rpc.ResultEvent{Event: eventId, AnyEventData: eventData}.Wrap())) + })) + }, "eventId"), + method.Unsubscribe: gorpc.NewWSRPCFunc(func(wsCtx rpctypes.WSRPCContext, + subscriptionId string) (rpc.Result, error) { + return wrapReturnBurrowResult(service.Unsubscribe(subscriptionId)) + }, "subscriptionId"), + // Status + method.Status: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.Status), ""), + method.NetInfo: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.NetInfo), ""), + // Accounts + method.ListAccounts: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListAccounts), ""), + method.GetAccount: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetAccount), "address"), + method.GetStorage: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetStorage), "address,key"), + method.DumpStorage: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.DumpStorage), "address"), + // Simulated call + method.Call: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.Call), "fromAddress,toAddress,data"), + method.CallCode: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.CallCode), "fromAddress,code,data"), + // Blockchain + method.Genesis: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.Genesis), ""), + method.ChainID: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ChainId), ""), + method.Blockchain: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.BlockchainInfo), "minHeight,maxHeight"), + method.GetBlock: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetBlock), "height"), + // Consensus + method.ListUnconfirmedTxs: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListUnconfirmedTxs), "maxTxs"), + method.ListValidators: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListValidators), ""), + method.DumpConsensusState: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.DumpConsensusState), ""), + // Names + method.GetName: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetName), "name"), + method.ListNames: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListNames), ""), + // Private keys and signing + method.GeneratePrivateAccount: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GeneratePrivateAccount), ""), + method.SignTx: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.SignTx), "tx,privAccounts"), + } +} + +func mustWrapFuncBurrowResult(f interface{}) interface{} { + wrapped, err := wrapFuncBurrowResult(f) + if err != nil { + panic(fmt.Errorf("must be able to wrap RPC function: %v", err)) + } + return wrapped +} + +// Takes a function with a covariant return type in func(args...) (ResultInner, error) +// and returns it as func(args...) (Result, error) so it can be serialised with the mapper +func wrapFuncBurrowResult(f interface{}) (interface{}, error) { + rv := reflect.ValueOf(f) + rt := rv.Type() + if rt.Kind() != reflect.Func { + return nil, fmt.Errorf("must be passed a func f, but got: %#v", f) + } + + in := make([]reflect.Type, rt.NumIn()) + for i := 0; i < rt.NumIn(); i++ { + in[i] = rt.In(i) + } + + if rt.NumOut() != 2 { + return nil, fmt.Errorf("expects f to return the pair of ResultInner, error but got %v return types", + rt.NumOut()) + } + + out := make([]reflect.Type, 2) + err := checkTypeImplements(rt.Out(0), (*rpc.ResultInner)(nil)) + if err != nil { + return nil, fmt.Errorf("wrong first return type: %v", err) + } + err = checkTypeImplements(rt.Out(1), (*error)(nil)) + if err != nil { + return nil, fmt.Errorf("wrong second return type: %v", err) + } + + out[0] = reflect.TypeOf(rpc.Result{}) + out[1] = rt.Out(1) + + return reflect.MakeFunc(reflect.FuncOf(in, out, false), + func(args []reflect.Value) []reflect.Value { + ret := rv.Call(args) + burrowResult := reflect.New(out[0]) + burrowResult.Elem().Field(0).Set(ret[0]) + ret[0] = burrowResult.Elem() + return ret + }).Interface(), nil +} + +func wrapReturnBurrowResult(result rpc.ResultInner, err error) (rpc.Result, error) { + return rpc.Result{ResultInner: result}, err +} + +// Passed a type and a pointer to an interface value checks that typ implements that interface +// returning a human-readable error if it does not +// (e.g. ifacePtr := (*MyInterface)(nil) can be a reasonably convenient stand-in for an actual type literal), +func checkTypeImplements(typ reflect.Type, ifacePtr interface{}) error { + ifaceType := reflect.TypeOf(ifacePtr).Elem() + if !typ.Implements(ifaceType) { + return fmt.Errorf("%s does not implement interface %s", typ, ifaceType) + } + return nil +} diff --git a/rpc/tm/routes_test.go b/rpc/tm/routes_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2136c389cdac0d1217971f8b38c308ba8a39873f --- /dev/null +++ b/rpc/tm/routes_test.go @@ -0,0 +1,29 @@ +package tm + +import ( + "testing" + + "github.com/hyperledger/burrow/rpc" + "github.com/stretchr/testify/require" +) + +func testChainId(chainName string) (*rpc.ResultChainId, error) { + return &rpc.ResultChainId{ + ChainName: chainName, + ChainId: "Foos", + GenesisHash: []byte{}, + }, nil +} + +func TestWrapFuncBurrowResult(t *testing.T) { + f, err := wrapFuncBurrowResult(testChainId) + require.NoError(t, err) + fOut, ok := f.(func(string) (rpc.Result, error)) + require.True(t, ok, "must be able to cast to function type") + br, err := fOut("Blum") + require.NoError(t, err) + bs, err := br.MarshalJSON() + require.NoError(t, err) + require.Equal(t, `{"type":"result_chain_id","data":{"chain_name":"Blum","chain_id":"Foos","genesis_hash":""}}`, + string(bs)) +} diff --git a/rpc/tm/server.go b/rpc/tm/server.go new file mode 100644 index 0000000000000000000000000000000000000000..13726d58c0493bfc676f1815deb68c794acf64e8 --- /dev/null +++ b/rpc/tm/server.go @@ -0,0 +1,44 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tm + +import ( + "net" + "net/http" + + "github.com/hyperledger/burrow/consensus/tendermint" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/rpc" + "github.com/tendermint/tendermint/rpc/lib/server" + "github.com/tendermint/tmlibs/events" +) + +func StartServer(service rpc.Service, pattern, listenAddress string, evsw events.EventSwitch, + logger logging_types.InfoTraceLogger) (net.Listener, error) { + + logger = logger.With(structure.ComponentKey, "RPC_TM") + routes := GetRoutes(service) + mux := http.NewServeMux() + wm := rpcserver.NewWebsocketManager(routes, evsw) + mux.HandleFunc(pattern, wm.WebsocketHandler) + tmLogger := tendermint.NewLogger(logger) + rpcserver.RegisterRPCFuncs(mux, routes, tmLogger) + listener, err := rpcserver.StartHTTPServer(listenAddress, mux, tmLogger) + if err != nil { + return nil, err + } + return listener, nil +} diff --git a/rpc/v0/json_service_test.go b/rpc/v0/json_service_test.go index 5428f94a727d2593427ea5e65f2c03d6758f4bb6..087b89696e6c2b3daac5421480b046e213d7dff4 100644 --- a/rpc/v0/json_service_test.go +++ b/rpc/v0/json_service_test.go @@ -33,7 +33,7 @@ func TestBroadcastTx(t *testing.T) { pubKey := account.GenPrivAccount().PubKey address := []byte{1} code := opcodes.Bytecode(opcodes.PUSH1, 1, opcodes.PUSH1, 1, opcodes.ADD) - var tx txs.Tx = txs.NewCallTxWithNonce(pubKey, address, code, 10, 2, + var tx txs.Tx = txs.NewCallTxWithSequence(pubKey, address, code, 10, 2, 1, 0) jsonBytes := wire.JSONBytesPretty(wrappedTx{tx}) request := rpc.NewRPCRequest("TestBroadcastTx", "BroadcastTx", jsonBytes) diff --git a/rpc/v0/rest_server_pipe_test.go b/rpc/v0/rest_server_pipe_test.go deleted file mode 100644 index 1cd8282288fb26024268a52b694c76856142c71c..0000000000000000000000000000000000000000 --- a/rpc/v0/rest_server_pipe_test.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package v0 - -import ( - "fmt" - - account "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - definitions "github.com/hyperledger/burrow/definitions" - event "github.com/hyperledger/burrow/event" - - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - consensus_types "github.com/hyperledger/burrow/consensus/types" - logging_types "github.com/hyperledger/burrow/logging/types" - manager_types "github.com/hyperledger/burrow/manager/types" - "github.com/hyperledger/burrow/txs" - - "github.com/hyperledger/burrow/logging/loggers" - abci_types "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-p2p" - mintTypes "github.com/tendermint/tendermint/types" -) - -// Base struct. -type MockPipe struct { - testData TestData - accounts definitions.Accounts - blockchain blockchain_types.Blockchain - consensusEngine consensus_types.ConsensusEngine - events event.EventEmitter - namereg definitions.NameReg - transactor definitions.Transactor - logger logging_types.InfoTraceLogger -} - -// Create a new mock tendermint pipe. -func NewMockPipe(td *TestData) definitions.Pipe { - return &MockPipe{ - testData: *td, - accounts: &accounts{td}, - blockchain: &chain{td}, - consensusEngine: &consensusEngine{td}, - events: &eventer{td}, - namereg: &namereg{td}, - transactor: &transactor{td}, - logger: loggers.NewNoopInfoTraceLogger(), - } -} - -// Create a mock pipe with default mock data. -func NewDefaultMockPipe() definitions.Pipe { - return NewMockPipe(LoadTestData()) -} - -func (pipe *MockPipe) Accounts() definitions.Accounts { - return pipe.accounts -} - -func (pipe *MockPipe) Blockchain() blockchain_types.Blockchain { - return pipe.blockchain -} - -func (pipe *MockPipe) Events() event.EventEmitter { - return pipe.events -} - -func (pipe *MockPipe) NameReg() definitions.NameReg { - return pipe.namereg -} - -func (pipe *MockPipe) Transactor() definitions.Transactor { - return pipe.transactor -} - -func (pipe *MockPipe) Logger() logging_types.InfoTraceLogger { - return pipe.logger -} - -func (pipe *MockPipe) GetApplication() manager_types.Application { - // TODO: [ben] mock application - return nil -} - -func (pipe *MockPipe) SetConsensusEngine(_ consensus_types.ConsensusEngine) error { - // TODO: [ben] mock consensus engine - return nil -} - -func (pipe *MockPipe) GetConsensusEngine() consensus_types.ConsensusEngine { - return pipe.consensusEngine -} - -func (pipe *MockPipe) SetBlockchain(_ blockchain_types.Blockchain) error { - // TODO: [ben] mock consensus engine - return nil -} - -func (pipe *MockPipe) GetBlockchain() blockchain_types.Blockchain { - return nil -} - -func (pipe *MockPipe) GetTendermintPipe() (definitions.TendermintPipe, error) { - return nil, fmt.Errorf("Tendermint pipe is not supported by mocked pipe.") -} - -func (pipe *MockPipe) GenesisHash() []byte { - return pipe.testData.GetGenesisHash.Output.Hash -} - -// Components - -// Accounts -type accounts struct { - testData *TestData -} - -func (acc *accounts) GenPrivAccount() (*account.PrivAccount, error) { - return acc.testData.GenPrivAccount.Output, nil -} - -func (acc *accounts) GenPrivAccountFromKey(key []byte) (*account.PrivAccount, error) { - return acc.testData.GenPrivAccount.Output, nil -} - -func (acc *accounts) Accounts([]*event.FilterData) (*core_types.AccountList, error) { - return acc.testData.GetAccounts.Output, nil -} - -func (acc *accounts) Account(address []byte) (*account.Account, error) { - return acc.testData.GetAccount.Output, nil -} - -func (acc *accounts) Storage(address []byte) (*core_types.Storage, error) { - return acc.testData.GetStorage.Output, nil -} - -func (acc *accounts) StorageAt(address, key []byte) (*core_types.StorageItem, error) { - return acc.testData.GetStorageAt.Output, nil -} - -// Blockchain -type chain struct { - testData *TestData -} - -func (this *chain) ChainId() string { - return this.testData.GetChainId.Output.ChainId -} - -func (this *chain) Height() int { - return this.testData.GetLatestBlockHeight.Output.Height -} - -func (this *chain) Block(height int) *mintTypes.Block { - return this.testData.GetBlock.Output -} - -func (this *chain) BlockMeta(height int) *mintTypes.BlockMeta { - return &mintTypes.BlockMeta{} -} - -// Consensus -type consensusEngine struct { - testData *TestData -} - -func (cons *consensusEngine) BroadcastTransaction(transaction []byte, - callback func(*abci_types.Response)) error { - return nil -} - -func (cons *consensusEngine) IsListening() bool { - return cons.testData.IsListening.Output.Listening -} - -func (cons *consensusEngine) Listeners() []p2p.Listener { - p2pListeners := make([]p2p.Listener, 0) - - for _, name := range cons.testData.GetListeners.Output.Listeners { - p2pListeners = append(p2pListeners, p2p.NewDefaultListener("tcp", name, true)) - } - - return p2pListeners -} - -func (cons *consensusEngine) NodeInfo() *p2p.NodeInfo { - return &p2p.NodeInfo{ - Version: cons.testData.GetNetworkInfo.Output.ClientVersion, - Moniker: cons.testData.GetNetworkInfo.Output.Moniker, - } -} - -func (cons *consensusEngine) Peers() []*consensus_types.Peer { - return cons.testData.GetPeers.Output -} - -func (cons *consensusEngine) PublicValidatorKey() crypto.PubKey { - return crypto.PubKeyEd25519{ - 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, - } -} - -func (cons *consensusEngine) Events() event.EventEmitter { - return nil -} - -func (cons *consensusEngine) ListUnconfirmedTxs(maxTxs int) ([]txs.Tx, error) { - return cons.testData.GetUnconfirmedTxs.Output.Txs, nil -} - -func (cons *consensusEngine) ListValidators() []consensus_types.Validator { - return nil -} - -func (cons *consensusEngine) ConsensusState() *consensus_types.ConsensusState { - return &consensus_types.ConsensusState{} -} - -func (cons *consensusEngine) PeerConsensusStates() map[string]string { - return map[string]string{} -} - -func (cons *consensusEngine) Stop() bool { - return true -} - -// Events -type eventer struct { - testData *TestData -} - -func (evntr *eventer) Subscribe(subId, event string, callback func(txs.EventData)) error { - return nil -} - -func (evntr *eventer) Unsubscribe(subId string) error { - return nil -} - -// NameReg -type namereg struct { - testData *TestData -} - -func (nmreg *namereg) Entry(key string) (*core_types.NameRegEntry, error) { - return nmreg.testData.GetNameRegEntry.Output, nil -} - -func (nmreg *namereg) Entries(filters []*event.FilterData) (*core_types.ResultListNames, error) { - return nmreg.testData.GetNameRegEntries.Output, nil -} - -// Txs -type transactor struct { - testData *TestData -} - -func (trans *transactor) Call(fromAddress, toAddress, data []byte) (*core_types.Call, error) { - return trans.testData.Call.Output, nil -} - -func (trans *transactor) CallCode(from, code, data []byte) (*core_types.Call, error) { - return trans.testData.CallCode.Output, nil -} - -func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { - receipt := txs.GenerateReceipt(trans.testData.GetChainId.Output.ChainId, tx) - return &receipt, nil -} - -func (trans *transactor) Transact(privKey, address, data []byte, gasLimit, fee int64) (*txs.Receipt, error) { - if len(address) == 0 { - return trans.testData.TransactCreate.Output, nil - } - return trans.testData.Transact.Output, nil -} - -func (trans *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*txs.EventDataCall, error) { - return nil, nil -} - -func (trans *transactor) Send(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) { - return nil, nil -} - -func (trans *transactor) SendAndHold(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) { - return nil, nil -} - -func (trans *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee int64) (*txs.Receipt, error) { - return trans.testData.TransactNameReg.Output, nil -} - -func (trans *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) { - return nil, nil -} diff --git a/server/server.go b/server/server.go index 2a3d7bc7cff5cd21147323b10f1e337021871285..fba39b00377c4186ada70fc27f3428842bd713f4 100644 --- a/server/server.go +++ b/server/server.go @@ -64,7 +64,6 @@ type ServeProcess struct { // Initializes all the servers and starts listening for connections. func (serveProcess *ServeProcess) Start() error { - router := gin.New() config := serveProcess.config diff --git a/test/fixtures/file_fixtures.go b/test/fixtures/file_fixtures.go deleted file mode 100644 index 875e8398e5e007b4b4289f67348301689961a91b..0000000000000000000000000000000000000000 --- a/test/fixtures/file_fixtures.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package fixtures - -import ( - "os" - "path" - - "io/ioutil" -) - -// FileFixtures writes files to a temporary location for use in testing. -type FileFixtures struct { - tempDir string - // If an error has occurred setting up fixtures - Error error -} - -// Set up a new FileFixtures object by passing an interlaced list of file names -// and file contents. The file names will be interpreted as relative to some -// temporary root directory that is fixed when allocate() is called on the -// FileFixtures struct. -func NewFileFixtures(identifyingPrefix string) *FileFixtures { - dir, err := ioutil.TempDir("", identifyingPrefix) - return &FileFixtures{ - tempDir: dir, - Error: err, - } -} - -// Returns the root temporary directory that this FileFixtures will populate and -// clear on RemoveAll() -func (ffs *FileFixtures) TempDir() string { - return ffs.tempDir -} - -// Add a file relative to the FileFixtures tempDir using name for the relative -// part of the path. -func (ffs *FileFixtures) AddFile(name, content string) string { - if ffs.Error != nil { - return "" - } - filePath := path.Join(ffs.tempDir, name) - ffs.AddDir(path.Dir(name)) - if ffs.Error == nil { - ffs.Error = createWriteClose(filePath, content) - } - return filePath -} - -// Ensure that the directory relative to the FileFixtures tempDir exists using -// name for the relative part of the path. -func (ffs *FileFixtures) AddDir(name string) string { - if ffs.Error != nil { - return "" - } - filePath := path.Join(ffs.tempDir, name) - ffs.Error = os.MkdirAll(filePath, 0777) - return filePath -} - -// Cleans up the the temporary files (with fire) -func (ffs *FileFixtures) RemoveAll() { - os.RemoveAll(ffs.tempDir) -} - -// Create a text file at filename with contents content -func createWriteClose(filename, content string) error { - // We'll create any parent dirs, with permissive permissions - err := os.MkdirAll(path.Dir(filename), 0777) - if err != nil { - return err - } - f, err := os.Create(filename) - if err != nil { - return err - } - f.WriteString(content) - defer f.Close() - return nil -} diff --git a/test/server/http_burst_test.go b/test/server/http_burst_test.go deleted file mode 100644 index b9e404e2801abb98046186e53ca0e9342eec7016..0000000000000000000000000000000000000000 --- a/test/server/http_burst_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - // "fmt" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -const ( - HTTP_MESSAGES = 300 -) - -// Send a burst of GET messages to the server. -func TestHttpFlooding(t *testing.T) { - serveProcess, err := NewServeScumbag() - assert.NoError(t, err, "Error creating new Server") - errSS := serveProcess.Start() - assert.NoError(t, errSS, "Scumbag-ed!") - t.Logf("Flooding http requests.") - for i := 0; i < 3; i++ { - err := runHttp() - assert.NoError(t, err) - time.Sleep(200 * time.Millisecond) - } - stopC := serveProcess.StopEventChannel() - errStop := serveProcess.Stop(0) - <-stopC - assert.NoError(t, errStop, "Scumbag-ed!") -} - -func runHttp() error { - c := 0 - for c < HTTP_MESSAGES { - resp, errG := http.Get("http://localhost:31400/scumbag") - if errG != nil { - return errG - } - c++ - resp.Body.Close() - } - return nil -} diff --git a/test/server/scumbag_test.go b/test/server/scumbag_test.go deleted file mode 100644 index 92eb0c03e8b8494c493f05464fb8c5753f136d37..0000000000000000000000000000000000000000 --- a/test/server/scumbag_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "encoding/json" - "os" - "runtime" - - "github.com/hyperledger/burrow/rpc" - "github.com/hyperledger/burrow/server" - - "github.com/gin-gonic/gin" - "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/tendermint/log15" -) - -var logger, _ = lifecycle.NewStdErrLogger() - -func init() { - runtime.GOMAXPROCS(runtime.NumCPU()) - log15.Root().SetHandler(log15.LvlFilterHandler( - log15.LvlWarn, - log15.StreamHandler(os.Stdout, log15.TerminalFormat()), - )) - gin.SetMode(gin.ReleaseMode) -} - -type ScumbagServer struct { - running bool -} - -func NewScumbagServer() server.Server { - return &ScumbagServer{} -} - -func (this *ScumbagServer) Start(sc *server.ServerConfig, g *gin.Engine) { - g.GET("/scumbag", func(c *gin.Context) { - c.String(200, "Scumbag") - }) - this.running = true -} - -func (this *ScumbagServer) Running() bool { - return this.running -} - -func (this *ScumbagServer) ShutDown() { - // fmt.Println("Scumbag...") -} - -type ScumSocketService struct{} - -func (this *ScumSocketService) Process(data []byte, session *server.WSSession) { - resp := rpc.NewRPCResponse("1", "Scumbag") - bts, _ := json.Marshal(resp) - session.Write(bts) -} - -func NewScumsocketServer(maxConnections uint16) *server.WebSocketServer { - sss := &ScumSocketService{} - return server.NewWebSocketServer(maxConnections, sss, logger) -} - -func NewServeScumbag() (*server.ServeProcess, error) { - cfg := server.DefaultServerConfig() - cfg.Bind.Port = uint16(31400) - return server.NewServeProcess(cfg, logger, NewScumbagServer()) -} - -func NewServeScumSocket(wsServer *server.WebSocketServer) (*server.ServeProcess, - error) { - cfg := server.DefaultServerConfig() - cfg.WebSocket.WebSocketEndpoint = "/scumsocket" - cfg.Bind.Port = uint16(31401) - return server.NewServeProcess(cfg, logger, wsServer) -} diff --git a/test/server/ws_burst_test.go b/test/server/ws_burst_test.go deleted file mode 100644 index 1721fa9a22ffad3b54b4bc0fdef8935df9448bdd..0000000000000000000000000000000000000000 --- a/test/server/ws_burst_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "testing" - "time" - - "github.com/hyperledger/burrow/client" - "github.com/hyperledger/burrow/server" - "github.com/stretchr/testify/assert" -) - -const CONNS uint16 = 100 -const MESSAGES = 1000 - -// To keep track of new websocket sessions on the server. -type SessionCounter struct { - opened int - closed int -} - -func (this *SessionCounter) Run(oChan, cChan <-chan *server.WSSession) { - go func() { - for { - select { - case <-oChan: - this.opened++ - break - case <-cChan: - this.closed++ - break - } - } - }() -} - -func (this *SessionCounter) Report() (int, int, int) { - return this.opened, this.closed, this.opened - this.closed -} - -// Testing to ensure that websocket server does not crash, and that it -// cleans up after itself. -func TestWsFlooding(t *testing.T) { - - // New websocket server. - wsServer := NewScumsocketServer(CONNS) - - // Keep track of sessions. - sc := &SessionCounter{} - - // Register the observer. - oChan := wsServer.SessionManager().SessionOpenEventChannel() - cChan := wsServer.SessionManager().SessionCloseEventChannel() - - sc.Run(oChan, cChan) - - serveProcess, err := NewServeScumSocket(wsServer) - assert.NoError(t, err, "Failed to serve new websocket.") - errServe := serveProcess.Start() - assert.NoError(t, errServe, "ScumSocketed!") - t.Logf("Flooding...") - // Run. Blocks. - errRun := runWs() - stopC := serveProcess.StopEventChannel() - errStop := serveProcess.Stop(0) - <-stopC - assert.NoError(t, errRun, "ScumSocketed!") - assert.NoError(t, errStop, "ScumSocketed!") - o, c, a := sc.Report() - assert.Equal(t, uint16(o), CONNS, "Server registered '%d' opened conns out of '%d'", o, CONNS) - assert.Equal(t, uint16(c), CONNS, "Server registered '%d' closed conns out of '%d'", c, CONNS) - assert.Equal(t, uint16(a), uint16(0), "Server registered '%d' conns still active after shutting down.", a) -} - -func runWs() error { - doneChan := make(chan bool) - errChan := make(chan error) - for i := uint16(0); i < CONNS; i++ { - go wsClient(doneChan, errChan) - } - runners := uint16(0) - for runners < CONNS { - select { - case <-doneChan: - runners++ - case err := <-errChan: - return err - } - } - return nil -} - -func wsClient(doneChan chan bool, errChan chan error) { - client := client.NewWSClient("ws://localhost:31401/scumsocket") - _, err := client.Dial() - if err != nil { - errChan <- err - return - } - readChan := client.StartRead() - i := 0 - for i < MESSAGES { - client.WriteMsg([]byte("test")) - <-readChan - i++ - } - client.Close() - time.Sleep(100 * time.Millisecond) - - doneChan <- true -} diff --git a/test/testdata/filters/testdata_filters.g_ b/test/testdata/filters/testdata_filters.g_ deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/test/testdata/helpers.go b/test/testdata/helpers.go deleted file mode 100644 index b591d858a7d7afcd45fd5e10566a8e179c678b15..0000000000000000000000000000000000000000 --- a/test/testdata/helpers.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package testdata - -import ( - "fmt" - "os" - "path" - - "github.com/hyperledger/burrow/files" - "github.com/hyperledger/burrow/server" - - stypes "github.com/hyperledger/burrow/manager/burrow-mint/state/types" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-wire" - "github.com/tendermint/tendermint/types" -) - -const TendermintConfigDefault = `# This is a TOML config file. -# For more information, see https:/github.com/toml-lang/toml - -moniker = "__MONIKER__" -seeds = "" -fast_sync = false -db_backend = "leveldb" -log_level = "debug" -node_laddr = "" -rpc_laddr = "" -` - -func CreateTempWorkDir(privValidator *types.PrivValidator, genesis *stypes.GenesisDoc, folderName string) (string, error) { - - workDir := path.Join(os.TempDir(), folderName) - os.RemoveAll(workDir) - errED := EnsureDir(workDir) - - if errED != nil { - return "", errED - } - - cfgName := path.Join(workDir, "config.toml") - scName := path.Join(workDir, "server_conf.toml") - pvName := path.Join(workDir, "priv_validator.json") - genesisName := path.Join(workDir, "genesis.json") - - // Write config. - errCFG := files.WriteFileRW(cfgName, []byte(TendermintConfigDefault)) - if errCFG != nil { - return "", errCFG - } - fmt.Printf("File written: %s\n.", cfgName) - - // Write validator. - errPV := writeJSON(pvName, privValidator) - if errPV != nil { - return "", errPV - } - fmt.Printf("File written: %s\n.", pvName) - - // Write genesis - errG := writeJSON(genesisName, genesis) - if errG != nil { - return "", errG - } - fmt.Printf("File written: %s\n.", genesisName) - - // Write server config. - errWC := server.WriteServerConfig(scName, server.DefaultServerConfig()) - if errWC != nil { - return "", errWC - } - fmt.Printf("File written: %s\n.", scName) - return workDir, nil -} - -// Used to write json files using tendermints wire package. -func writeJSON(file string, v interface{}) error { - var n int64 - var errW error - fo, errC := os.Create(file) - if errC != nil { - return errC - } - wire.WriteJSON(v, fo, &n, &errW) - if errW != nil { - return errW - } - errL := fo.Close() - if errL != nil { - return errL - } - return nil -} diff --git a/txs/events.go b/txs/events.go deleted file mode 100644 index 6c634d18386d41b7596b310483fdf9c7acdb6896..0000000000000000000000000000000000000000 --- a/txs/events.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package txs - -import ( - "fmt" - "time" - - . "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-wire" - tm_types "github.com/tendermint/tendermint/types" // Block -) - -// Functions to generate eventId strings - -func EventStringAccInput(addr []byte) string { return fmt.Sprintf("Acc/%X/Input", addr) } -func EventStringAccOutput(addr []byte) string { return fmt.Sprintf("Acc/%X/Output", addr) } -func EventStringAccCall(addr []byte) string { return fmt.Sprintf("Acc/%X/Call", addr) } -func EventStringLogEvent(addr []byte) string { return fmt.Sprintf("Log/%X", addr) } -func EventStringPermissions(name string) string { return fmt.Sprintf("Permissions/%s", name) } -func EventStringNameReg(name string) string { return fmt.Sprintf("NameReg/%s", name) } -func EventStringBond() string { return "Bond" } -func EventStringUnbond() string { return "Unbond" } -func EventStringRebond() string { return "Rebond" } -func EventStringDupeout() string { return "Dupeout" } -func EventStringNewBlock() string { return "NewBlock" } -func EventStringFork() string { return "Fork" } - -func EventStringNewRound() string { return fmt.Sprintf("NewRound") } -func EventStringTimeoutPropose() string { return fmt.Sprintf("TimeoutPropose") } -func EventStringCompleteProposal() string { return fmt.Sprintf("CompleteProposal") } -func EventStringPolka() string { return fmt.Sprintf("Polka") } -func EventStringUnlock() string { return fmt.Sprintf("Unlock") } -func EventStringLock() string { return fmt.Sprintf("Lock") } -func EventStringRelock() string { return fmt.Sprintf("Relock") } -func EventStringTimeoutWait() string { return fmt.Sprintf("TimeoutWait") } -func EventStringVote() string { return fmt.Sprintf("Vote") } - -//---------------------------------------- - -const ( - EventDataTypeNewBlock = byte(0x01) - EventDataTypeFork = byte(0x02) - EventDataTypeTx = byte(0x03) - EventDataTypeCall = byte(0x04) - EventDataTypeLog = byte(0x05) - EventDataTypeNewBlockHeader = byte(0x06) - - EventDataTypeRoundState = byte(0x11) - EventDataTypeVote = byte(0x12) -) - -type EventData interface { - AssertIsEventData() -} - -var _ = wire.RegisterInterface( - struct{ EventData }{}, - wire.ConcreteType{EventDataNewBlockHeader{}, EventDataTypeNewBlockHeader}, - wire.ConcreteType{EventDataNewBlock{}, EventDataTypeNewBlock}, - // wire.ConcreteType{EventDataFork{}, EventDataTypeFork }, - wire.ConcreteType{EventDataTx{}, EventDataTypeTx}, - wire.ConcreteType{EventDataCall{}, EventDataTypeCall}, - wire.ConcreteType{EventDataLog{}, EventDataTypeLog}, - wire.ConcreteType{EventDataRoundState{}, EventDataTypeRoundState}, - wire.ConcreteType{EventDataVote{}, EventDataTypeVote}, -) - -// Most event messages are basic types (a block, a transaction) -// but some (an input to a call tx or a receive) are more exotic - -type EventDataNewBlock struct { - Block *tm_types.Block `json:"block"` -} - -type EventDataNewBlockHeader struct { - Header *tm_types.Header `json:"header"` -} - -// All txs fire EventDataTx, but only CallTx might have Return or Exception -type EventDataTx struct { - Tx Tx `json:"tx"` - Return []byte `json:"return"` - Exception string `json:"exception"` -} - -// EventDataCall fires when we call a contract, and when a contract calls another contract -type EventDataCall struct { - CallData *CallData `json:"call_data"` - Origin []byte `json:"origin"` - TxID []byte `json:"tx_id"` - Return []byte `json:"return"` - Exception string `json:"exception"` -} - -type CallData struct { - Caller []byte `json:"caller"` - Callee []byte `json:"callee"` - Data []byte `json:"data"` - Value int64 `json:"value"` - Gas int64 `json:"gas"` -} - -// EventDataLog fires when a contract executes the LOG opcode -type EventDataLog struct { - Address Word256 `json:"address"` - Topics []Word256 `json:"topics"` - Data []byte `json:"data"` - Height int64 `json:"height"` -} - -// We fire the most recent round state that led to the event -// (ie. NewRound will have the previous rounds state) -type EventDataRoundState struct { - CurrentTime time.Time `json:"current_time"` - - Height int `json:"height"` - Round int `json:"round"` - Step string `json:"step"` - StartTime time.Time `json:"start_time"` - CommitTime time.Time `json:"commit_time"` - Proposal *tm_types.Proposal `json:"proposal"` - ProposalBlock *tm_types.Block `json:"proposal_block"` - LockedRound int `json:"locked_round"` - LockedBlock *tm_types.Block `json:"locked_block"` - POLRound int `json:"pol_round"` -} - -type EventDataVote struct { - Index int - Address []byte - Vote *tm_types.Vote -} - -func (_ EventDataNewBlock) AssertIsEventData() {} -func (_ EventDataNewBlockHeader) AssertIsEventData() {} -func (_ EventDataTx) AssertIsEventData() {} -func (_ EventDataCall) AssertIsEventData() {} -func (_ EventDataLog) AssertIsEventData() {} -func (_ EventDataRoundState) AssertIsEventData() {} -func (_ EventDataVote) AssertIsEventData() {} diff --git a/txs/go_wire_codec.go b/txs/go_wire_codec.go new file mode 100644 index 0000000000000000000000000000000000000000..ebedfcf7bc1d699d4d72a8238f493ccc4eb47de6 --- /dev/null +++ b/txs/go_wire_codec.go @@ -0,0 +1,53 @@ +package txs + +import ( + "bytes" + "sync" + + "github.com/tendermint/go-wire" +) + +type goWireCodec struct { + // Worth it? Possibly not, but we need to instantiate a codec though so... + bufferPool sync.Pool +} + +func NewGoWireCodec() *goWireCodec { + return &goWireCodec{ + bufferPool: sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, + }, + } +} + +func (gwc *goWireCodec) EncodeTx(tx Tx) ([]byte, error) { + var n int + var err error + buf := gwc.bufferPool.Get().(*bytes.Buffer) + defer gwc.recycle(buf) + wire.WriteBinary(struct{ Tx }{tx}, buf, &n, &err) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// panic on err +func (gwc *goWireCodec) DecodeTx(txBytes []byte) (Tx, error) { + var n int + var err error + tx := new(Tx) + buf := bytes.NewBuffer(txBytes) + wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) + if err != nil { + return nil, err + } + return *tx, nil +} + +func (gwc *goWireCodec) recycle(buf *bytes.Buffer) { + buf.Reset() + gwc.bufferPool.Put(buf) +} diff --git a/txs/go_wire_codec_test.go b/txs/go_wire_codec_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7e191c19e6d5818b296064b27cda244780ddea9f --- /dev/null +++ b/txs/go_wire_codec_test.go @@ -0,0 +1,59 @@ +package txs + +import ( + "testing" + + acm "github.com/hyperledger/burrow/account" + "github.com/stretchr/testify/assert" +) + +func TestEncodeTxDecodeTx(t *testing.T) { + gwc := NewGoWireCodec() + inputAddress := acm.Address{1, 2, 3, 4, 5} + outputAddress := acm.Address{5, 4, 3, 2, 1} + amount := uint64(2) + sequence := uint64(3) + tx := &SendTx{ + Inputs: []*TxInput{{ + Address: inputAddress, + Amount: amount, + Sequence: sequence, + }}, + Outputs: []*TxOutput{{ + Address: outputAddress, + Amount: amount, + }}, + } + txBytes, err := gwc.EncodeTx(tx) + if err != nil { + t.Fatal(err) + } + txOut, err := gwc.DecodeTx(txBytes) + assert.NoError(t, err, "DecodeTx error") + assert.Equal(t, tx, txOut) +} + +func TestEncodeTxDecodeTx_CallTx(t *testing.T) { + gwc := NewGoWireCodec() + inputAddress := acm.Address{1, 2, 3, 4, 5} + amount := uint64(2) + sequence := uint64(3) + tx := &CallTx{ + Input: &TxInput{ + Address: inputAddress, + Amount: amount, + Sequence: sequence, + }, + GasLimit: 233, + Fee: 2, + Address: nil, + Data: []byte("code"), + } + txBytes, err := gwc.EncodeTx(tx) + if err != nil { + t.Fatal(err) + } + txOut, err := gwc.DecodeTx(txBytes) + assert.NoError(t, err, "DecodeTx error") + assert.Equal(t, tx, txOut) +} diff --git a/txs/names.go b/txs/names.go index 6535bf7656870219e56dafa106cd254a80756e0d..23c693f0a1a8db9a6c6c559f30f601092482db99 100644 --- a/txs/names.go +++ b/txs/names.go @@ -16,20 +16,18 @@ package txs import ( "regexp" - - core_types "github.com/hyperledger/burrow/core/types" ) var ( - MinNameRegistrationPeriod int = 5 + MinNameRegistrationPeriod uint64 = 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 + NameByteCostMultiplier uint64 = 1 + NameBlockCostMultiplier uint64 = 1 MaxNameLength = 64 MaxDataLength = 1 << 16 @@ -50,16 +48,10 @@ func validateNameRegEntryData(data string) bool { } // base cost is "effective" number of bytes -func NameBaseCost(name, data string) int64 { - return int64(len(data) + 32) +func NameBaseCost(name, data string) uint64 { + return uint64(len(data) + 32) } -func NameCostPerBlock(baseCost int64) int64 { +func NameCostPerBlock(baseCost uint64) uint64 { return NameBlockCostMultiplier * NameByteCostMultiplier * baseCost } - -// XXX: vestige of an older time -type ResultListNames struct { - BlockHeight int `json:"block_height"` - Names []*core_types.NameRegEntry `json:"names"` -} diff --git a/txs/tx.go b/txs/tx.go index 44cd1b1056dadd93225cad00f904d110039d684e..6d061b04522d59d623e6be789da4a8be5dc9ea96 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -15,32 +15,26 @@ package txs import ( - "bytes" "encoding/json" "errors" + "fmt" "io" - "golang.org/x/crypto/ripemd160" - acm "github.com/hyperledger/burrow/account" - ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/tendermint/go-common" + ptypes "github.com/hyperledger/burrow/permission" "github.com/tendermint/go-wire" - - "github.com/tendermint/go-crypto" - tendermint_types "github.com/tendermint/tendermint/types" // votes for dupeout .. + "github.com/tendermint/go-wire/data" + "golang.org/x/crypto/ripemd160" ) 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") + ErrTxInvalidAddress = errors.New("error invalid address") + ErrTxDuplicateAddress = errors.New("error duplicate address") + ErrTxInvalidAmount = errors.New("error invalid amount") + ErrTxInsufficientFunds = errors.New("error insufficient funds") + ErrTxUnknownPubKey = errors.New("error unknown pubkey") + ErrTxInvalidPubKey = errors.New("error invalid pubkey") + ErrTxInvalidSignature = errors.New("error invalid signature") ) type ErrTxInvalidString struct { @@ -52,12 +46,12 @@ func (e ErrTxInvalidString) Error() string { } type ErrTxInvalidSequence struct { - Got int - Expected int + Got uint64 + Expected uint64 } func (e ErrTxInvalidSequence) Error() string { - return Fmt("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected) + return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected) } /* @@ -71,7 +65,6 @@ Account Txs: Validation Txs: - BondTx New validator posts a bond - UnbondTx Validator leaves - - DupeoutTx Validator dupes out (equivocates) Admin Txs: - PermissionsTx @@ -85,27 +78,22 @@ const ( TxTypeName = byte(0x03) // Validation transactions - TxTypeBond = byte(0x11) - TxTypeUnbond = byte(0x12) - TxTypeRebond = byte(0x13) - TxTypeDupeout = byte(0x14) + TxTypeBond = byte(0x11) + TxTypeUnbond = byte(0x12) + TxTypeRebond = byte(0x13) // Admin transactions - TxTypePermissions = byte(0x20) + TxTypePermissions = byte(0x1f) ) -// 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}, -) +var mapper = data.NewMapper(Wrapper{}). + RegisterImplementation(&SendTx{}, "send_tx", TxTypeSend). + RegisterImplementation(&CallTx{}, "call_tx", TxTypeCall). + RegisterImplementation(&NameTx{}, "name_tx", TxTypeName). + RegisterImplementation(&BondTx{}, "bond_tx", TxTypeBond). + RegisterImplementation(&UnbondTx{}, "unbond_tx", TxTypeUnbond). + RegisterImplementation(&RebondTx{}, "rebond_tx", TxTypeRebond). + RegisterImplementation(&PermissionsTx{}, "permissions_tx", TxTypePermissions) //----------------------------------------------------------------------------- @@ -114,6 +102,18 @@ type ( WriteSignBytes(chainID string, w io.Writer, n *int, err *error) } + Wrapper struct { + Tx `json:"unwrap"` + } + + Encoder interface { + EncodeTx(tx Tx) ([]byte, error) + } + + Decoder interface { + DecodeTx(txBytes []byte) (Tx, error) + } + // UnconfirmedTxs UnconfirmedTxs struct { Txs []Tx `json:"txs"` @@ -126,40 +126,73 @@ type ( // BroadcastTx or Transact Receipt struct { - TxHash []byte `json:"tx_hash"` - CreatesContract uint8 `json:"creates_contract"` - ContractAddr []byte `json:"contract_addr"` + TxHash []byte `json:"tx_hash"` + CreatesContract bool `json:"creates_contract"` + ContractAddr acm.Address `json:"contract_addr"` } NameTx struct { Input *TxInput `json:"input"` Name string `json:"name"` Data string `json:"data"` - Fee int64 `json:"fee"` + Fee uint64 `json:"fee"` } CallTx struct { - Input *TxInput `json:"input"` - Address []byte `json:"address"` - GasLimit int64 `json:"gas_limit"` - Fee int64 `json:"fee"` - Data []byte `json:"data"` + Input *TxInput `json:"input"` + // Pointer since CallTx defines unset 'to' address as inducing account creation + Address *acm.Address `json:"address"` + GasLimit uint64 `json:"gas_limit"` + Fee uint64 `json:"fee"` + Data []byte `json:"data"` } 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 crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx - PubKey crypto.PubKey `json:"pub_key"` // Must not be nil, may be nil + Address acm.Address `json:"address"` // Hash of the PublicKey + Amount uint64 `json:"amount"` // Must not exceed account balance + Sequence uint64 `json:"sequence"` // Must be 1 greater than the last committed TxInput + Signature acm.Signature `json:"signature"` // Depends on the PublicKey type and the whole Tx + PubKey acm.PublicKey `json:"pub_key"` // Must not be nil, may be nil } TxOutput struct { - Address []byte `json:"address"` // Hash of the PubKey - Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs. + Address acm.Address `json:"address"` // Hash of the PublicKey + Amount uint64 `json:"amount"` // The sum of all outputs must not exceed the inputs. } ) +// Wrap the Tx in a struct that allows for go-wire JSON serialisation +func Wrap(tx Tx) Wrapper { + if txWrapped, ok := tx.(Wrapper); ok { + return txWrapped + } + return Wrapper{ + Tx: tx, + } +} + +// A serialisation wrapper that is itself a Tx +func (txw Wrapper) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + txw.Tx.WriteSignBytes(chainID, w, n, err) +} + +func (txw Wrapper) MarshalJSON() ([]byte, error) { + return mapper.ToJSON(txw.Tx) +} + +func (txw *Wrapper) UnmarshalJSON(data []byte) (err error) { + parsed, err := mapper.FromJSON(data) + if err == nil && parsed != nil { + txw.Tx = parsed.(Tx) + } + return err +} + +// Get the inner Tx that this Wrapper wraps +func (txw *Wrapper) Unwrap() Tx { + return txw.Tx +} + func (txIn *TxInput) ValidateBasic() error { if len(txIn.Address) != 20 { return ErrTxInvalidAddress @@ -171,11 +204,11 @@ func (txIn *TxInput) ValidateBasic() error { } func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(Fmt(`{"address":"%X","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","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) + return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PubKey) } //----------------------------------------------------------------------------- @@ -191,18 +224,18 @@ func (txOut *TxOutput) ValidateBasic() error { } func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(Fmt(`{"address":"%X","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err) } func (txOut *TxOutput) String() string { - return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Amount) + return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount) } //----------------------------------------------------------------------------- func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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) + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) for i, in := range tx.Inputs { in.WriteSignBytes(w, n, err) if i != len(tx.Inputs)-1 { @@ -220,40 +253,31 @@ func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error } func (tx *SendTx) String() string { - return Fmt("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) + return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) } //----------------------------------------------------------------------------- func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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) + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"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) + return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) } //----------------------------------------------------------------------------- func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"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(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err) wire.WriteTo([]byte(`}]}`), w, n, err) } @@ -262,46 +286,46 @@ func (tx *NameTx) ValidateStrings() error { return ErrTxInvalidString{"Name must not be empty"} } if len(tx.Name) > MaxNameLength { - return ErrTxInvalidString{Fmt("Name is too long. Max %d bytes", MaxNameLength)} + return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)} } if len(tx.Data) > MaxDataLength { - return ErrTxInvalidString{Fmt("Data is too long. Max %d bytes", MaxDataLength)} + return ErrTxInvalidString{fmt.Sprintf("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)} + return ErrTxInvalidString{fmt.Sprintf("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 ErrTxInvalidString{fmt.Sprintf("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) + return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) } //----------------------------------------------------------------------------- type BondTx struct { - PubKey crypto.PubKeyEd25519 `json:"pub_key"` // NOTE: these don't have type byte - Signature crypto.SignatureEd25519 `json:"signature"` - Inputs []*TxInput `json:"inputs"` - UnbondTo []*TxOutput `json:"unbond_to"` + PubKey acm.PublicKey `json:"pub_key"` // NOTE: these don't have type byte + Signature acm.Signature `json:"signature"` + Inputs []*TxInput `json:"inputs"` + UnbondTo []*TxOutput `json:"unbond_to"` } func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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) + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"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([]byte(fmt.Sprintf(`],"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 { @@ -314,71 +338,53 @@ func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error } func (tx *BondTx) String() string { - return Fmt("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo) + return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo) } //----------------------------------------------------------------------------- type UnbondTx struct { - Address []byte `json:"address"` - Height int `json:"height"` - Signature crypto.SignatureEd25519 `json:"signature"` + Address acm.Address `json:"address"` + Height int `json:"height"` + Signature acm.Signature `json:"signature"` } func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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) + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","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) + return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) } //----------------------------------------------------------------------------- type RebondTx struct { - Address []byte `json:"address"` - Height int `json:"height"` - Signature crypto.SignatureEd25519 `json:"signature"` + Address acm.Address `json:"address"` + Height int `json:"height"` + Signature acm.Signature `json:"signature"` } func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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) + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","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 tendermint_types.Vote `json:"vote_a"` - VoteB tendermint_types.Vote `json:"vote_b"` -} - -func (tx *DupeoutTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - // PanicSanity("DupeoutTx has no sign bytes") - // TODO - // return -} - -func (tx *DupeoutTx) String() string { - return Fmt("DupeoutTx{%X,%v,%v}", tx.Address, tx.VoteA, tx.VoteB) + return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) } //----------------------------------------------------------------------------- type PermissionsTx struct { - Input *TxInput `json:"input"` - PermArgs ptypes.PermArgs `json:"args"` + Input *TxInput `json:"input"` + PermArgs *ptypes.PermArgs `json:"args"` } func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, 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.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"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) @@ -386,7 +392,7 @@ func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err } func (tx *PermissionsTx) String() string { - return Fmt("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs) + return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs) } //----------------------------------------------------------------------------- @@ -401,41 +407,16 @@ func TxHash(chainID string, tx Tx) []byte { //----------------------------------------------------------------------------- -func EncodeTx(tx Tx) ([]byte, error) { - var n int - var err error - buf := new(bytes.Buffer) - wire.WriteBinary(struct{ Tx }{tx}, buf, &n, &err) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// panic on err -func DecodeTx(txBytes []byte) (Tx, error) { - var n int - var err error - tx := new(Tx) - buf := bytes.NewBuffer(txBytes) - wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) - if err != nil { - return nil, err - } - return *tx, nil -} - func GenerateReceipt(chainId string, tx Tx) Receipt { receipt := Receipt{ - TxHash: TxHash(chainId, tx), - CreatesContract: 0, - ContractAddr: nil, + TxHash: TxHash(chainId, tx), } if callTx, ok := tx.(*CallTx); ok { - if len(callTx.Address) == 0 { - receipt.CreatesContract = 1 - receipt.ContractAddr = NewContractAddress(callTx.Input.Address, - callTx.Input.Sequence) + receipt.CreatesContract = callTx.Address == nil + if receipt.CreatesContract { + receipt.ContractAddr = acm.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence) + } else { + receipt.ContractAddr = *callTx.Address } } return receipt @@ -447,7 +428,7 @@ func GenerateReceipt(chainId string, tx Tx) Receipt { func jsonEscape(str string) string { escapedBytes, err := json.Marshal(str) if err != nil { - PanicSanity(Fmt("Error json-escaping a string", str)) + panic(fmt.Sprintf("error json-escaping a string", str)) } return string(escapedBytes) } diff --git a/txs/tx_test.go b/txs/tx_test.go index ca4b2ff0048278efdb71b55170bbd385d6e3af2e..7ec8f44004742ab8bf7c63bd2a278d44877c88f0 100644 --- a/txs/tx_test.go +++ b/txs/tx_test.go @@ -15,47 +15,53 @@ package txs import ( + "fmt" "testing" - acm "github.com/hyperledger/burrow/account" - ptypes "github.com/hyperledger/burrow/permission/types" + "encoding/json" + acm "github.com/hyperledger/burrow/account" + ptypes "github.com/hyperledger/burrow/permission" "github.com/stretchr/testify/assert" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-crypto" + "github.com/stretchr/testify/require" ) var chainID = "myChainID" +func makeAddress(str string) (address acm.Address) { + copy(address[:], ([]byte)(str)) + return +} + func TestSendTxSignable(t *testing.T) { sendTx := &SendTx{ Inputs: []*TxInput{ - &TxInput{ - Address: []byte("input1"), + { + Address: makeAddress("input1"), Amount: 12345, Sequence: 67890, }, - &TxInput{ - Address: []byte("input2"), + { + Address: makeAddress("input2"), Amount: 111, Sequence: 222, }, }, Outputs: []*TxOutput{ - &TxOutput{ - Address: []byte("output1"), + { + Address: makeAddress("output1"), Amount: 333, }, - &TxOutput{ - Address: []byte("output2"), + { + Address: makeAddress("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}]}]}`, - chainID) + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"%s","amount":12345,"sequence":67890},{"address":"%s","amount":111,"sequence":222}],"outputs":[{"address":"%s","amount":333},{"address":"%s","amount":444}]}]}`, + chainID, sendTx.Inputs[0].Address.String(), sendTx.Inputs[1].Address.String(), sendTx.Outputs[0].Address.String(), sendTx.Outputs[1].Address.String()) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) @@ -63,21 +69,22 @@ func TestSendTxSignable(t *testing.T) { } func TestCallTxSignable(t *testing.T) { + toAddress := makeAddress("contract1") callTx := &CallTx{ Input: &TxInput{ - Address: []byte("input1"), + Address: makeAddress("input1"), Amount: 12345, Sequence: 67890, }, - Address: []byte("contract1"), + Address: &toAddress, 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}}]}`, - chainID) + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[2,{"address":"%s","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"%s","amount":12345,"sequence":67890}}]}`, + chainID, callTx.Address.String(), callTx.Input.Address.String()) if signStr != expected { t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr) } @@ -86,7 +93,7 @@ func TestCallTxSignable(t *testing.T) { func TestNameTxSignable(t *testing.T) { nameTx := &NameTx{ Input: &TxInput{ - Address: []byte("input1"), + Address: makeAddress("input1"), Amount: 12345, Sequence: 250, }, @@ -96,59 +103,66 @@ func TestNameTxSignable(t *testing.T) { } 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"}]}`, - chainID) + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[3,{"data":"secretly.not.google.com","fee":1000,"input":{"address":"%s","amount":12345,"sequence":250},"name":"google.com"}]}`, + chainID, nameTx.Input.Address.String()) 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) + privAccount := acm.GeneratePrivateAccountFromSecret("foooobars") bondTx := &BondTx{ - PubKey: privAccount.PubKey.(crypto.PubKeyEd25519), + PubKey: privAccount.PublicKey(), Inputs: []*TxInput{ - &TxInput{ - Address: []byte("input1"), + { + Address: makeAddress("input1"), Amount: 12345, Sequence: 67890, }, - &TxInput{ - Address: []byte("input2"), + { + Address: makeAddress("input2"), Amount: 111, Sequence: 222, }, }, UnbondTo: []*TxOutput{ - &TxOutput{ - Address: []byte("output1"), + { + Address: makeAddress("output1"), Amount: 333, }, - &TxOutput{ - Address: []byte("output2"), + { + Address: makeAddress("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":"3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29","unbond_to":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, - chainID) - if signStr != expected { - t.Errorf("Unexpected sign string for BondTx. \nGot %s\nExpected %s", signStr, expected) - } + expected := fmt.Sprintf(`{"chain_id":"%s",`+ + `"tx":[17,{"inputs":[{"address":"%s",`+ + `"amount":12345,"sequence":67890},{"address":"%s",`+ + `"amount":111,"sequence":222}],"pub_key":[1,"%X"],`+ + `"unbond_to":[{"address":"%s",`+ + `"amount":333},{"address":"%s",`+ + `"amount":444}]}]}`, + chainID, + bondTx.Inputs[0].Address.String(), + bondTx.Inputs[1].Address.String(), + bondTx.PubKey.RawBytes(), + bondTx.UnbondTo[0].Address.String(), + bondTx.UnbondTo[1].Address.String()) + + assert.Equal(t, expected, string(acm.SignBytes(chainID, bondTx)), "Unexpected sign string for BondTx") } func TestUnbondTxSignable(t *testing.T) { unbondTx := &UnbondTx{ - Address: []byte("address1"), + Address: makeAddress("address1"), Height: 111, } signBytes := acm.SignBytes(chainID, unbondTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%s","tx":[18,{"address":"6164647265737331","height":111}]}`, - chainID) + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[18,{"address":"%s","height":111}]}`, + chainID, unbondTx.Address.String()) if signStr != expected { t.Errorf("Got unexpected sign string for UnbondTx") } @@ -156,13 +170,13 @@ func TestUnbondTxSignable(t *testing.T) { func TestRebondTxSignable(t *testing.T) { rebondTx := &RebondTx{ - Address: []byte("address1"), + Address: makeAddress("address1"), Height: 111, } signBytes := acm.SignBytes(chainID, rebondTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%s","tx":[19,{"address":"6164647265737331","height":111}]}`, - chainID) + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[19,{"address":"%s","height":111}]}`, + chainID, rebondTx.Address.String()) if signStr != expected { t.Errorf("Got unexpected sign string for RebondTx") } @@ -171,77 +185,84 @@ func TestRebondTxSignable(t *testing.T) { func TestPermissionsTxSignable(t *testing.T) { permsTx := &PermissionsTx{ Input: &TxInput{ - Address: []byte("input1"), + Address: makeAddress("input1"), Amount: 12345, Sequence: 250, }, - PermArgs: &ptypes.SetBaseArgs{ - Address: []byte("address1"), - Permission: 1, - Value: true, - }, + PermArgs: ptypes.SetBaseArgs(makeAddress("address1"), 1, 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}}]}`, - chainID) + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[31,{"args":"{"PermFlag":%v,"Address":"%s","Permission":1,"Value":true}","input":{"address":"%s","amount":12345,"sequence":250}}]}`, + chainID, ptypes.SetBase, permsTx.PermArgs.Address.String(), permsTx.Input.Address.String()) if signStr != expected { t.Errorf("Got unexpected sign string for PermsTx. Expected:\n%v\nGot:\n%v", expected, signStr) } } -func TestEncodeTxDecodeTx(t *testing.T) { - inputAddress := []byte{1, 2, 3, 4, 5} - outputAddress := []byte{5, 4, 3, 2, 1} - amount := int64(2) - sequence := 3 - tx := &SendTx{ - Inputs: []*TxInput{{ - Address: inputAddress, - Amount: amount, - Sequence: sequence, - }}, - Outputs: []*TxOutput{{ - Address: outputAddress, - Amount: amount, - }}, - } - txBytes, err := EncodeTx(tx) - if err != nil { - t.Fatal(err) - } - txOut, err := DecodeTx(txBytes) - assert.NoError(t, err, "DecodeTx error") - assert.Equal(t, tx, txOut) +func TestTxWrapper_MarshalJSON(t *testing.T) { + toAddress := makeAddress("contract1") + callTx := &CallTx{ + Input: &TxInput{ + Address: makeAddress("input1"), + Amount: 12345, + Sequence: 67890, + }, + Address: &toAddress, + GasLimit: 111, + Fee: 222, + Data: []byte("data1"), + } + testTxMarshalJSON(t, callTx) +} + +func TestNewPermissionsTxWithSequence(t *testing.T) { + privateKey := acm.PrivateKeyFromSecret("Shhh...") + + args := ptypes.SetBaseArgs(privateKey.PublicKey().Address(), ptypes.HasRole, true) + permTx := NewPermissionsTxWithSequence(privateKey.PublicKey(), args, 1) + testTxMarshalJSON(t, permTx) +} + +func testTxMarshalJSON(t *testing.T, tx Tx) { + txw := &Wrapper{Tx: tx} + bs, err := json.Marshal(txw) + require.NoError(t, err) + txwOut := new(Wrapper) + err = json.Unmarshal(bs, txwOut) + require.NoError(t, err) + bsOut, err := json.Marshal(txwOut) + require.NoError(t, err) + assert.Equal(t, string(bs), string(bsOut)) } /* func TestDupeoutTxSignable(t *testing.T) { - privAcc := acm.GenPrivAccount() - partSetHeader := types.PartSetHeader{Total: 10, Hash: []byte("partsethash")} + privAcc := acm.GeneratePrivateAccount() + partSetHeader := types.PartSetHeader{Total: 10, Hash: makeAddress("partsethash")} voteA := &types.Vote{ Height: 10, Round: 2, Type: types.VoteTypePrevote, - BlockHash: []byte("myblockhash"), + BlockHash: makeAddress("myblockhash"), BlockPartsHeader: partSetHeader, } - sig := privAcc.Sign(chainID, voteA) + sig := privAcc acm.ChainSign(chainID, voteA) voteA.Signature = sig.(crypto.SignatureEd25519) voteB := voteA.Copy() - voteB.BlockHash = []byte("myotherblockhash") - sig = privAcc.Sign(chainID, voteB) + voteB.BlockHash = makeAddress("myotherblockhash") + sig = privAcc acm.ChainSign(chainID, voteB) voteB.Signature = sig.(crypto.SignatureEd25519) dupeoutTx := &DupeoutTx{ - Address: []byte("address1"), + Address: makeAddress("address1"), VoteA: *voteA, VoteB: *voteB, } signBytes := acm.SignBytes(chainID, dupeoutTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%s","tx":[20,{"address":"6164647265737331","vote_a":%v,"vote_b":%v}]}`, + expected := fmt.Sprintf(`{"chain_id":"%s","tx":[20,{"address":"%s","vote_a":%v,"vote_b":%v}]}`, chainID, *voteA, *voteB) if signStr != expected { t.Errorf("Got unexpected sign string for DupeoutTx") diff --git a/txs/tx_utils.go b/txs/tx_utils.go index 6e1519b6850d1594bd7c25497059bd6dcbf80b61..753f3e3229273d7ae6970f3435f3f2cf975d981e 100644 --- a/txs/tx_utils.go +++ b/txs/tx_utils.go @@ -18,15 +18,9 @@ import ( "fmt" acm "github.com/hyperledger/burrow/account" - ptypes "github.com/hyperledger/burrow/permission/types" - - "github.com/tendermint/go-crypto" + ptypes "github.com/hyperledger/burrow/permission" ) -type AccountGetter interface { - GetAccount(addr []byte) *acm.Account -} - //---------------------------------------------------------------------------- // SendTx interface for adding inputs/outputs and adding signatures @@ -37,28 +31,30 @@ func NewSendTx() *SendTx { } } -func (tx *SendTx) AddInput(st AccountGetter, pubkey crypto.PubKey, amt int64) error { +func (tx *SendTx) AddInput(st acm.Getter, pubkey acm.PublicKey, amt uint64) error { addr := pubkey.Address() - acc := st.GetAccount(addr) + acc, err := st.GetAccount(addr) + if err != nil { + return err + } if acc == nil { - return fmt.Errorf("Invalid address %X from pubkey %X", addr, pubkey) + return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey) } - return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1) + return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1) } -func (tx *SendTx) AddInputWithNonce(pubkey crypto.PubKey, amt int64, nonce int) error { +func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { addr := pubkey.Address() tx.Inputs = append(tx.Inputs, &TxInput{ Address: addr, Amount: amt, - Sequence: nonce, - Signature: crypto.SignatureEd25519{}, + Sequence: sequence, PubKey: pubkey, }) return nil } -func (tx *SendTx) AddOutput(addr []byte, amt int64) error { +func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error { tx.Outputs = append(tx.Outputs, &TxOutput{ Address: addr, Amount: amt, @@ -66,36 +62,40 @@ func (tx *SendTx) AddOutput(addr []byte, amt int64) error { return nil } -func (tx *SendTx) SignInput(chainID string, i int, privAccount *acm.PrivAccount) error { +func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.PrivateAccount) 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) + tx.Inputs[i].PubKey = privAccount.PublicKey() + tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) return nil } //---------------------------------------------------------------------------- // CallTx interface for creating tx -func NewCallTx(st AccountGetter, from crypto.PubKey, to, data []byte, amt, gasLimit, fee int64) (*CallTx, error) { +func NewCallTx(st acm.Getter, from acm.PublicKey, to *acm.Address, data []byte, + amt, gasLimit, fee uint64) (*CallTx, error) { + addr := from.Address() - acc := st.GetAccount(addr) + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } if acc == nil { - return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) + return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from) } - nonce := acc.Sequence + 1 - return NewCallTxWithNonce(from, to, data, amt, gasLimit, fee, nonce), nil + sequence := acc.Sequence() + 1 + return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil } -func NewCallTxWithNonce(from crypto.PubKey, to, data []byte, amt, gasLimit, fee int64, nonce int) *CallTx { - addr := from.Address() +func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte, + amt, gasLimit, fee, sequence uint64) *CallTx { input := &TxInput{ - Address: addr, + Address: from.Address(), Amount: amt, - Sequence: nonce, - Signature: crypto.SignatureEd25519{}, + Sequence: sequence, PubKey: from, } @@ -108,32 +108,33 @@ func NewCallTxWithNonce(from crypto.PubKey, to, data []byte, amt, gasLimit, fee } } -func (tx *CallTx) Sign(chainID string, privAccount *acm.PrivAccount) { - tx.Input.PubKey = privAccount.PubKey - tx.Input.Signature = privAccount.Sign(chainID, tx) +func (tx *CallTx) Sign(chainID string, privAccount acm.PrivateAccount) { + tx.Input.PubKey = privAccount.PublicKey() + tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) } //---------------------------------------------------------------------------- // NameTx interface for creating tx -func NewNameTx(st AccountGetter, from crypto.PubKey, name, data string, amt, fee int64) (*NameTx, error) { +func NewNameTx(st acm.Getter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) { addr := from.Address() - acc := st.GetAccount(addr) + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } if acc == nil { - return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) + return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) } - nonce := acc.Sequence + 1 - return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil + sequence := acc.Sequence() + 1 + return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil } -func NewNameTxWithNonce(from crypto.PubKey, name, data string, amt, fee int64, nonce int) *NameTx { - addr := from.Address() +func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx { input := &TxInput{ - Address: addr, + Address: from.Address(), Amount: amt, - Sequence: nonce, - Signature: crypto.SignatureEd25519{}, + Sequence: sequence, PubKey: from, } @@ -145,48 +146,45 @@ func NewNameTxWithNonce(from crypto.PubKey, name, data string, amt, fee int64, n } } -func (tx *NameTx) Sign(chainID string, privAccount *acm.PrivAccount) { - tx.Input.PubKey = privAccount.PubKey - tx.Input.Signature = privAccount.Sign(chainID, tx) +func (tx *NameTx) Sign(chainID string, privAccount acm.PrivateAccount) { + tx.Input.PubKey = privAccount.PublicKey() + tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) } //---------------------------------------------------------------------------- // BondTx interface for adding inputs/outputs and adding signatures -func NewBondTx(pubkey crypto.PubKey) (*BondTx, error) { - pubkeyEd, ok := pubkey.(crypto.PubKeyEd25519) - if !ok { - return nil, fmt.Errorf("Pubkey must be ed25519") - } +func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) { return &BondTx{ - PubKey: pubkeyEd, + PubKey: pubkey, Inputs: []*TxInput{}, UnbondTo: []*TxOutput{}, }, nil } -func (tx *BondTx) AddInput(st AccountGetter, pubkey crypto.PubKey, amt int64) error { +func (tx *BondTx) AddInput(st acm.Getter, pubkey acm.PublicKey, amt uint64) error { addr := pubkey.Address() - acc := st.GetAccount(addr) + acc, err := st.GetAccount(addr) + if err != nil { + return err + } if acc == nil { - return fmt.Errorf("Invalid address %X from pubkey %X", addr, pubkey) + return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey) } - return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1) + return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1)) } -func (tx *BondTx) AddInputWithNonce(pubkey crypto.PubKey, amt int64, nonce int) error { - addr := pubkey.Address() +func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { tx.Inputs = append(tx.Inputs, &TxInput{ - Address: addr, + Address: pubkey.Address(), Amount: amt, - Sequence: nonce, - Signature: crypto.SignatureEd25519{}, + Sequence: sequence, PubKey: pubkey, }) return nil } -func (tx *BondTx) AddOutput(addr []byte, amt int64) error { +func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error { tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ Address: addr, Amount: amt, @@ -194,74 +192,70 @@ func (tx *BondTx) AddOutput(addr []byte, amt int64) error { return nil } -func (tx *BondTx) SignBond(chainID string, privAccount *acm.PrivAccount) error { - sig := privAccount.Sign(chainID, tx) - sigEd, ok := sig.(crypto.SignatureEd25519) - if !ok { - return fmt.Errorf("Bond signer must be ED25519") - } - tx.Signature = sigEd +func (tx *BondTx) SignBond(chainID string, privAccount acm.PrivateAccount) error { + tx.Signature = acm.ChainSign(privAccount, chainID, tx) return nil } -func (tx *BondTx) SignInput(chainID string, i int, privAccount *acm.PrivAccount) error { +func (tx *BondTx) SignInput(chainID string, i int, privAccount acm.PrivateAccount) 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) + tx.Inputs[i].PubKey = privAccount.PublicKey() + tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) return nil } //---------------------------------------------------------------------- // UnbondTx interface for creating tx -func NewUnbondTx(addr []byte, height int) *UnbondTx { +func NewUnbondTx(addr acm.Address, height int) *UnbondTx { return &UnbondTx{ Address: addr, Height: height, } } -func (tx *UnbondTx) Sign(chainID string, privAccount *acm.PrivAccount) { - tx.Signature = privAccount.Sign(chainID, tx).(crypto.SignatureEd25519) +func (tx *UnbondTx) Sign(chainID string, privAccount acm.PrivateAccount) { + tx.Signature = acm.ChainSign(privAccount, chainID, tx) } //---------------------------------------------------------------------- // RebondTx interface for creating tx -func NewRebondTx(addr []byte, height int) *RebondTx { +func NewRebondTx(addr acm.Address, height int) *RebondTx { return &RebondTx{ Address: addr, Height: height, } } -func (tx *RebondTx) Sign(chainID string, privAccount *acm.PrivAccount) { - tx.Signature = privAccount.Sign(chainID, tx).(crypto.SignatureEd25519) +func (tx *RebondTx) Sign(chainID string, privAccount acm.PrivateAccount) { + tx.Signature = acm.ChainSign(privAccount, chainID, tx) } //---------------------------------------------------------------------------- // PermissionsTx interface for creating tx -func NewPermissionsTx(st AccountGetter, from crypto.PubKey, args ptypes.PermArgs) (*PermissionsTx, error) { +func NewPermissionsTx(st acm.Getter, from acm.PublicKey, args *ptypes.PermArgs) (*PermissionsTx, error) { addr := from.Address() - acc := st.GetAccount(addr) + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } if acc == nil { - return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) + return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) } - nonce := acc.Sequence + 1 - return NewPermissionsTxWithNonce(from, args, nonce), nil + sequence := acc.Sequence() + 1 + return NewPermissionsTxWithSequence(from, args, sequence), nil } -func NewPermissionsTxWithNonce(from crypto.PubKey, args ptypes.PermArgs, nonce int) *PermissionsTx { - addr := from.Address() +func NewPermissionsTxWithSequence(from acm.PublicKey, args *ptypes.PermArgs, sequence uint64) *PermissionsTx { input := &TxInput{ - Address: addr, + Address: from.Address(), Amount: 1, // NOTE: amounts can't be 0 ... - Sequence: nonce, - Signature: crypto.SignatureEd25519{}, + Sequence: sequence, PubKey: from, } @@ -271,7 +265,7 @@ func NewPermissionsTxWithNonce(from crypto.PubKey, args ptypes.PermArgs, nonce i } } -func (tx *PermissionsTx) Sign(chainID string, privAccount *acm.PrivAccount) { - tx.Input.PubKey = privAccount.PubKey - tx.Input.Signature = privAccount.Sign(chainID, tx) +func (tx *PermissionsTx) Sign(chainID string, privAccount acm.PrivateAccount) { + tx.Input.PubKey = privAccount.PublicKey() + tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) } diff --git a/util/logging/cmd/main.go b/util/logging/cmd/main.go index fc6d16dd616bc3f9b7bc8e99fdedf85eb0ae3d13..cc6960b6bb389c396bdbc18b9d7662d8a9769165 100644 --- a/util/logging/cmd/main.go +++ b/util/logging/cmd/main.go @@ -31,8 +31,7 @@ func main() { "module", "p2p", "captured_logging_source", "tendermint_log15")). AddSinks( - Sink().SetOutput(SyslogOutput("Burrow-network")), - Sink().SetOutput(FileOutput("/var/log/burrow-network.log")), + Sink().SetOutput(StdoutOutput()), ), ), } diff --git a/util/snatives/cmd/main.go b/util/snatives/cmd/main.go index 16711bd03645bd57e85c8be9ffcb828e3d1ffa4f..a20bbe7149dfc18a1c791f76c011751b0d3ab2d3 100644 --- a/util/snatives/cmd/main.go +++ b/util/snatives/cmd/main.go @@ -17,13 +17,13 @@ package main import ( "fmt" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" + "github.com/hyperledger/burrow/execution/evm" "github.com/hyperledger/burrow/util/snatives/templates" ) // Dump SNative contracts func main() { - contracts := vm.SNativeContracts() + contracts := evm.SNativeContracts() // Index of next contract i := 1 fmt.Print("pragma solidity >=0.0.0;\n\n") diff --git a/util/snatives/templates/solidity_templates.go b/util/snatives/templates/solidity_templates.go index 165a2e6ab4b8877f7b6c0d9ef160e5d98cbff283..fec8861ad779c9f615755bd45e2ac17bc1fb9b53 100644 --- a/util/snatives/templates/solidity_templates.go +++ b/util/snatives/templates/solidity_templates.go @@ -20,7 +20,7 @@ import ( "strings" "text/template" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" + "github.com/hyperledger/burrow/execution/evm" ) const contractTemplateText = `/** @@ -63,11 +63,11 @@ func init() { } type solidityContract struct { - *vm.SNativeContractDescription + *evm.SNativeContractDescription } type solidityFunction struct { - *vm.SNativeFunctionDescription + *evm.SNativeFunctionDescription } // @@ -75,7 +75,7 @@ type solidityFunction struct { // // Create a templated solidityContract from an SNative contract description -func NewSolidityContract(contract *vm.SNativeContractDescription) *solidityContract { +func NewSolidityContract(contract *evm.SNativeContractDescription) *solidityContract { return &solidityContract{contract} } @@ -95,7 +95,7 @@ func (contract *solidityContract) InstanceName() string { } func (contract *solidityContract) Address() string { - return fmt.Sprintf("0x%x", + return fmt.Sprintf("0x%s", contract.SNativeContractDescription.Address()) } @@ -123,7 +123,7 @@ func (contract *solidityContract) Functions() []*solidityFunction { // // Create a templated solidityFunction from an SNative function description -func NewSolidityFunction(function *vm.SNativeFunctionDescription) *solidityFunction { +func NewSolidityFunction(function *evm.SNativeFunctionDescription) *solidityFunction { return &solidityFunction{function} } diff --git a/util/snatives/templates/solidity_templates_test.go b/util/snatives/templates/solidity_templates_test.go index 58fe37f7e461db87fbd9864263b312a5b6f5c300..998aa11fda15db59060c1fca1a4cb87a555129d8 100644 --- a/util/snatives/templates/solidity_templates_test.go +++ b/util/snatives/templates/solidity_templates_test.go @@ -18,12 +18,12 @@ import ( "fmt" "testing" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" + "github.com/hyperledger/burrow/execution/evm" "github.com/stretchr/testify/assert" ) func TestSNativeFuncTemplate(t *testing.T) { - contract := vm.SNativeContracts()["Permissions"] + contract := evm.SNativeContracts()["Permissions"] function, err := contract.FunctionByName("removeRole") if err != nil { t.Fatal("Couldn't get function") @@ -37,7 +37,7 @@ func TestSNativeFuncTemplate(t *testing.T) { // This test checks that we can generate the SNative contract interface and // prints it to stdout func TestSNativeContractTemplate(t *testing.T) { - contract := vm.SNativeContracts()["Permissions"] + contract := evm.SNativeContracts()["Permissions"] solidityContract := NewSolidityContract(contract) solidity, err := solidityContract.Solidity() assert.NoError(t, err)