diff --git a/.gitignore b/.gitignore index 062ac40a40e60a8deb295da0d715829b791ee5b6..c7f40537d814cb7864a2367604c295cfa919567c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ test_scratch/ # Temporary / cached *.swp -debug .idea .vscode diff --git a/Makefile b/Makefile index ba7048e3fe3d39db21a656fc2287e6eee95389e5..1ae718c5aeedf54a58bdf16974be15aac1a47122 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ SHELL := /bin/bash REPO := $(shell pwd) GOFILES_NOVENDOR := $(shell go list -f "{{.Dir}}" ./...) PACKAGES_NOVENDOR := $(shell go list ./...) +COMMIT := $(shell git rev-parse --short HEAD) # Bosmarmot integration testing BOSMARMOT_PROJECT := github.com/monax/bosmarmot BOSMARMOT_GOPATH := ${REPO}/.gopath_bos @@ -99,7 +100,7 @@ build_race: check build_race_db build_race_client # build burrow .PHONY: build_db build_db: - go build -o ${REPO}/bin/burrow ./cmd/burrow + go build -ldflags "-X github.com/hyperledger/burrow/project.commit=${COMMIT}" -o ${REPO}/bin/burrow ./cmd/burrow # build burrow-client .PHONY: build_client diff --git a/account/account.go b/account/account.go index f17ceeaedb32312f0c8a89b3499dce15ed275094..ab6e987480246167cc4373884ac5bcfc7482a968 100644 --- a/account/account.go +++ b/account/account.go @@ -25,6 +25,8 @@ import ( "github.com/tendermint/go-wire" ) +var GlobalPermissionsAddress = Address(binary.Zero160) + // Signable is an interface for all signable things. // It typically removes signatures before serializing. type Signable interface { @@ -66,6 +68,8 @@ type Account interface { // Obtain a deterministic serialisation of this account // (i.e. update order and Go runtime independent) Encode() ([]byte, error) + // String representation of the account + String() string } type MutableAccount interface { @@ -214,14 +218,6 @@ func AsMutableAccount(account Account) MutableAccount { 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 @@ -266,6 +262,10 @@ func (caw concreteAccountWrapper) Encode() ([]byte, error) { return caw.ConcreteAccount.Encode() } +func (caw concreteAccountWrapper) String() string { + return caw.ConcreteAccount.String() +} + func (caw concreteAccountWrapper) MarshalJSON() ([]byte, error) { return json.Marshal(caw.ConcreteAccount) } diff --git a/account/account_test.go b/account/account_test.go index 800ea4ff8c4638a71fed54dc4f9defa79a7ec263..aaffb2ff882fb8b60a85e2160e40138f2cf8ce19 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -21,6 +21,8 @@ import ( "fmt" + "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/go-crypto" @@ -77,11 +79,20 @@ func TestAccountSerialise(t *testing.T) { func TestDecodeConcrete(t *testing.T) { concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + concreteAcc.Permissions = types.AccountPermissions{ + Base: types.BasePermissions{ + Perms: permission.SetGlobal, + SetBit: permission.SetGlobal, + }, + Roles: []string{"bums"}, + } acc := concreteAcc.Account() encodedAcc, err := acc.Encode() require.NoError(t, err) + concreteAccOut, err := DecodeConcrete(encodedAcc) require.NoError(t, err) + assert.Equal(t, concreteAcc, *concreteAccOut) concreteAccOut, err = DecodeConcrete([]byte("flungepliffery munknut tolopops")) assert.Error(t, err) diff --git a/account/bytecode.go b/account/bytecode.go index f0f8b9a8aecededce4b7ab2b1d5cdcabed0bf6bd..01a1cdaddb15ca3e6cba73b06221949507a0d449 100644 --- a/account/bytecode.go +++ b/account/bytecode.go @@ -75,8 +75,8 @@ func (bc Bytecode) Tokens() ([]string, error) { return tokens, fmt.Errorf("did not recognise byte %#x at position %v as an OpCode:\n %s", bc[i], i, lexingPositionString(bc, i, tokens)) } - tokens = append(tokens, op.Name()) pushes := op.Pushes() + tokens = append(tokens, op.Name()) if pushes > 0 { // This is a PUSH<N> OpCode so consume N bytes from the input, render them as hex, and skip to next OpCode if i+pushes >= len(bc) { diff --git a/account/private_account.go b/account/private_account.go index 59f8245f109f7aed6a2847ee9808af27e50d85a4..9b705a02d518ede77b9047bc4288473259fff961 100644 --- a/account/private_account.go +++ b/account/private_account.go @@ -68,6 +68,10 @@ func (cpaw concretePrivateAccountWrapper) PrivateKey() PrivateKey { return cpaw.ConcretePrivateAccount.PrivateKey } +func (cpaw concretePrivateAccountWrapper) String() string { + return cpaw.ConcretePrivateAccount.String() +} + // ConcretePrivateAccount func (pa ConcretePrivateAccount) PrivateAccount() PrivateAccount { diff --git a/account/state.go b/account/state.go deleted file mode 100644 index 5569fbe636afc94f665f40970fe6267d5a7dac58..0000000000000000000000000000000000000000 --- a/account/state.go +++ /dev/null @@ -1,73 +0,0 @@ -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 -} - -type IterableStateWriter interface { - StateReader - Updater - StorageSetter - Iterable - StorageIterable -} diff --git a/account/state/state.go b/account/state/state.go new file mode 100644 index 0000000000000000000000000000000000000000..005d3082132303bbdb23db5cab678fe85528d077 --- /dev/null +++ b/account/state/state.go @@ -0,0 +1,101 @@ +package state + +import ( + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + ptypes "github.com/hyperledger/burrow/permission/types" +) + +type AccountGetter interface { + // Get an account by its address return nil if it does not exist (which should not be an error) + GetAccount(address acm.Address) (acm.Account, error) +} + +type AccountIterable 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(acm.Account) (stop bool)) (stopped bool, err error) +} + +type AccountUpdater interface { + // Updates the fields of updatedAccount by address, creating the account + // if it does not exist + UpdateAccount(updatedAccount acm.Account) error + // Remove the account at address + RemoveAccount(address acm.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 acm.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 acm.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 acm.Address, consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) +} + +// Compositions + +// Read-only account and storage state +type Reader interface { + AccountGetter + StorageGetter +} + +// Read and list account and storage state +type Iterable interface { + Reader + AccountIterable + StorageIterable +} + +// Read and write account and storage state +type Writer interface { + Reader + AccountUpdater + StorageSetter +} + +type IterableWriter interface { + Reader + AccountUpdater + StorageSetter + AccountIterable + StorageIterable +} + +func GetMutableAccount(getter AccountGetter, address acm.Address) (acm.MutableAccount, error) { + acc, err := getter.GetAccount(address) + if err != nil { + return nil, err + } + return acm.AsMutableAccount(acc), nil +} + +func GlobalPermissionsAccount(getter AccountGetter) acm.Account { + acc, err := getter.GetAccount(acm.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(getter AccountGetter) ptypes.AccountPermissions { + if getter == nil { + return ptypes.AccountPermissions{ + Roles: []string{}, + } + } + return GlobalPermissionsAccount(getter).Permissions() +} diff --git a/account/state_cache.go b/account/state/state_cache.go similarity index 76% rename from account/state_cache.go rename to account/state/state_cache.go index 28b6d0c58affd8a2f3383847459985b0ebd2a241..f9d85c979c2e253a6d417f49338d3579894663f6 100644 --- a/account/state_cache.go +++ b/account/state/state_cache.go @@ -12,48 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. -package account +package state import ( "fmt" "sort" "sync" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" ) -type StateCache interface { - IterableStateWriter - Sync(state StateWriter) error - Reset(backend StateIterable) - Flush(state IterableStateWriter) error - Backend() StateIterable +type Cache interface { + IterableWriter + Sync(state Writer) error + Reset(backend Iterable) + Flush(state IterableWriter) error + Backend() Iterable } type stateCache struct { sync.RWMutex - backend StateIterable - accounts map[Address]*accountInfo + backend Iterable + accounts map[acm.Address]*accountInfo } type accountInfo struct { sync.RWMutex - account Account + account acm.Account storage map[binary.Word256]binary.Word256 removed bool updated bool } -// Returns a StateCache that wraps an underlying StateReader to use on a cache miss, can write to an output StateWriter +// Returns a Cache that wraps an underlying Reader to use on a cache miss, can write to an output Writer // via Sync. Goroutine safe for concurrent access. -func NewStateCache(backend StateIterable) StateCache { +func NewCache(backend Iterable) Cache { return &stateCache{ backend: backend, - accounts: make(map[Address]*accountInfo), + accounts: make(map[acm.Address]*accountInfo), } } -func (cache *stateCache) GetAccount(address Address) (Account, error) { +func (cache *stateCache) GetAccount(address acm.Address) (acm.Account, error) { accInfo, err := cache.get(address) if err != nil { return nil, err @@ -66,7 +67,7 @@ func (cache *stateCache) GetAccount(address Address) (Account, error) { return accInfo.account, nil } -func (cache *stateCache) UpdateAccount(account Account) error { +func (cache *stateCache) UpdateAccount(account acm.Account) error { accInfo, err := cache.get(account.Address()) if err != nil { return err @@ -78,10 +79,11 @@ func (cache *stateCache) UpdateAccount(account Account) error { } accInfo.account = account accInfo.updated = true + return nil } -func (cache *stateCache) RemoveAccount(address Address) error { +func (cache *stateCache) RemoveAccount(address acm.Address) error { accInfo, err := cache.get(address) if err != nil { return err @@ -96,7 +98,7 @@ func (cache *stateCache) RemoveAccount(address Address) error { } // Iterates over all accounts first in cache and then in backend until consumer returns true for 'stop' -func (cache *stateCache) IterateAccounts(consumer func(Account) (stop bool)) (stopped bool, err error) { +func (cache *stateCache) IterateAccounts(consumer func(acm.Account) (stop bool)) (stopped bool, err error) { // Try cache first for early exit cache.RLock() for _, info := range cache.accounts { @@ -108,7 +110,7 @@ func (cache *stateCache) IterateAccounts(consumer func(Account) (stop bool)) (st return cache.backend.IterateAccounts(consumer) } -func (cache *stateCache) GetStorage(address Address, key binary.Word256) (binary.Word256, error) { +func (cache *stateCache) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { accInfo, err := cache.get(address) if err != nil { return binary.Zero256, err @@ -133,7 +135,7 @@ func (cache *stateCache) GetStorage(address Address, key binary.Word256) (binary } // NOTE: Set value to zero to remove. -func (cache *stateCache) SetStorage(address Address, key binary.Word256, value binary.Word256) error { +func (cache *stateCache) SetStorage(address acm.Address, key binary.Word256, value binary.Word256) error { accInfo, err := cache.get(address) accInfo.Lock() defer accInfo.Unlock() @@ -149,7 +151,7 @@ func (cache *stateCache) SetStorage(address Address, key binary.Word256, value b } // Iterates over all storage items first in cache and then in backend until consumer returns true for 'stop' -func (cache *stateCache) IterateStorage(address Address, +func (cache *stateCache) IterateStorage(address acm.Address, consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) { accInfo, err := cache.get(address) if err != nil { @@ -168,10 +170,10 @@ func (cache *stateCache) IterateStorage(address Address, // Syncs changes to the backend in deterministic order. Sends storage updates before updating // the account they belong so that storage values can be taken account of in the update. -func (cache *stateCache) Sync(state StateWriter) error { +func (cache *stateCache) Sync(state Writer) error { cache.Lock() defer cache.Unlock() - var addresses Addresses + var addresses acm.Addresses for address := range cache.accounts { addresses = append(addresses, address) } @@ -212,15 +214,15 @@ func (cache *stateCache) Sync(state StateWriter) error { } // Resets the cache to empty initialising the backing map to the same size as the previous iteration. -func (cache *stateCache) Reset(backend StateIterable) { +func (cache *stateCache) Reset(backend Iterable) { cache.Lock() defer cache.Unlock() cache.backend = backend - cache.accounts = make(map[Address]*accountInfo, len(cache.accounts)) + cache.accounts = make(map[acm.Address]*accountInfo, len(cache.accounts)) } -// Syncs the StateCache and Resets it to use as the backend StateReader -func (cache *stateCache) Flush(state IterableStateWriter) error { +// Syncs the Cache and Resets it to use as the backend Reader +func (cache *stateCache) Flush(state IterableWriter) error { err := cache.Sync(state) if err != nil { return err @@ -229,12 +231,12 @@ func (cache *stateCache) Flush(state IterableStateWriter) error { return nil } -func (cache *stateCache) Backend() StateIterable { +func (cache *stateCache) Backend() Iterable { return cache.backend } // Get the cache accountInfo item creating it if necessary -func (cache *stateCache) get(address Address) (*accountInfo, error) { +func (cache *stateCache) get(address acm.Address) (*accountInfo, error) { cache.RLock() accInfo := cache.accounts[address] cache.RUnlock() diff --git a/account/state/state_cache_test.go b/account/state/state_cache_test.go new file mode 100644 index 0000000000000000000000000000000000000000..de9683b2fbdc16f8ec93dc0577408b76431d04e5 --- /dev/null +++ b/account/state/state_cache_test.go @@ -0,0 +1,190 @@ +package state + +import ( + "fmt" + "testing" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/execution/evm/asm" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStateCache_GetAccount(t *testing.T) { + // Build backend states for read and write + readBackend := testAccounts() + writeBackend := NewCache(newTestState()) + cache := NewCache(readBackend) + + acc := readBackend.Accounts[addressOf("acc1")] + accOut, err := cache.GetAccount(acc.Address()) + require.NoError(t, err) + cache.UpdateAccount(accOut) + assert.Equal(t, acm.AsConcreteAccount(acc), acm.AsConcreteAccount(accOut)) + + err = cache.Sync(writeBackend) + require.NoError(t, err) + accOut, err = writeBackend.GetAccount(acc.Address()) + require.NotNil(t, accOut) + assert.NoError(t, err) + assert.Equal(t, acm.AsConcreteAccount(acc), acm.AsConcreteAccount(accOut)) +} + +func TestStateCache_UpdateAccount(t *testing.T) { + // Build backend states for read and write + backend := NewCache(newTestState()) + cache := NewCache(backend) + // Create acccount + accNew := acm.NewConcreteAccountFromSecret("accNew") + balance := uint64(24) + accNew.Balance = balance + err := cache.UpdateAccount(accNew.Account()) + require.NoError(t, err) + + // Check cache + accNewOut, err := cache.GetAccount(accNew.Address) + require.NoError(t, err) + assert.Equal(t, balance, accNewOut.Balance()) + + // Check not stored in backend + accNewOut, err = backend.GetAccount(accNew.Address) + require.NoError(t, err) + assert.Nil(t, accNewOut) + + // Check syncs to backend + err = cache.Sync(backend) + require.NoError(t, err) + accNewOut, err = backend.GetAccount(accNew.Address) + require.NoError(t, err) + assert.Equal(t, balance, accNewOut.Balance()) + + // Alter in cache + newBalance := uint64(100029) + accNew.Balance = newBalance + err = cache.UpdateAccount(accNew.Account()) + require.NoError(t, err) + + // Check cache + accNewOut, err = cache.GetAccount(accNew.Address) + require.NoError(t, err) + assert.Equal(t, newBalance, accNewOut.Balance()) + + // Check backend unchanged + accNewOut, err = backend.GetAccount(accNew.Address) + require.NoError(t, err) + assert.Equal(t, balance, accNewOut.Balance()) +} + +func TestStateCache_RemoveAccount(t *testing.T) { +} + +func TestStateCache_GetStorage(t *testing.T) { +} + +func TestStateCache_SetStorage(t *testing.T) { +} + +func TestStateCache_Sync(t *testing.T) { +} + +func TestStateCache_get(t *testing.T) { +} + +func testAccounts() *testState { + acc1 := acm.NewConcreteAccountFromSecret("acc1") + acc1.Permissions.Base.Perms = permission.AddRole | permission.Send + acc1.Permissions.Base.SetBit = acc1.Permissions.Base.Perms + + acc2 := acm.NewConcreteAccountFromSecret("acc2") + acc2.Permissions.Base.Perms = permission.AddRole | permission.Send + acc2.Permissions.Base.SetBit = acc1.Permissions.Base.Perms + acc2.Code, _ = acm.NewBytecode(asm.PUSH1, 0x20) + + cache := combine( + account(acc1.Account(), "I AM A KEY", "NO YOU ARE A KEY"), + account(acc2.Account(), "ducks", "have lucks", + "chickens", "just cluck"), + ) + return cache +} + +func addressOf(secret string) acm.Address { + return acm.NewConcreteAccountFromSecret(secret).Address +} + +// testState StateIterable + +type testState struct { + Accounts map[acm.Address]acm.Account + Storage map[acm.Address]map[binary.Word256]binary.Word256 +} + +var _ Iterable = &testState{} + +func newTestState() *testState { + return &testState{ + Accounts: make(map[acm.Address]acm.Account), + Storage: make(map[acm.Address]map[binary.Word256]binary.Word256), + } +} + +func account(acc acm.Account, keyvals ...string) *testState { + ts := newTestState() + ts.Accounts[acc.Address()] = acc + ts.Storage[acc.Address()] = make(map[binary.Word256]binary.Word256) + for i := 0; i < len(keyvals); i += 2 { + ts.Storage[acc.Address()][word(keyvals[i])] = word(keyvals[i+1]) + } + return ts +} + +func combine(states ...*testState) *testState { + ts := newTestState() + for _, state := range states { + for _, acc := range state.Accounts { + ts.Accounts[acc.Address()] = acc + ts.Storage[acc.Address()] = state.Storage[acc.Address()] + } + } + return ts +} + +func word(str string) binary.Word256 { + return binary.LeftPadWord256([]byte(str)) +} + +func (tsr *testState) IterateAccounts(consumer func(acm.Account) (stop bool)) (stopped bool, err error) { + for _, acc := range tsr.Accounts { + if consumer(acc) { + return true, nil + } + } + return false, nil +} + +func (tsr *testState) IterateStorage(address acm.Address, consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) { + for key, value := range tsr.Storage[address] { + if consumer(key, value) { + return true, nil + } + } + return false, nil +} + +func (tsr *testState) GetAccount(address acm.Address) (acm.Account, error) { + return tsr.Accounts[address], nil +} + +func (tsr *testState) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + storage, ok := tsr.Storage[address] + if !ok { + return binary.Zero256, fmt.Errorf("could not find storage for account %s", address) + } + value, ok := storage[key] + if !ok { + return binary.Zero256, fmt.Errorf("could not find key %x for account %s", key, address) + } + return value, nil +} diff --git a/account/state_cache_test.go b/account/state_cache_test.go deleted file mode 100644 index 6ecc2cdd108b2e947b567943afdcf4ccdf40565d..0000000000000000000000000000000000000000 --- a/account/state_cache_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package account - -import ( - "testing" - - "fmt" - - "github.com/hyperledger/burrow/binary" -) - -// TODO: write tests as part of feature branch -type testStateReader struct { - Accounts map[Address]Account - Storage map[Address]map[binary.Word256]binary.Word256 -} - -func accountAndStorage(account Account, keyvals ...binary.Word256) *testStateReader { - return &testStateReader{} - -} - -func (tsr *testStateReader) GetAccount(address Address) (Account, error) { - account, ok := tsr.Accounts[address] - if !ok { - return nil, fmt.Errorf("could not find account %s", address) - } - return account, nil -} - -func (tsr *testStateReader) GetStorage(address Address, key binary.Word256) (binary.Word256, error) { - storage, ok := tsr.Storage[address] - if !ok { - return binary.Zero256, fmt.Errorf("could not find storage for account %s", address) - } - value, ok := storage[key] - if !ok { - return binary.Zero256, fmt.Errorf("could not find key %x for account %s", key, address) - } - return value, nil -} - -var _ StateReader = &testStateReader{} - -func TestStateCache_GetAccount(t *testing.T) { -} - -func TestStateCache_UpdateAccount(t *testing.T) { -} - -func TestStateCache_RemoveAccount(t *testing.T) { -} - -func TestStateCache_GetStorage(t *testing.T) { -} - -func TestStateCache_SetStorage(t *testing.T) { -} - -func TestStateCache_Sync(t *testing.T) { -} - -func TestStateCache_get(t *testing.T) { -} diff --git a/binary/word256.go b/binary/word256.go index 7bdbb4ec0e63921e44ff8b3191b2485aa3a8cb6f..b24f869cafcc0aff5baf976504d43ba54603309c 100644 --- a/binary/word256.go +++ b/binary/word256.go @@ -108,6 +108,7 @@ func Uint64FromWord256(word Word256) uint64 { } func Int64FromWord256(word Word256) int64 { + buf := word.Postfix(8) return GetInt64BE(buf) } diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 6cda8da59ce02519dabb8159ad03a9b1310eac86..dae49f81adc865be17b0c5f4d9d6fc1a9dee0975 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -24,7 +24,6 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" dbm "github.com/tendermint/tmlibs/db" ) @@ -99,10 +98,10 @@ type PersistedState struct { } func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, - logger logging_types.InfoTraceLogger) (*blockchain, error) { + logger *logging.Logger) (*blockchain, error) { - logger = logging.WithScope(logger, "LoadOrNewBlockchain") - logging.InfoMsg(logger, "Trying to load blockchain state from database", + logger = logger.WithScope("LoadOrNewBlockchain") + logger.InfoMsg("Trying to load blockchain state from database", "database_key", stateKey) blockchain, err := LoadBlockchain(db) if err != nil { @@ -118,7 +117,7 @@ func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, return blockchain, nil } - logging.InfoMsg(logger, "No existing blockchain state found in database, making new blockchain") + logger.InfoMsg("No existing blockchain state found in database, making new blockchain") return NewBlockchain(db, genesisDoc), nil } diff --git a/client/methods/helpers.go b/client/methods/helpers.go index 6cdb8b3aedaca504d7ec803c250f9e1265686e0a..6fbf1f46ebc2b2d3c863193f6c5312d4e270d777 100644 --- a/client/methods/helpers.go +++ b/client/methods/helpers.go @@ -20,10 +20,9 @@ import ( "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" ) -func unpackSignAndBroadcast(result *rpc.TxResult, logger logging_types.InfoTraceLogger) { +func unpackSignAndBroadcast(result *rpc.TxResult, logger *logging.Logger) { if result == nil { // if we don't provide --sign or --broadcast return @@ -42,15 +41,15 @@ func unpackSignAndBroadcast(result *rpc.TxResult, logger logging_types.InfoTrace ) } - logging.InfoMsg(logger, "SignAndBroadcast result") + logger.InfoMsg("SignAndBroadcast result") } -func loggerFromClientDo(do *client.Do, scope string) (logging_types.InfoTraceLogger, error) { +func loggerFromClientDo(do *client.Do, scope string) (*logging.Logger, error) { logger, err := lifecycle.NewLoggerFromLoggingConfig(config.DefaultClientLoggingConfig()) if err != nil { return nil, err } - logger = logging.WithScope(logger, scope) + logger = logger.WithScope(scope) lifecycle.CaptureStdlibLogOutput(logger) return logger, nil } diff --git a/client/methods/status.go b/client/methods/status.go index 14a8be609eb100ca2cdecc3652f6af840e45cd6d..7487fdf19bafecda67104aad6a7d4b871ec8f080 100644 --- a/client/methods/status.go +++ b/client/methods/status.go @@ -36,7 +36,7 @@ func Status(do *client.Do) error { return fmt.Errorf("Error requesting chainId from chain at (%s): %s", do.NodeAddrFlag, err) } - logger.Info("chain", do.NodeAddrFlag, + logger.Info.Log("chain", do.NodeAddrFlag, "genesisHash", fmt.Sprintf("%X", genesisHash), "chainName", chainName, "chainId", chainId, diff --git a/client/mock/client_mock.go b/client/mock/client_mock.go index 5e195aefa2a679870b74b0876eb864257cace799..0fb53896ffb2284a7e9d754d75577afae03627b8 100644 --- a/client/mock/client_mock.go +++ b/client/mock/client_mock.go @@ -17,8 +17,7 @@ package mock import ( acm "github.com/hyperledger/burrow/account" . "github.com/hyperledger/burrow/client" - "github.com/hyperledger/burrow/logging/loggers" - logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/txs" "github.com/tendermint/go-crypto" @@ -100,6 +99,6 @@ func (mock *MockNodeClient) ListValidators() (blockHeight uint64, bondedValidato return } -func (mock *MockNodeClient) Logger() logging_types.InfoTraceLogger { - return loggers.NewNoopInfoTraceLogger() +func (mock *MockNodeClient) Logger() *logging.Logger { + return logging.NewNoopLogger() } diff --git a/client/node_client.go b/client/node_client.go index aecfb847894e58657936c61ea40e8d65d31fd1bd..6e19231a2405c1edced4b45d713a968c61851a9c 100644 --- a/client/node_client.go +++ b/client/node_client.go @@ -19,7 +19,6 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/logging" - 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" @@ -41,7 +40,7 @@ type NodeClient interface { ListValidators() (blockHeight uint64, bondedValidators, unbondingValidators []acm.Validator, err error) // Logging context for this NodeClient - Logger() logging_types.InfoTraceLogger + Logger() *logging.Logger } type NodeWebsocketClient interface { @@ -59,15 +58,15 @@ var _ NodeClient = (*burrowNodeClient)(nil) // burrow-client is a simple struct exposing the client rpc methods type burrowNodeClient struct { broadcastRPC string - logger logging_types.InfoTraceLogger + logger *logging.Logger } // BurrowKeyClient.New returns a new monax-keys client for provided rpc location // Monax-keys connects over http request-responses -func NewBurrowNodeClient(rpcString string, logger logging_types.InfoTraceLogger) *burrowNodeClient { +func NewBurrowNodeClient(rpcString string, logger *logging.Logger) *burrowNodeClient { return &burrowNodeClient{ broadcastRPC: rpcString, - logger: logging.WithScope(logger, "BurrowNodeClient"), + logger: logger.WithScope("BurrowNodeClient"), } } @@ -101,7 +100,7 @@ func (burrowNodeClient *burrowNodeClient) DeriveWebsocketClient() (nodeWsClient // } // wsAddr = "ws://" + wsAddr wsAddr = nodeAddr - logging.TraceMsg(burrowNodeClient.logger, "Subscribing to websocket address", + burrowNodeClient.logger.TraceMsg("Subscribing to websocket address", "websocket address", wsAddr, "endpoint", "/websocket", ) @@ -111,7 +110,7 @@ func (burrowNodeClient *burrowNodeClient) DeriveWebsocketClient() (nodeWsClient } derivedBurrowNodeWebsocketClient := &burrowNodeWebsocketClient{ tendermintWebsocket: wsClient, - logger: logging.WithScope(burrowNodeClient.logger, "BurrowNodeWebsocketClient"), + logger: burrowNodeClient.logger.WithScope("BurrowNodeWebsocketClient"), } return derivedBurrowNodeWebsocketClient, nil } @@ -260,6 +259,6 @@ func (burrowNodeClient *burrowNodeClient) ListValidators() (blockHeight uint64, return } -func (burrowNodeClient *burrowNodeClient) Logger() logging_types.InfoTraceLogger { +func (burrowNodeClient *burrowNodeClient) Logger() *logging.Logger { return burrowNodeClient.logger } diff --git a/client/rpc/client.go b/client/rpc/client.go index 53d80dbcf582742f0bed89eb4def2cf4d10a1fdc..3a76948c2638df2e4589c5e5188dc8bf75635c7d 100644 --- a/client/rpc/client.go +++ b/client/rpc/client.go @@ -24,6 +24,7 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/client" "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/permission/snatives" "github.com/hyperledger/burrow/txs" ) @@ -114,7 +115,7 @@ func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, if err != nil { return nil, fmt.Errorf("could not convert action '%s' to PermFlag: %v", action, err) } - permArgs := ptypes.PermArgs{ + permArgs := snatives.PermArgs{ PermFlag: permFlag, } diff --git a/client/rpc/client_util.go b/client/rpc/client_util.go index d437a92dae88ef47846e443ecffc93f273f019cf..914ba28f6387d87305d11c694c9f96c416092253 100644 --- a/client/rpc/client_util.go +++ b/client/rpc/client_util.go @@ -22,7 +22,6 @@ import ( 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/txs" ) @@ -89,7 +88,7 @@ func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, return } else if pubkey != "" { if addr != "" { - logging.InfoMsg(nodeClient.Logger(), "Both a public key and an address have been specified. The public key takes precedent.", + nodeClient.Logger().InfoMsg("Both a public key and an address have been specified. The public key takes precedent.", "public_key", pubkey, "address", addr, ) @@ -141,7 +140,7 @@ func checkCommon(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, return pub, amt, sequence, err2 } sequence = account.Sequence() + 1 - logging.TraceMsg(nodeClient.Logger(), "Fetch sequence from node", + nodeClient.Logger().TraceMsg("Fetch sequence from node", "sequence", sequence, "account address", address, ) diff --git a/client/websocket_client.go b/client/websocket_client.go index 41ba931c3f689a2ba2a7af65631ae047d9b56f68..fe08dde89d912394e422cccd9e1f33f068f938a0 100644 --- a/client/websocket_client.go +++ b/client/websocket_client.go @@ -25,7 +25,6 @@ import ( exe_events "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/rpc" tm_client "github.com/hyperledger/burrow/rpc/tm/client" "github.com/hyperledger/burrow/txs" @@ -51,7 +50,7 @@ var _ NodeWebsocketClient = (*burrowNodeWebsocketClient)(nil) type burrowNodeWebsocketClient struct { // TODO: assert no memory leak on closing with open websocket tendermintWebsocket *rpcclient.WSClient - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Subscribe to an eventid @@ -107,7 +106,7 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( case response := <-burrowNodeWebsocketClient.tendermintWebsocket.ResponsesCh: if response.Error != nil { - logging.InfoMsg(burrowNodeWebsocketClient.logger, + burrowNodeWebsocketClient.logger.InfoMsg( "Error received on websocket channel", structure.ErrorKey, err) continue } @@ -117,12 +116,12 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( resultSubscribe := new(rpc.ResultSubscribe) err = json.Unmarshal(response.Result, resultSubscribe) if err != nil { - logging.InfoMsg(burrowNodeWebsocketClient.logger, "Unable to unmarshal ResultSubscribe", + burrowNodeWebsocketClient.logger.InfoMsg("Unable to unmarshal ResultSubscribe", structure.ErrorKey, err) continue } // TODO: collect subscription IDs, push into channel and on completion - logging.InfoMsg(burrowNodeWebsocketClient.logger, "Received confirmation for event", + burrowNodeWebsocketClient.logger.InfoMsg("Received confirmation for event", "event", resultSubscribe.EventID, "subscription_id", resultSubscribe.SubscriptionID) @@ -130,14 +129,14 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( resultEvent := new(rpc.ResultEvent) err = json.Unmarshal(response.Result, resultEvent) if err != nil { - logging.InfoMsg(burrowNodeWebsocketClient.logger, "Unable to unmarshal ResultEvent", + burrowNodeWebsocketClient.logger.InfoMsg("Unable to unmarshal ResultEvent", structure.ErrorKey, err) continue } blockData := resultEvent.EventDataNewBlock() if blockData != nil { latestBlockHash = blockData.Block.Hash() - logging.TraceMsg(burrowNodeWebsocketClient.logger, "Registered new block", + burrowNodeWebsocketClient.logger.TraceMsg("Registered new block", "block", blockData.Block, "latest_block_hash", latestBlockHash, ) @@ -147,7 +146,7 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( resultEvent := new(rpc.ResultEvent) err = json.Unmarshal(response.Result, resultEvent) if err != nil { - logging.InfoMsg(burrowNodeWebsocketClient.logger, "Unable to unmarshal ResultEvent", + burrowNodeWebsocketClient.logger.InfoMsg("Unable to unmarshal ResultEvent", structure.ErrorKey, err) continue } @@ -165,7 +164,7 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( } if !bytes.Equal(eventDataTx.Tx.Hash(chainId), tx.Hash(chainId)) { - logging.TraceMsg(burrowNodeWebsocketClient.logger, "Received different event", + burrowNodeWebsocketClient.logger.TraceMsg("Received different event", // TODO: consider re-implementing TxID again, or other more clear debug "received transaction event", eventDataTx.Tx.Hash(chainId)) continue @@ -191,7 +190,7 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( return default: - logging.InfoMsg(burrowNodeWebsocketClient.logger, "Received unsolicited response", + burrowNodeWebsocketClient.logger.InfoMsg("Received unsolicited response", "response_id", response.ID, "expected_response_id", tm_client.EventResponseID(eventID)) } diff --git a/cmd/burrow/main.go b/cmd/burrow/main.go index 146a5d64a4adbc6f0542f5399679050e212fbdab..61c270aee1928791df6b64e372978520844ef1ad 100644 --- a/cmd/burrow/main.go +++ b/cmd/burrow/main.go @@ -8,12 +8,13 @@ import ( "github.com/hyperledger/burrow/config" "github.com/hyperledger/burrow/config/source" + "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/genesis/spec" "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/logging" logging_config "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/logging/config/presets" - "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/project" "github.com/jawher/mow.cli" ) @@ -33,7 +34,7 @@ func main() { burrow.Action = func() { if *versionOpt { - fmt.Println(project.History.CurrentVersion().String()) + fmt.Println(project.FullVersion()) os.Exit(0) } // We need to reflect on whether this obscures where values are coming from @@ -137,8 +138,13 @@ func main() { describeLoggingOpt := cmd.BoolOpt("describe-logging", false, "Print an exhaustive list of logging instructions available with the --logging option") + debugOpt := cmd.BoolOpt("d debug", false, "Include maximal debug options in config "+ + "including logging opcodes and dumping EVM tokens to disk these can be later pruned from the "+ + "generated config.") + 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]" + "[--validator-index=<index>] [--toml-in] [--json-out] [--logging=<logging program>] " + + "[--describe-logging] [--debug]" cmd.Action = func() { conf := config.DefaultBurrowConfig() @@ -172,7 +178,7 @@ func main() { if err != nil { fatalf("could not read GenesisSpec: %v", err) } - keyClient := keys.NewKeyClient(conf.Keys.URL, loggers.NewNoopInfoTraceLogger()) + keyClient := keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger()) conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient) if err != nil { fatalf("could not realise GenesisSpec: %v", err) @@ -213,6 +219,12 @@ func main() { } } + if *debugOpt { + conf.Execution = &execution.ExecutionConfig{ + VMOptions: []execution.VMOption{execution.DumpTokens, execution.DebugOpcodes}, + } + } + if *jsonOutOpt { os.Stdout.WriteString(conf.JSONString()) } else { diff --git a/config/config.go b/config/config.go index 91c5331b490be27c398e9e8ce528a894092d63a7..160f240632db14485053c0f20a91a77f3678e97e 100644 --- a/config/config.go +++ b/config/config.go @@ -10,6 +10,7 @@ import ( "github.com/hyperledger/burrow/consensus/tendermint" "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/keys" logging_config "github.com/hyperledger/burrow/logging/config" @@ -25,6 +26,7 @@ type BurrowConfig struct { ValidatorAddress *acm.Address `json:",omitempty" toml:",omitempty"` GenesisDoc *genesis.GenesisDoc `json:",omitempty" toml:",omitempty"` Tendermint *tendermint.BurrowTendermintConfig `json:",omitempty" toml:",omitempty"` + Execution *execution.ExecutionConfig `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"` @@ -57,7 +59,17 @@ func (conf *BurrowConfig) Kernel(ctx context.Context) (*core.Kernel, error) { } privValidator := validator.NewPrivValidatorMemory(val, keys.Signer(keyClient, val.Address())) - return core.NewKernel(ctx, privValidator, conf.GenesisDoc, conf.Tendermint.TendermintConfig(), conf.RPC, logger) + var exeOptions []execution.ExecutionOption + if conf.Execution != nil { + var err error + exeOptions, err = conf.Execution.ExecutionOptions() + if err != nil { + return nil, err + } + } + + return core.NewKernel(ctx, privValidator, conf.GenesisDoc, conf.Tendermint.TendermintConfig(), conf.RPC, exeOptions, + logger) } func (conf *BurrowConfig) JSONString() string { diff --git a/consensus/tendermint/abci/app.go b/consensus/tendermint/abci/app.go index ee549575566b2420d0f53214d94f97d6a3ce63c5..1bc0700c3cafbd9a214cbe42350c812fcdd66391 100644 --- a/consensus/tendermint/abci/app.go +++ b/consensus/tendermint/abci/app.go @@ -10,7 +10,6 @@ import ( "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/project" "github.com/hyperledger/burrow/txs" abci_types "github.com/tendermint/abci/types" @@ -30,19 +29,19 @@ type abciApp struct { // Utility txDecoder txs.Decoder // Logging - logger logging_types.InfoTraceLogger + logger *logging.Logger } func NewApp(blockchain bcm.MutableBlockchain, checker execution.BatchExecutor, committer execution.BatchCommitter, - logger logging_types.InfoTraceLogger) abci_types.Application { + logger *logging.Logger) 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"), + logger: logger.WithScope("abci.NewApp").With(structure.ComponentKey, "ABCI_App"), } } @@ -73,7 +72,7 @@ func (app *abciApp) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { defer app.mtx.Unlock() tx, err := app.txDecoder.DecodeTx(txBytes) if err != nil { - logging.TraceMsg(app.logger, "CheckTx decoding error", + app.logger.TraceMsg("CheckTx decoding error", "tag", "CheckTx", structure.ErrorKey, err) return abci_types.ResponseCheckTx{ @@ -86,7 +85,7 @@ func (app *abciApp) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { err = app.checker.Execute(tx) if err != nil { - logging.TraceMsg(app.logger, "CheckTx execution error", + app.logger.TraceMsg("CheckTx execution error", structure.ErrorKey, err, "tag", "CheckTx", "tx_hash", receipt.TxHash, @@ -98,7 +97,7 @@ func (app *abciApp) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { } receiptBytes := wire.BinaryBytes(receipt) - logging.TraceMsg(app.logger, "CheckTx success", + app.logger.TraceMsg("CheckTx success", "tag", "CheckTx", "tx_hash", receipt.TxHash, "creates_contract", receipt.CreatesContract) @@ -124,7 +123,7 @@ func (app *abciApp) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { defer app.mtx.Unlock() tx, err := app.txDecoder.DecodeTx(txBytes) if err != nil { - logging.TraceMsg(app.logger, "DeliverTx decoding error", + app.logger.TraceMsg("DeliverTx decoding error", "tag", "DeliverTx", structure.ErrorKey, err) return abci_types.ResponseDeliverTx{ @@ -136,7 +135,7 @@ func (app *abciApp) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { receipt := txs.GenerateReceipt(app.blockchain.ChainID(), tx) err = app.committer.Execute(tx) if err != nil { - logging.TraceMsg(app.logger, "DeliverTx execution error", + app.logger.TraceMsg("DeliverTx execution error", structure.ErrorKey, err, "tag", "DeliverTx", "tx_hash", receipt.TxHash, @@ -147,7 +146,7 @@ func (app *abciApp) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { } } - logging.TraceMsg(app.logger, "DeliverTx success", + app.logger.TraceMsg("DeliverTx success", "tag", "DeliverTx", "tx_hash", receipt.TxHash, "creates_contract", receipt.CreatesContract) @@ -168,7 +167,7 @@ func (app *abciApp) Commit() abci_types.ResponseCommit { app.mtx.Lock() defer app.mtx.Unlock() tip := app.blockchain.Tip() - logging.InfoMsg(app.logger, "Committing block", + app.logger.InfoMsg("Committing block", "tag", "Commit", structure.ScopeKey, "Commit()", "block_height", app.block.Header.Height, @@ -204,7 +203,7 @@ func (app *abciApp) Commit() abci_types.ResponseCommit { // Perform a sanity check our block height if app.blockchain.LastBlockHeight() != uint64(app.block.Header.Height) { - logging.InfoMsg(app.logger, "Burrow block height disagrees with Tendermint block height", + app.logger.InfoMsg("Burrow block height disagrees with Tendermint block height", structure.ScopeKey, "Commit()", "burrow_height", app.blockchain.LastBlockHeight(), "tendermint_height", app.block.Header.Height) diff --git a/consensus/tendermint/logger.go b/consensus/tendermint/logger.go index c42f46a4ca679890b6f67c5fd3f3978949bf0f8e..6022f47307eade096ddbd9e92c492cda12c9ae85 100644 --- a/consensus/tendermint/logger.go +++ b/consensus/tendermint/logger.go @@ -2,30 +2,29 @@ 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 + logger *logging.Logger } -func NewLogger(logger logging_types.InfoTraceLogger) *tendermintLogger { +func NewLogger(logger *logging.Logger) *tendermintLogger { return &tendermintLogger{ logger: logger, } } func (tml *tendermintLogger) Info(msg string, keyvals ...interface{}) { - logging.InfoMsg(tml.logger, msg, keyvals...) + tml.logger.InfoMsg(msg, keyvals...) } func (tml *tendermintLogger) Error(msg string, keyvals ...interface{}) { - logging.InfoMsg(tml.logger, msg, keyvals...) + tml.logger.InfoMsg(msg, keyvals...) } func (tml *tendermintLogger) Debug(msg string, keyvals ...interface{}) { - logging.TraceMsg(tml.logger, msg, keyvals...) + tml.logger.TraceMsg(msg, keyvals...) } func (tml *tendermintLogger) With(keyvals ...interface{}) log.Logger { diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go index 7a3af288ad8149f76052c1cabbb4ce467e2763c5..27fa18ee4cdcf59e5589edfd7b86c99cae68d627 100644 --- a/consensus/tendermint/tendermint.go +++ b/consensus/tendermint/tendermint.go @@ -10,8 +10,8 @@ import ( "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/genesis" + "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/tendermint/config" @@ -49,7 +49,7 @@ func NewNode( blockchain bcm.MutableBlockchain, checker execution.BatchExecutor, committer execution.BatchCommitter, - logger logging_types.InfoTraceLogger) (*Node, error) { + logger *logging.Logger) (*Node, error) { var err error // disable Tendermint's RPC diff --git a/core/kernel.go b/core/kernel.go index f00cd7a9b17d0d03f690ddcd2c135a6c33b63795..c033098e393abbc52dc8254e7acb6a652863f2eb 100644 --- a/core/kernel.go +++ b/core/kernel.go @@ -17,12 +17,15 @@ package core import ( "context" "fmt" + "net/http" + _ "net/http/pprof" "os" "os/signal" "sync" "syscall" "time" + kitlog "github.com/go-kit/kit/log" bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/consensus/tendermint" "github.com/hyperledger/burrow/consensus/tendermint/query" @@ -31,7 +34,6 @@ import ( "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/process" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/tm" @@ -43,26 +45,31 @@ import ( dbm "github.com/tendermint/tmlibs/db" ) -const CooldownMilliseconds = 1000 -const ServerShutdownTimeoutMilliseconds = 1000 +const ( + CooldownMilliseconds = 1000 + ServerShutdownTimeoutMilliseconds = 1000 + LoggingCallerDepth = 5 +) // Kernel is the root structure of Burrow type Kernel struct { // Expose these public-facing interfaces to allow programmatic extension of the Kernel by other projects Emitter event.Emitter - Service rpc.Service + Service *rpc.Service Launchers []process.Launcher - Logger logging_types.InfoTraceLogger + Logger *logging.Logger processes map[string]process.Process shutdownNotify chan struct{} shutdownOnce sync.Once } func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesisDoc *genesis.GenesisDoc, - tmConf *tm_config.Config, rpcConfig *rpc.RPCConfig, logger logging_types.InfoTraceLogger) (*Kernel, error) { + tmConf *tm_config.Config, rpcConfig *rpc.RPCConfig, exeOptions []execution.ExecutionOption, + logger *logging.Logger) (*Kernel, error) { - logger = logging.WithScope(logger, "NewKernel") - var err error + logger = logger.WithScope("NewKernel()").With(structure.TimeKey, kitlog.DefaultTimestampUTC) + tmLogger := logger.With(structure.CallerKey, kitlog.Caller(LoggingCallerDepth+1)) + logger = logger.WithInfo(structure.CallerKey, kitlog.Caller(LoggingCallerDepth)) stateDB := dbm.NewDB("burrow_state", dbm.GoLevelDBBackendStr, tmConf.DBDir()) blockchain, err := bcm.LoadOrNewBlockchain(stateDB, genesisDoc, logger) @@ -86,8 +93,8 @@ func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesi checker := execution.NewBatchChecker(state, tmGenesisDoc.ChainID, blockchain, logger) emitter := event.NewEmitter(logger) - committer := execution.NewBatchCommitter(state, tmGenesisDoc.ChainID, blockchain, emitter, logger) - tmNode, err := tendermint.NewNode(tmConf, privValidator, tmGenesisDoc, blockchain, checker, committer, logger) + committer := execution.NewBatchCommitter(state, tmGenesisDoc.ChainID, blockchain, emitter, logger, exeOptions...) + tmNode, err := tendermint.NewNode(tmConf, privValidator, tmGenesisDoc, blockchain, checker, committer, tmLogger) if err != nil { return nil, err @@ -96,15 +103,25 @@ func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesi transactor := execution.NewTransactor(blockchain, state, emitter, 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(ctx, state, state, emitter, blockchain, transactor, query.NewNodeView(tmNode, txCodec), logger) launchers := []process.Launcher{ + { + Name: "Profiling Server", + Disabled: rpcConfig.Profiler.Disabled, + Launch: func() (process.Process, error) { + debugServer := &http.Server{ + Addr: ":6060", + } + go func() { + err := debugServer.ListenAndServe() + if err != nil { + logger.InfoMsg("Error from pprof debug server", structure.ErrorKey, err) + } + }() + return debugServer, nil + }, + }, { Name: "Database", Launch: func() (process.Process, error) { @@ -139,7 +156,7 @@ func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesi case <-ctx.Done(): return ctx.Err() case <-tmNode.Quit: - logging.InfoMsg(logger, "Tendermint Node has quit, closing DB connections...") + logger.InfoMsg("Tendermint Node has quit, closing DB connections...") // Close tendermint database connections using our wrapper tmNode.Close() return nil @@ -149,7 +166,8 @@ func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesi }, }, { - Name: "RPC/tm", + Name: "RPC/tm", + Disabled: rpcConfig.TM.Disabled, Launch: func() (process.Process, error) { listener, err := tm.StartServer(service, "/websocket", rpcConfig.TM.ListenAddress, emitter, logger) if err != nil { @@ -159,7 +177,8 @@ func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesi }, }, { - Name: "RPC/V0", + Name: "RPC/V0", + Disabled: rpcConfig.V0.Disabled, Launch: func() (process.Process, error) { codec := v0.NewTCodec() jsonServer := v0.NewJSONServer(v0.NewJSONService(codec, service, logger)) @@ -216,7 +235,7 @@ func (kern *Kernel) supervise() { signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL) sig := <-signals - logging.InfoMsg(kern.Logger, fmt.Sprintf("Caught %v signal so shutting down", sig), + kern.Logger.InfoMsg(fmt.Sprintf("Caught %v signal so shutting down", sig), "signal", sig.String()) kern.Shutdown(context.Background()) } @@ -224,9 +243,9 @@ func (kern *Kernel) supervise() { // Stop the kernel allowing for a graceful shutdown of components in order func (kern *Kernel) Shutdown(ctx context.Context) (err error) { kern.shutdownOnce.Do(func() { - logger := logging.WithScope(kern.Logger, "Shutdown") - logging.InfoMsg(logger, "Attempting graceful shutdown...") - logging.InfoMsg(logger, "Shutting down servers") + logger := kern.Logger.WithScope("Shutdown") + logger.InfoMsg("Attempting graceful shutdown...") + logger.InfoMsg("Shutting down servers") ctx, cancel := context.WithTimeout(ctx, ServerShutdownTimeoutMilliseconds*time.Millisecond) defer cancel() // Shutdown servers in reverse order to boot @@ -234,10 +253,10 @@ func (kern *Kernel) Shutdown(ctx context.Context) (err error) { name := kern.Launchers[i].Name srvr, ok := kern.processes[name] if ok { - logging.InfoMsg(logger, "Shutting down server", "server_name", name) + logger.InfoMsg("Shutting down server", "server_name", name) sErr := srvr.Shutdown(ctx) if sErr != nil { - logging.InfoMsg(logger, "Failed to shutdown server", + logger.InfoMsg("Failed to shutdown server", "server_name", name, structure.ErrorKey, sErr) if err == nil { @@ -246,8 +265,9 @@ func (kern *Kernel) Shutdown(ctx context.Context) (err error) { } } } - logging.InfoMsg(logger, "Shutdown complete") - logging.Sync(kern.Logger) + logger.InfoMsg("Shutdown complete") + structure.Sync(kern.Logger.Info) + structure.Sync(kern.Logger.Trace) // 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) diff --git a/core/kernel_test.go b/core/kernel_test.go index 427461075c7118fd5dde4c3364168fa4abe599f0..aa1af7efc1aefc498cef965014a6d163d6e7f5ab 100644 --- a/core/kernel_test.go +++ b/core/kernel_test.go @@ -12,8 +12,7 @@ import ( "github.com/hyperledger/burrow/consensus/tendermint" "github.com/hyperledger/burrow/consensus/tendermint/validator" "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/logging/loggers" - logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/stretchr/testify/assert" tm_config "github.com/tendermint/tendermint/config" @@ -28,7 +27,7 @@ func TestBootThenShutdown(t *testing.T) { os.Chdir(testDir) tmConf := tm_config.DefaultConfig() //logger, _, _ := lifecycle.NewStdErrLogger() - logger := loggers.NewNoopInfoTraceLogger() + logger := logging.NewNoopLogger() genesisDoc, _, privateValidators := genesis.NewDeterministicGenesis(123).GenesisDoc(1, true, 1000, 1, true, 1000) privValidator := validator.NewPrivValidatorMemory(privateValidators[0], privateValidators[0]) assert.NoError(t, bootWaitBlocksShutdown(privValidator, genesisDoc, tmConf, logger, nil)) @@ -40,7 +39,7 @@ func TestBootShutdownResume(t *testing.T) { os.Chdir(testDir) tmConf := tm_config.DefaultConfig() //logger, _, _ := lifecycle.NewStdErrLogger() - logger := loggers.NewNoopInfoTraceLogger() + logger := logging.NewNoopLogger() genesisDoc, _, privateValidators := genesis.NewDeterministicGenesis(123).GenesisDoc(1, true, 1000, 1, true, 1000) privValidator := validator.NewPrivValidatorMemory(privateValidators[0], privateValidators[0]) @@ -62,10 +61,10 @@ func TestBootShutdownResume(t *testing.T) { } func bootWaitBlocksShutdown(privValidator tm_types.PrivValidator, genesisDoc *genesis.GenesisDoc, - tmConf *tm_config.Config, logger logging_types.InfoTraceLogger, + tmConf *tm_config.Config, logger *logging.Logger, blockChecker func(block *tm_types.EventDataNewBlock) (cont bool)) error { - kern, err := NewKernel(context.Background(), privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), logger) + kern, err := NewKernel(context.Background(), privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), nil, logger) if err != nil { return err } diff --git a/event/cache_test.go b/event/cache_test.go index 877d04387a2caef15ab0ba135181babf0a7cf44a..276d323c47ce444a505bfec63ef4d131fb0f2ef0 100644 --- a/event/cache_test.go +++ b/event/cache_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging" "github.com/stretchr/testify/assert" ) @@ -17,7 +17,7 @@ func TestEventCache_Flush(t *testing.T) { errCh := make(chan error) flushed := false - em := NewEmitter(loggers.NewNoopInfoTraceLogger()) + em := NewEmitter(logging.NewNoopLogger()) SubscribeCallback(ctx, em, "nothingness", NewQueryBuilder(), func(message interface{}) bool { // Check against sending a buffer of zeroed messages if message == nil { @@ -60,7 +60,7 @@ func TestEventCache_Flush(t *testing.T) { } func TestEventCacheGrowth(t *testing.T) { - evc := NewEventCache(NewEmitter(loggers.NewNoopInfoTraceLogger())) + evc := NewEventCache(NewEmitter(logging.NewNoopLogger())) fireNEvents(evc, 100) c := cap(evc.events) diff --git a/event/convention_test.go b/event/convention_test.go index 31e9571b148affcac0639cf02f8533c283c3d496..35c8f7d675cc2ddb870893b5545955e4d1b3d451 100644 --- a/event/convention_test.go +++ b/event/convention_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging" "github.com/stretchr/testify/assert" ) func TestSubscribeCallback(t *testing.T) { ctx := context.Background() - em := NewEmitter(loggers.NewNoopInfoTraceLogger()) + em := NewEmitter(logging.NewNoopLogger()) ch := make(chan interface{}) SubscribeCallback(ctx, em, "TestSubscribeCallback", MatchAllQueryable(), func(msg interface{}) bool { ch <- msg diff --git a/event/emitter.go b/event/emitter.go index d41d0fd1b30a4a798db16f000bd099f17fc17f81..a09d158f9fb830511a00a9fb15d5bbb395d72478 100644 --- a/event/emitter.go +++ b/event/emitter.go @@ -21,8 +21,8 @@ import ( "fmt" "strings" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/process" "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/pubsub" @@ -52,10 +52,10 @@ type Emitter interface { type emitter struct { common.BaseService pubsubServer *pubsub.Server - logger logging_types.InfoTraceLogger + logger *logging.Logger } -func NewEmitter(logger logging_types.InfoTraceLogger) Emitter { +func NewEmitter(logger *logging.Logger) Emitter { pubsubServer := pubsub.NewServer(pubsub.BufferCapacity(DefaultEventBufferCapacity)) pubsubServer.BaseService = *common.NewBaseService(nil, "Emitter", pubsubServer) pubsubServer.Start() diff --git a/event/emitter_test.go b/event/emitter_test.go index 473bf4c5d5b8870a074257fb130cde1dab6ad218..c881cc53f3fede54cef50d2e913a25d158fe91a7 100644 --- a/event/emitter_test.go +++ b/event/emitter_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestEmitter(t *testing.T) { - em := NewEmitter(loggers.NewNoopInfoTraceLogger()) + em := NewEmitter(logging.NewNoopLogger()) ctx := context.Background() out := make(chan interface{}) diff --git a/execution/config.go b/execution/config.go new file mode 100644 index 0000000000000000000000000000000000000000..f90b6a02c529fcd5aa67fee1abf1011f725673d2 --- /dev/null +++ b/execution/config.go @@ -0,0 +1,39 @@ +package execution + +import ( + "fmt" + + "github.com/hyperledger/burrow/execution/evm" +) + +type VMOption string + +const ( + DebugOpcodes VMOption = "DebugOpcodes" + DumpTokens VMOption = "DumpTokens" +) + +type ExecutionConfig struct { + VMOptions []VMOption `json:",omitempty" toml:",omitempty"` +} + +func DefaultExecutionConfig() *ExecutionConfig { + return &ExecutionConfig{} +} + +func (ec *ExecutionConfig) ExecutionOptions() ([]ExecutionOption, error) { + var exeOptions []ExecutionOption + var vmOptions []func(*evm.VM) + for _, option := range ec.VMOptions { + switch option { + case DebugOpcodes: + vmOptions = append(vmOptions, evm.DebugOpcodes) + case DumpTokens: + vmOptions = append(vmOptions, evm.DumpTokens) + default: + return nil, fmt.Errorf("VM option '%s' not recognised", option) + } + } + exeOptions = append(exeOptions, VMOptions(vmOptions...)) + return exeOptions, nil +} diff --git a/execution/evm/accounts.go b/execution/evm/accounts.go index 618557aaa9aabca3d6f4982ce81ad793f5838521..800e6269b1afb7f39c2fcf77a7e79722cc5d4147 100644 --- a/execution/evm/accounts.go +++ b/execution/evm/accounts.go @@ -3,17 +3,16 @@ package evm import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" 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, - logger logging_types.InfoTraceLogger) acm.MutableAccount { + logger *logging.Logger) acm.MutableAccount { // Generate an address sequence := creator.Sequence() - logging.TraceMsg(logger, "Incrementing sequence number in DeriveNewAccount()", + logger.TraceMsg("Incrementing sequence number in DeriveNewAccount()", "tag", "sequence", "account", creator.Address(), "old_sequence", sequence, @@ -23,11 +22,11 @@ func DeriveNewAccount(creator acm.MutableAccount, permissions ptypes.AccountPerm addr := acm.NewContractAddress(creator.Address(), sequence) // Create account from address. - return (&acm.ConcreteAccount{ + return acm.ConcreteAccount{ Address: addr, Balance: 0, Code: nil, Sequence: 0, Permissions: permissions, - }).MutableAccount() + }.MutableAccount() } diff --git a/execution/evm/asm/opcodes.go b/execution/evm/asm/opcodes.go index 0a4e353199718db0266c542e8e59b176704ca27e..0db53834facf9d6e682f06de1f58ccae99c321af 100644 --- a/execution/evm/asm/opcodes.go +++ b/execution/evm/asm/opcodes.go @@ -49,6 +49,9 @@ const ( XOR NOT BYTE + SHL + SHR + SAR SHA3 = 0x20 ) @@ -68,6 +71,8 @@ const ( GASPRICE_DEPRECATED EXTCODESIZE EXTCODECOPY + RETURNDATASIZE + RETURNDATACOPY ) const ( @@ -182,6 +187,8 @@ const ( DELEGATECALL // 0x70 range - other + STATICCALL = 0xfa + REVERT = 0xfd SELFDESTRUCT = 0xff ) @@ -210,6 +217,9 @@ var opCodeNames = map[OpCode]string{ OR: "OR", XOR: "XOR", BYTE: "BYTE", + SHL: "SHL", + SHR: "SHR", + SAR: "SAR", ADDMOD: "ADDMOD", MULMOD: "MULMOD", @@ -238,6 +248,8 @@ var opCodeNames = map[OpCode]string{ GASLIMIT: "GASLIMIT", EXTCODESIZE: "EXTCODESIZE", EXTCODECOPY: "EXTCODECOPY", + RETURNDATASIZE: "RETURNDATASIZE", + RETURNDATACOPY: "RETURNDATACOPY", // 0x50 range - 'storage' and execution POP: "POP", @@ -332,8 +344,9 @@ var opCodeNames = map[OpCode]string{ RETURN: "RETURN", CALLCODE: "CALLCODE", DELEGATECALL: "DELEGATECALL", - + STATICCALL: "STATICCALL", // 0x70 range - other + REVERT: "REVERT", SELFDESTRUCT: "SELFDESTRUCT", } diff --git a/execution/evm/fake_app_state.go b/execution/evm/fake_app_state.go index 09a985ca4efedc76265e3b829deb061441586b90..7efbe82cd9e13d90ca7fa21b57d381f47f781f39 100644 --- a/execution/evm/fake_app_state.go +++ b/execution/evm/fake_app_state.go @@ -20,6 +20,7 @@ import ( "bytes" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" . "github.com/hyperledger/burrow/binary" ) @@ -28,7 +29,7 @@ type FakeAppState struct { storage map[string]Word256 } -var _ acm.StateWriter = &FakeAppState{} +var _ state.Writer = &FakeAppState{} func (fas *FakeAppState) GetAccount(addr acm.Address) (acm.Account, error) { account := fas.accounts[addr] diff --git a/execution/evm/gas.go b/execution/evm/gas.go index a764a5a57f6985f20cda9dfc172c83ec27662afd..dff920c1586f01deb8144a1105f7ddb6532df830 100644 --- a/execution/evm/gas.go +++ b/execution/evm/gas.go @@ -18,6 +18,7 @@ const ( GasSha3 uint64 = 1 GasGetAccount uint64 = 1 GasStorageUpdate uint64 = 1 + GasCreateAccount uint64 = 1 GasBaseOp uint64 = 0 // TODO: make this 1 GasStackOp uint64 = 1 diff --git a/execution/evm/log_event_test.go b/execution/evm/log_event_test.go index 496e4e61068a9a7874fda6545b21623f3c224295..3a054d51afd1281da7c51ef66aab91a68425337a 100644 --- a/execution/evm/log_event_test.go +++ b/execution/evm/log_event_test.go @@ -26,7 +26,7 @@ import ( "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" + "github.com/hyperledger/burrow/logging" "github.com/stretchr/testify/require" ) @@ -52,9 +52,9 @@ func TestLog4(t *testing.T) { st.accounts[account1.Address()] = account1 st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + ourVm := NewVM(st, newParams(), acm.ZeroAddress, nil, logger) - emitter := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) + emitter := event.NewEmitter(logging.NewNoopLogger()) ch := make(chan *events.EventDataLog) diff --git a/execution/evm/native.go b/execution/evm/native.go index 198977c4ccd3208c5522bd2d9aeaa8143665b0a0..70f00ed6d2a25c31a42de210f29161f0905cb68b 100644 --- a/execution/evm/native.go +++ b/execution/evm/native.go @@ -18,8 +18,9 @@ import ( "crypto/sha256" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" . "github.com/hyperledger/burrow/binary" - logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/logging" "golang.org/x/crypto/ripemd160" ) @@ -53,8 +54,8 @@ func registerNativeContracts() { //----------------------------------------------------------------------------- -type NativeContract func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) +type NativeContract func(state state.Writer, caller acm.Account, input []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) /* Removed due to C dependency func ecrecoverFunc(state State, caller *acm.Account, input []byte, gas *int64) (output []byte, err error) { @@ -73,14 +74,15 @@ func ecrecoverFunc(state State, caller *acm.Account, input []byte, gas *int64) ( recovered, err := secp256k1.RecoverPubkey(hash, sig) if err != nil { return nil, err +OH NO STOCASTIC CAT CODING!!!! } hashed := sha3.Sha3(recovered[1:]) return LeftPadBytes(hashed, 32), nil } */ -func sha256Func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func sha256Func(state state.Writer, caller acm.Account, input []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { // Deduct gas gasRequired := uint64((len(input)+31)/32)*GasSha256Word + GasSha256Base if *gas < gasRequired { @@ -95,8 +97,8 @@ func sha256Func(state acm.StateWriter, caller acm.Account, input []byte, gas *ui return hasher.Sum(nil), nil } -func ripemd160Func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func ripemd160Func(state state.Writer, caller acm.Account, input []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { // Deduct gas gasRequired := uint64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base if *gas < gasRequired { @@ -111,8 +113,8 @@ func ripemd160Func(state acm.StateWriter, caller acm.Account, input []byte, gas return LeftPadBytes(hasher.Sum(nil), 32), nil } -func identityFunc(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func identityFunc(state state.Writer, caller acm.Account, input []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { // Deduct gas gasRequired := uint64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase if *gas < gasRequired { diff --git a/execution/evm/options.go b/execution/evm/options.go new file mode 100644 index 0000000000000000000000000000000000000000..5081729daf98d75b1e5b46567451d9eb4578eb42 --- /dev/null +++ b/execution/evm/options.go @@ -0,0 +1,15 @@ +package evm + +func MemoryProvider(memoryProvider func() Memory) func(*VM) { + return func(vm *VM) { + vm.memoryProvider = memoryProvider + } +} + +func DebugOpcodes(vm *VM) { + vm.debugOpcodes = true +} + +func DumpTokens(vm *VM) { + vm.dumpTokens = true +} diff --git a/execution/evm/snative.go b/execution/evm/snative.go index 4b8ac87cbed5c22a449d034070b256d05c4bb4bb..34375a0adf50b456ad74383a31f0936f0e1f223c 100644 --- a/execution/evm/snative.go +++ b/execution/evm/snative.go @@ -20,12 +20,12 @@ import ( "strings" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" . "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" ) @@ -226,11 +226,20 @@ func NewSNativeContract(comment, name string, } } +type ErrLacksSNativePermission struct { + Address acm.Address + SNative string +} + +func (e ErrLacksSNativePermission) Error() string { + return fmt.Sprintf("account %s does not have SNative function call permission: %s", e.Address, e.SNative) +} + // 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(state acm.StateWriter, caller acm.Account, - args []byte, gas *uint64, logger logging_types.InfoTraceLogger) (output []byte, err error) { +func (contract *SNativeContractDescription) Dispatch(state state.Writer, caller acm.Account, + args []byte, gas *uint64, logger *logging.Logger) (output []byte, err error) { logger = logger.With(structure.ScopeKey, "Dispatch", "contract_name", contract.Name) @@ -244,7 +253,7 @@ func (contract *SNativeContractDescription) Dispatch(state acm.StateWriter, call return nil, err } - logging.TraceMsg(logger, "Dispatching to function", + logger.TraceMsg("Dispatching to function", "caller", caller.Address(), "function_name", function.Name) @@ -341,8 +350,8 @@ 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(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func hasBase(state state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { addrWord256, permNum := returnTwoArgs(args) address := acm.AddressFromWord256(addrWord256) @@ -359,7 +368,7 @@ func hasBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64 } hasPermission := HasPermission(state, acc, permN) permInt := byteFromBool(hasPermission) - logger.Trace("function", "hasBase", + logger.Trace.Log("function", "hasBase", "address", address.String(), "account_base_permissions", acc.Permissions().Base, "perm_flag", fmt.Sprintf("%b", permN), @@ -367,12 +376,12 @@ func hasBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64 return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func setBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func setBase(stateWriter state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { addrWord256, permNum, permVal := returnThreeArgs(args) address := acm.AddressFromWord256(addrWord256) - acc, err := acm.GetMutableAccount(state, address) + acc, err := state.GetMutableAccount(stateWriter, address) if err != nil { return nil, err } @@ -387,19 +396,19 @@ func setBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64 if err = acc.MutablePermissions().Base.Set(permN, permV); err != nil { return nil, err } - state.UpdateAccount(acc) - logger.Trace("function", "setBase", "address", address.String(), + stateWriter.UpdateAccount(acc) + logger.Trace.Log("function", "setBase", "address", address.String(), "permission_flag", fmt.Sprintf("%b", permN), "permission_value", permV) - return effectivePermBytes(acc.Permissions().Base, globalPerms(state)), nil + return effectivePermBytes(acc.Permissions().Base, globalPerms(stateWriter)), nil } -func unsetBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func unsetBase(stateWriter state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { addrWord256, permNum := returnTwoArgs(args) address := acm.AddressFromWord256(addrWord256) - acc, err := acm.GetMutableAccount(state, address) + acc, err := state.GetMutableAccount(stateWriter, address) if err != nil { return nil, err } @@ -413,19 +422,19 @@ func unsetBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint if err = acc.MutablePermissions().Base.Unset(permN); err != nil { return nil, err } - state.UpdateAccount(acc) - logger.Trace("function", "unsetBase", "address", address.String(), + stateWriter.UpdateAccount(acc) + logger.Trace.Log("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 + return effectivePermBytes(acc.Permissions().Base, globalPerms(stateWriter)), nil } -func setGlobal(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func setGlobal(stateWriter state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { permNum, permVal := returnTwoArgs(args) - acc, err := acm.GetMutableAccount(state, permission.GlobalPermissionsAddress) + acc, err := state.GetMutableAccount(stateWriter, acm.GlobalPermissionsAddress) if err != nil { return nil, err } @@ -440,15 +449,15 @@ func setGlobal(state acm.StateWriter, caller acm.Account, args []byte, gas *uint if err = acc.MutablePermissions().Base.Set(permN, permV); err != nil { return nil, err } - state.UpdateAccount(acc) - logger.Trace("function", "setGlobal", + stateWriter.UpdateAccount(acc) + logger.Trace.Log("function", "setGlobal", "permission_flag", fmt.Sprintf("%b", permN), "permission_value", permV) return permBytes(acc.Permissions().Base.ResultantPerms()), nil } -func hasRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func hasRole(state state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { addrWord256, role := returnTwoArgs(args) address := acm.AddressFromWord256(addrWord256) @@ -462,18 +471,18 @@ func hasRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64 roleS := string(role.Bytes()) hasRole := acc.Permissions().HasRole(roleS) permInt := byteFromBool(hasRole) - logger.Trace("function", "hasRole", "address", address.String(), + logger.Trace.Log("function", "hasRole", "address", address.String(), "role", roleS, "has_role", hasRole) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func addRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func addRole(stateWriter state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { addrWord256, role := returnTwoArgs(args) address := acm.AddressFromWord256(addrWord256) - acc, err := acm.GetMutableAccount(state, address) + acc, err := state.GetMutableAccount(stateWriter, address) if err != nil { return nil, err } @@ -483,19 +492,19 @@ func addRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64 roleS := string(role.Bytes()) roleAdded := acc.MutablePermissions().AddRole(roleS) permInt := byteFromBool(roleAdded) - state.UpdateAccount(acc) - logger.Trace("function", "addRole", "address", address.String(), + stateWriter.UpdateAccount(acc) + logger.Trace.Log("function", "addRole", "address", address.String(), "role", roleS, "role_added", roleAdded) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func removeRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, - logger logging_types.InfoTraceLogger) (output []byte, err error) { +func removeRole(stateWriter state.Writer, caller acm.Account, args []byte, gas *uint64, + logger *logging.Logger) (output []byte, err error) { addrWord256, role := returnTwoArgs(args) address := acm.AddressFromWord256(addrWord256) - acc, err := acm.GetMutableAccount(state, address) + acc, err := state.GetMutableAccount(stateWriter, address) if err != nil { return nil, err } @@ -505,8 +514,8 @@ func removeRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uin roleS := string(role.Bytes()) roleRemoved := acc.MutablePermissions().RmRole(roleS) permInt := byteFromBool(roleRemoved) - state.UpdateAccount(acc) - logger.Trace("function", "removeRole", "address", address.String(), + stateWriter.UpdateAccount(acc) + logger.Trace.Log("function", "removeRole", "address", address.String(), "role", roleS, "role_removed", roleRemoved) return LeftPadWord256([]byte{permInt}).Bytes(), nil @@ -515,23 +524,14 @@ func removeRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uin //------------------------------------------------------------------------------------------------ // Errors and utility funcs -type ErrLacksSNativePermission struct { - Address acm.Address - SNative string -} - -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 <= permission.AllPermFlags } // Get the global BasePermissions -func globalPerms(state acm.StateWriter) ptypes.BasePermissions { - return permission.GlobalAccountPermissions(state).Base +func globalPerms(stateWriter state.Writer) ptypes.BasePermissions { + return state.GlobalAccountPermissions(stateWriter).Base } // Compute the effective permissions from an acm.Account's BasePermissions by diff --git a/execution/evm/stack.go b/execution/evm/stack.go index 2db8018355dab6635a48dcd246254d79780f01f5..3e9dfd5b8987707f68334e6b56f7044bc8fd409c 100644 --- a/execution/evm/stack.go +++ b/execution/evm/stack.go @@ -103,14 +103,20 @@ func (st *Stack) PopBytes() []byte { return st.Pop().Bytes() } -func (st *Stack) Pop64() int64 { +func (st *Stack) Pop64() (int64, error) { d := st.Pop() - return Int64FromWord256(d) + if Is64BitOverflow(d) { + return 0, fmt.Errorf("int64 overflow from word: %v", d) + } + return Int64FromWord256(d), nil } -func (st *Stack) PopU64() uint64 { +func (st *Stack) PopU64() (uint64, error) { d := st.Pop() - return Uint64FromWord256(d) + if Is64BitOverflow(d) { + return 0, fmt.Errorf("uint64 overflow from word: %v", d) + } + return Uint64FromWord256(d), nil } func (st *Stack) PopBigIntSigned() *big.Int { @@ -169,3 +175,12 @@ func (st *Stack) Print(n int) { } fmt.Println("#############") } + +func Is64BitOverflow(word Word256) bool { + for i := 0; i < len(word)-8; i++ { + if word[i] != 0 { + return true + } + } + return false +} diff --git a/execution/evm/vm.go b/execution/evm/vm.go index 98fd2943281ce862ed1bdcda0e7fc60eb811dd19..f94cf768ea702cde7fd208e7fb117cc96ab68d46 100644 --- a/execution/evm/vm.go +++ b/execution/evm/vm.go @@ -18,15 +18,18 @@ import ( "bytes" "errors" "fmt" + "io/ioutil" + "math/big" + "strings" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" . "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" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" ) @@ -45,6 +48,12 @@ var ( ErrDataStackUnderflow = errors.New("Data stack underflow") ErrInvalidContract = errors.New("Invalid contract") ErrNativeContractCodeCopy = errors.New("Tried to copy native contract code") + ErrExecutionReverted = errors.New("Execution reverted") +) + +const ( + dataStackCapacity = 1024 + callStackCapacity = 100 // TODO ensure usage. ) type ErrPermission struct { @@ -55,10 +64,36 @@ func (err ErrPermission) Error() string { return fmt.Sprintf("Contract does not have permission to %s", err.typ) } -const ( - dataStackCapacity = 1024 - callStackCapacity = 100 // TODO ensure usage. -) +type ErrNestedCall struct { + NestedError error + Caller acm.Address + Callee acm.Address + StackDepth int +} + +func (err ErrNestedCall) Error() string { + return fmt.Sprintf("error in nested call at depth %v: %s (callee) -> %s (caller): %v", + err.StackDepth, err.Callee, err.Caller, err.NestedError) +} + +type ErrCall struct { + CallError error + NestedErrors []ErrNestedCall +} + +func (err ErrCall) Error() string { + buf := new(bytes.Buffer) + buf.WriteString("call error: ") + buf.WriteString(err.CallError.Error()) + if len(err.NestedErrors) > 0 { + buf.WriteString(", nested call errors:\n") + for _, nestedErr := range err.NestedErrors { + buf.WriteString(nestedErr.Error()) + buf.WriteByte('\n') + } + } + return buf.String() +} type Params struct { BlockHeight uint64 @@ -68,31 +103,40 @@ type Params struct { } type VM struct { - state acm.StateWriter - memoryProvider func() Memory - params Params - origin acm.Address - txHash []byte - callDepth int - publisher event.Publisher - logger logging_types.InfoTraceLogger + stateWriter state.Writer + memoryProvider func() Memory + params Params + origin acm.Address + txHash []byte + stackDepth int + nestedCallErrors []ErrNestedCall + publisher event.Publisher + logger *logging.Logger + debugOpcodes bool + dumpTokens bool } -func NewVM(state acm.StateWriter, memoryProvider func() Memory, params Params, origin acm.Address, txid []byte, - logger logging_types.InfoTraceLogger) *VM { - return &VM{ - state: state, - memoryProvider: memoryProvider, +func NewVM(stateWriter state.Writer, params Params, origin acm.Address, txid []byte, + logger *logging.Logger, options ...func(*VM)) *VM { + vm := &VM{ + stateWriter: stateWriter, + memoryProvider: DefaultDynamicMemoryProvider, params: params, origin: origin, - callDepth: 0, + stackDepth: 0, txHash: txid, - logger: logging.WithScope(logger, "NewVM"), + logger: logger.WithScope("NewVM"), } + for _, option := range options { + option(vm) + } + return vm } func (vm *VM) Debugf(format string, a ...interface{}) { - logging.TraceMsg(vm.logger, fmt.Sprintf(format, a...), "tag", "vm_debug") + if vm.debugOpcodes { + vm.logger.TraceMsg(fmt.Sprintf(format, a...), "tag", "DebugOpcodes") + } } // satisfies go_events.Eventable @@ -106,8 +150,8 @@ func (vm *VM) SetPublisher(publisher event.Publisher) { // 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(state acm.StateWriter, acc acm.Account, perm ptypes.PermFlag) bool { - value, _ := acc.Permissions().Base.Compose(permission.GlobalAccountPermissions(state).Base).Get(perm) +func HasPermission(stateWriter state.Writer, acc acm.Account, perm ptypes.PermFlag) bool { + value, _ := acc.Permissions().Base.Compose(state.GlobalAccountPermissions(stateWriter).Base).Get(perm) return value } @@ -142,17 +186,25 @@ func (vm *VM) Call(caller, callee acm.MutableAccount, code, input []byte, value } if len(code) > 0 { - vm.callDepth += 1 + vm.stackDepth += 1 output, err = vm.call(caller, callee, code, input, value, gas) - vm.callDepth -= 1 + vm.stackDepth -= 1 if err != nil { + err = ErrCall{ + CallError: err, + NestedErrors: vm.nestedCallErrors, + } *exception = err.Error() - err := transfer(callee, caller, value) - if err != nil { - // data has been corrupted in ram - panic("Could not return value to caller") + transferErr := transfer(callee, caller, value) + if transferErr != nil { + return nil, fmt.Errorf("error transferring value %v %s (callee) -> %s (caller)", + value, callee, caller) } } + if vm.stackDepth == 0 { + // clean up ready for next call + vm.nestedCallErrors = nil + } } return @@ -162,7 +214,7 @@ func (vm *VM) Call(caller, callee acm.MutableAccount, code, input []byte, value // 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 acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { +func (vm *VM) DelegateCall(caller acm.Account, 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) @@ -173,9 +225,9 @@ func (vm *VM) DelegateCall(caller, callee acm.MutableAccount, code, input []byte // DelegateCall does not transfer the value to the callee. if len(code) > 0 { - vm.callDepth += 1 + vm.stackDepth += 1 output, err = vm.call(caller, callee, code, input, value, gas) - vm.callDepth -= 1 + vm.stackDepth -= 1 if err != nil { *exception = err.Error() } @@ -197,10 +249,16 @@ func useGasNegative(gasLeft *uint64, gasToUse uint64, err *error) bool { } // Just like Call() but does not transfer 'value' or modify the callDepth. -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(), +func (vm *VM) call(caller acm.Account, 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.stackDepth, caller.Address().Bytes()[:4], callee.Address(), len(callee.Code()), *gas, input) + logger := vm.logger.With("tx_hash", vm.txHash) + + if vm.dumpTokens { + dumpTokens(vm.txHash, caller, callee, code) + } + var ( pc int64 = 0 stack = NewStack(dataStackCapacity, gas, &err) @@ -220,19 +278,19 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case ADD: // 0x01 x, y := stack.PopBigInt(), stack.PopBigInt() - sum := x.Add(x, y) + sum := new(big.Int).Add(x, y) res := stack.PushBigInt(sum) vm.Debugf(" %v + %v = %v (%X)\n", x, y, sum, res) case MUL: // 0x02 x, y := stack.PopBigInt(), stack.PopBigInt() - prod := x.Mul(x, y) + prod := new(big.Int).Mul(x, y) res := stack.PushBigInt(prod) vm.Debugf(" %v * %v = %v (%X)\n", x, y, prod, res) case SUB: // 0x03 x, y := stack.PopBigInt(), stack.PopBigInt() - diff := x.Sub(x, y) + diff := new(big.Int).Sub(x, y) res := stack.PushBigInt(diff) vm.Debugf(" %v - %v = %v (%X)\n", x, y, diff, res) @@ -242,7 +300,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(Zero256) vm.Debugf(" %x / %x = %v\n", x, y, 0) } else { - div := x.Div(x, y) + div := new(big.Int).Div(x, y) res := stack.PushBigInt(div) vm.Debugf(" %v / %v = %v (%X)\n", x, y, div, res) } @@ -253,7 +311,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(Zero256) vm.Debugf(" %x / %x = %v\n", x, y, 0) } else { - div := x.Div(x, y) + div := new(big.Int).Div(x, y) res := stack.PushBigInt(div) vm.Debugf(" %v / %v = %v (%X)\n", x, y, div, res) } @@ -264,7 +322,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(Zero256) vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { - mod := x.Mod(x, y) + mod := new(big.Int).Mod(x, y) res := stack.PushBigInt(mod) vm.Debugf(" %v %% %v = %v (%X)\n", x, y, mod, res) } @@ -275,7 +333,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(Zero256) vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { - mod := x.Mod(x, y) + mod := new(big.Int).Mod(x, y) res := stack.PushBigInt(mod) vm.Debugf(" %v %% %v = %v (%X)\n", x, y, mod, res) } @@ -286,7 +344,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(Zero256) vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { - add := x.Add(x, y) + add := new(big.Int).Add(x, y) mod := add.Mod(add, z) res := stack.PushBigInt(mod) vm.Debugf(" %v + %v %% %v = %v (%X)\n", x, y, z, mod, res) @@ -298,7 +356,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(Zero256) vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { - mul := x.Mul(x, y) + mul := new(big.Int).Mul(x, y) mod := mul.Mod(mul, z) res := stack.PushBigInt(mod) vm.Debugf(" %v * %v %% %v = %v (%X)\n", x, y, z, mod, res) @@ -306,12 +364,15 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case EXP: // 0x0A x, y := stack.PopBigInt(), stack.PopBigInt() - pow := x.Exp(x, y, nil) + pow := new(big.Int).Exp(x, y, nil) res := stack.PushBigInt(pow) vm.Debugf(" %v ** %v = %v (%X)\n", x, y, pow, res) case SIGNEXTEND: // 0x0B - back := stack.PopU64() + back, popErr := stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } if back < Word256Length-1 { stack.PushBigInt(SignExtend(back, stack.PopBigInt())) } @@ -413,7 +474,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" !%X = %X\n", x, z) case BYTE: // 0x1A - idx, val := stack.Pop64(), stack.Pop() + idx, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + val := stack.Pop() res := byte(0) if idx < 32 { res = val[idx] @@ -444,7 +509,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + acc, errAcc := vm.stateWriter.GetAccount(acm.AddressFromWord256(addr)) if errAcc != nil { return nil, firstErr(err, errAcc) } @@ -468,7 +533,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" => %v\n", value) case CALLDATALOAD: // 0x35 - offset := stack.Pop64() + offset, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(input, offset, 32) if !ok { return nil, firstErr(err, ErrInputOutOfBounds) @@ -483,8 +551,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case CALLDATACOPY: // 0x37 memOff := stack.PopBigInt() - inputOff := stack.Pop64() - length := stack.Pop64() + inputOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(input, inputOff, length) if !ok { return nil, firstErr(err, ErrInputOutOfBounds) @@ -503,8 +577,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case CODECOPY: // 0x39 memOff := stack.PopBigInt() - codeOff := stack.Pop64() - length := stack.Pop64() + codeOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(code, codeOff, length) if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) @@ -525,7 +605,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + acc, errAcc := vm.stateWriter.GetAccount(acm.AddressFromWord256(addr)) if errAcc != nil { return nil, firstErr(err, errAcc) } @@ -546,7 +626,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + acc, errAcc := vm.stateWriter.GetAccount(acm.AddressFromWord256(addr)) if errAcc != nil { return nil, firstErr(err, errAcc) } @@ -559,8 +639,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value } code := acc.Code() memOff := stack.PopBigInt() - codeOff := stack.Pop64() - length := stack.Pop64() + codeOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(code, codeOff, length) if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) @@ -618,7 +704,12 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE8: // 0x53 - offset, val := stack.PopBigInt(), byte(stack.Pop64()&0xFF) + offset := stack.PopBigInt() + val64, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + val := byte(val64 & 0xFF) memErr := memory.Write(offset, []byte{val}) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) @@ -628,23 +719,27 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case SLOAD: // 0x54 loc := stack.Pop() - data, errSto := vm.state.GetStorage(callee.Address(), loc) + data, errSto := vm.stateWriter.GetStorage(callee.Address(), loc) if errSto != nil { return nil, firstErr(err, errSto) } stack.Push(data) - vm.Debugf(" {0x%X : 0x%X}\n", loc, data) + vm.Debugf("%s {0x%X = 0x%X}\n", callee.Address(), loc, data) case SSTORE: // 0x55 loc, data := stack.Pop(), stack.Pop() if useGasNegative(gas, GasStorageUpdate, &err) { return nil, err } - vm.state.SetStorage(callee.Address(), loc, data) - vm.Debugf(" {0x%X : 0x%X}\n", loc, data) + vm.stateWriter.SetStorage(callee.Address(), loc, data) + vm.Debugf("%s {0x%X := 0x%X}\n", callee.Address(), loc, data) case JUMP: // 0x56 - jumpErr := vm.jump(code, stack.Pop64(), &pc) + to, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + jumpErr := vm.jump(code, to, &pc) if jumpErr != nil { vm.Debugf(" => JUMP err: %s", jumpErr) return nil, firstErr(err, jumpErr) @@ -652,7 +747,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value continue case JUMPI: // 0x57 - pos, cond := stack.Pop64(), stack.Pop() + pos, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + cond := stack.Pop() if !cond.IsZero() { jumpErr := vm.jump(code, pos, &pc) if jumpErr != nil { @@ -717,8 +816,6 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value return nil, firstErr(err, ErrMemoryOutOfBounds) } if vm.publisher != nil { - eventID := events.EventStringLogEvent(callee.Address()) - fmt.Printf("eventID: %s\n", eventID) events.PublishLogEvent(vm.publisher, callee.Address(), &events.EventDataLog{ Address: callee.Address(), Topics: topics, @@ -729,10 +826,13 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" => T:%X D:%X\n", topics, data) case CREATE: // 0xF0 - if !HasPermission(vm.state, callee, permission.CreateContract) { + if !HasPermission(vm.stateWriter, callee, permission.CreateContract) { return nil, ErrPermission{"create_contract"} } - contractValue := stack.PopU64() + contractValue, popErr := stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } offset, size := stack.PopBigInt(), stack.PopBigInt() input, memErr := memory.Read(offset, size) if memErr != nil { @@ -746,9 +846,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value } // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount := DeriveNewAccount(callee, permission.GlobalAccountPermissions(vm.state), - vm.logger.With("tx_hash", vm.txHash)) - vm.state.UpdateAccount(newAccount) + var gasErr error + if useGasNegative(gas, GasCreateAccount, &gasErr) { + return nil, firstErr(err, gasErr) + } + newAccount, createErr := vm.createAccount(callee, logger) + if createErr != nil { + return nil, firstErr(err, createErr) + } // Run the input to get the contract code. // NOTE: no need to copy 'input' as per Call contract. @@ -760,11 +865,18 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value stack.Push(newAccount.Address().Word256()) } + if err_ == ErrExecutionReverted { + return ret, nil + } + case CALL, CALLCODE, DELEGATECALL: // 0xF1, 0xF2, 0xF4 - if !HasPermission(vm.state, callee, permission.Call) { + if !HasPermission(vm.stateWriter, callee, permission.Call) { return nil, ErrPermission{"call"} } - gasLimit := stack.PopU64() + gasLimit, popErr := stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } addr := stack.Pop() // NOTE: for DELEGATECALL value is preserved from the original // caller, as such it is not stored on stack as an argument @@ -772,10 +884,19 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value // 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.PopU64() + value, popErr = stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + } + // inputs + inOffset, inSize := stack.PopBigInt(), stack.PopBigInt() + // outputs + retOffset := stack.PopBigInt() + retSize, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) } - inOffset, inSize := stack.PopBigInt(), stack.PopBigInt() // inputs - retOffset, retSize := stack.PopBigInt(), stack.Pop64() // outputs vm.Debugf(" => %X\n", addr) // Get the arguments from the memory @@ -787,18 +908,19 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value // Ensure that gasLimit is reasonable if *gas < gasLimit { - return nil, firstErr(err, ErrInsufficientGas) - } else { - *gas -= gasLimit - // NOTE: we will return any used gas later. + // EIP150 - the 63/64 rule - rather than error we pass this specified fraction of the total available gas + gasLimit = *gas - *gas/64 } + // NOTE: we will return any used gas later. + *gas -= gasLimit // Begin execution var ret []byte var callErr error + if nativeContract := registeredNativeContracts[addr]; nativeContract != nil { // Native contract - ret, callErr = nativeContract(vm.state, callee, args, &gasLimit, vm.logger) + ret, callErr = nativeContract(vm.stateWriter, callee, args, &gasLimit, logger) // for now we fire the Call event. maybe later we'll fire more particulars var exception string @@ -812,7 +934,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if useGasNegative(gas, GasGetAccount, &callErr) { return nil, callErr } - acc, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) + acc, errAcc := state.GetMutableAccount(vm.stateWriter, acm.AddressFromWord256(addr)) if errAcc != nil { return nil, firstErr(callErr, errAcc) } @@ -833,23 +955,43 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value } else { // nil account means we're sending funds to a new account if acc == nil { - if !HasPermission(vm.state, caller, permission.CreateAccount) { + if !HasPermission(vm.stateWriter, caller, permission.CreateAccount) { return nil, ErrPermission{"create_account"} } - acc = (&acm.ConcreteAccount{Address: acm.AddressFromWord256(addr)}).MutableAccount() + acc = acm.ConcreteAccount{Address: acm.AddressFromWord256(addr)}.MutableAccount() } // add account to the tx cache - vm.state.UpdateAccount(acc) + vm.stateWriter.UpdateAccount(acc) ret, callErr = vm.Call(callee, acc, acc.Code(), args, value, &gasLimit) } } + // In case any calls deeper in the stack (particularly SNatives) has altered either of two accounts to which + // we hold a reference, we need to freshen our state for subsequent iterations of this call frame's EVM loop + var getErr error + caller, getErr = vm.stateWriter.GetAccount(caller.Address()) + if getErr != nil { + return nil, firstErr(err, getErr) + } + callee, getErr = state.GetMutableAccount(vm.stateWriter, callee.Address()) + if getErr != nil { + return nil, firstErr(err, getErr) + } // Push result if callErr != nil { - vm.Debugf("error on call: %s\n", callErr.Error()) - // TODO: we probably don't want to return the error - decide - //err = firstErr(err, callErr) + vm.Debugf("error from nested sub-call (depth: %v): %s\n", vm.stackDepth, callErr.Error()) + // So we can return nested error if the top level return is an error + vm.nestedCallErrors = append(vm.nestedCallErrors, ErrNestedCall{ + NestedError: callErr, + StackDepth: vm.stackDepth, + Caller: caller.Address(), + Callee: callee.Address(), + }) stack.Push(Zero256) + + if callErr == ErrExecutionReverted { + memory.Write(retOffset, RightPadBytes(ret, int(retSize))) + } } else { stack.Push(One256) @@ -878,42 +1020,78 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, nil + case REVERT: // 0xFD + return nil, fmt.Errorf("REVERT not yet fully implemented") + offset, size := stack.PopBigInt(), stack.PopBigInt() + output, memErr := memory.Read(offset, size) + if memErr != nil { + vm.Debugf(" => Memory err: %s", memErr) + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + + vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) + return output, ErrExecutionReverted + case SELFDESTRUCT: // 0xFF addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - // TODO if the receiver is , then make it the fee. (?) - // TODO: create account if doesn't exist (no reason not to) - receiver, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) + receiver, errAcc := state.GetMutableAccount(vm.stateWriter, acm.AddressFromWord256(addr)) if errAcc != nil { return nil, firstErr(err, errAcc) } if receiver == nil { - return nil, firstErr(err, ErrUnknownAddress) + var gasErr error + if useGasNegative(gas, GasCreateAccount, &gasErr) { + return nil, firstErr(err, gasErr) + } + if !HasPermission(vm.stateWriter, callee, permission.CreateContract) { + return nil, firstErr(err, ErrPermission{"create_contract"}) + } + var createErr error + receiver, createErr = vm.createAccount(callee, logger) + if createErr != nil { + return nil, firstErr(err, createErr) + } + } receiver, errAdd := receiver.AddToBalance(callee.Balance()) if errAdd != nil { return nil, firstErr(err, errAdd) } - vm.state.UpdateAccount(receiver) - vm.state.RemoveAccount(callee.Address()) + vm.stateWriter.UpdateAccount(receiver) + vm.stateWriter.RemoveAccount(callee.Address()) vm.Debugf(" => (%X) %v\n", addr[:4], callee.Balance()) fallthrough case STOP: // 0x00 return nil, nil + case STATICCALL, SHL, SHR, SAR, RETURNDATASIZE, RETURNDATACOPY: + return nil, fmt.Errorf("%s not yet implemented", op.Name()) default: vm.Debugf("(pc) %-3v Invalid opcode %X\n", pc, op) - return nil, fmt.Errorf("Invalid opcode %X", op) + return nil, fmt.Errorf("invalid opcode %X", op) } - pc++ } } +func (vm *VM) createAccount(callee acm.MutableAccount, logger *logging.Logger) (acm.MutableAccount, error) { + newAccount := DeriveNewAccount(callee, state.GlobalAccountPermissions(vm.stateWriter), logger) + err := vm.stateWriter.UpdateAccount(newAccount) + if err != nil { + return nil, err + } + err = vm.stateWriter.UpdateAccount(callee) + if err != nil { + return nil, err + } + return newAccount, nil +} + // TODO: [Silas] this function seems extremely dubious to me. It was being used // in circumstances where its behaviour did not match the intention. It's bounds // check is strange (treats a read at data length as a zero read of arbitrary length) @@ -976,3 +1154,28 @@ func transfer(from, to acm.MutableAccount, amount uint64) error { } return nil } + +// Dump the bytecode being sent to the EVM in the current working directory +func dumpTokens(txHash []byte, caller, callee acm.Account, code []byte) { + var tokensString string + tokens, err := acm.Bytecode(code).Tokens() + if err != nil { + tokensString = fmt.Sprintf("error generating tokens from bytecode: %v", err) + } else { + tokensString = strings.Join(tokens, "\n") + } + txHashString := "tx-none" + if len(txHash) >= 4 { + txHashString = fmt.Sprintf("tx-%X", txHash[:4]) + } + callerString := "caller-none" + if caller != nil { + callerString = fmt.Sprintf("caller-%v", caller.Address()) + } + calleeString := "callee-none" + if callee != nil { + calleeString = fmt.Sprintf("callee-%s", caller.Address()) + } + ioutil.WriteFile(fmt.Sprintf("tokens_%s_%s_%s.asm", txHashString, callerString, calleeString), + []byte(tokensString), 0777) +} diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go index a6335deeb28622fdd99c1c985871cd3e8cb35359..82d79012b6127a72fe7f4ae112613041145c8bcf 100644 --- a/execution/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -24,12 +24,13 @@ import ( "time" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/event" . "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/loggers" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" "github.com/stretchr/testify/assert" @@ -38,8 +39,9 @@ import ( ) // Test output is a bit clearer if we /dev/null the logging, but can be re-enabled by uncommenting the below -//var logger, _ = lifecycle.NewStdErrLogger() -var logger = loggers.NewNoopInfoTraceLogger() +//var logger, _, _ = lifecycle.NewStdErrLogger() +// +var logger = logging.NewNoopLogger() func newAppState() *FakeAppState { fas := &FakeAppState{ @@ -47,7 +49,7 @@ func newAppState() *FakeAppState { storage: make(map[string]Word256), } // For default permissions - fas.accounts[permission.GlobalPermissionsAddress] = acm.ConcreteAccount{ + fas.accounts[acm.GlobalPermissionsAddress] = acm.ConcreteAccount{ Permissions: permission.DefaultAccountPermissions, }.Account() return fas @@ -72,7 +74,7 @@ func newAccount(seed ...byte) acm.MutableAccount { // Runs a basic loop func TestVM(t *testing.T) { - ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + ourVm := NewVM(newAppState(), newParams(), acm.ZeroAddress, nil, logger) // Create accounts account1 := newAccount(1) @@ -95,7 +97,7 @@ func TestVM(t *testing.T) { //Test attempt to jump to bad destination (position 16) func TestJumpErr(t *testing.T) { - ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + ourVm := NewVM(newAppState(), newParams(), acm.ZeroAddress, nil, logger) // Create accounts account1 := newAccount(1) @@ -132,7 +134,7 @@ func TestSubcurrency(t *testing.T) { st.accounts[account1.Address()] = account1 st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + ourVm := NewVM(st, newParams(), acm.ZeroAddress, nil, logger) var gas uint64 = 1000 @@ -158,16 +160,41 @@ func TestSubcurrency(t *testing.T) { } } +//This test case is taken from EIP-140 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-140.md); +//it is meant to test the implementation of the REVERT opcode +func TestRevert(t *testing.T) { + ourVm := NewVM(newAppState(), newParams(), acm.ZeroAddress, nil, logger) + + // Create accounts + account1 := newAccount(1) + account2 := newAccount(1, 0, 1) + + var gas uint64 = 100000 + + bytecode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, REVERT) + + start := time.Now() + output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas) + assert.Error(t, err, "Expected execution reverted error") + fmt.Printf("Output: %v Error: %v\n", output, err) + fmt.Println("Call took:", time.Since(start)) +} + // Test sending tokens from a contract to another account func TestSendCall(t *testing.T) { fakeAppState := newAppState() - ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + ourVm := NewVM(fakeAppState, newParams(), acm.ZeroAddress, nil, logger) // Create accounts account1 := newAccount(1) account2 := newAccount(2) account3 := newAccount(3) + fakeAppState.UpdateAccount(account1) + fakeAppState.UpdateAccount(account2) + fakeAppState.UpdateAccount(account3) // account1 will call account2 which will trigger CALL opcode to account3 addr := account3.Address() contractCode := callContractCode(addr) @@ -189,7 +216,7 @@ func TestSendCall(t *testing.T) { 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") + assert.NoError(t, err, "Expected insufficient gas error") } // This test was introduced to cover an issues exposed in our handling of the @@ -198,8 +225,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) { - state := newAppState() - ourVm := NewVM(state, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + appState := newAppState() + ourVm := NewVM(appState, newParams(), acm.ZeroAddress, nil, logger) inOff := 0 inSize := 0 // no call data @@ -219,7 +246,7 @@ func TestDelegateCallGas(t *testing.T) { costBetweenGasAndDelegateCall := gasCost + subCost + delegateCallCost + pushCost // Do a simple operation using 1 gas unit - calleeAccount, calleeAddress := makeAccountWithCode(state, "callee", + calleeAccount, calleeAddress := makeAccountWithCode(appState, "callee", MustSplice(PUSH1, calleeReturnValue, return1())) // Here we split up the caller code so we can make a DELEGATE call with @@ -232,7 +259,7 @@ func TestDelegateCallGas(t *testing.T) { callerCodeSuffix := MustSplice(GAS, SUB, DELEGATECALL, returnWord()) // Perform a delegate call - callerAccount, _ := makeAccountWithCode(state, "caller", + callerAccount, _ := makeAccountWithCode(appState, "caller", MustSplice(callerCodePrefix, // Give just enough gas to make the DELEGATECALL costBetweenGasAndDelegateCall, @@ -252,17 +279,17 @@ func TestDelegateCallGas(t *testing.T) { // Should fail _, err = runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, callerAccount.Code(), 100) - assert.Error(t, err, "Should have insufficient funds for call") + assert.Error(t, err, "Should have insufficient gas for call") } func TestMemoryBounds(t *testing.T) { - state := newAppState() + appState := newAppState() memoryProvider := func() Memory { return NewDynamicMemory(1024, 2048) } - ourVm := NewVM(state, memoryProvider, newParams(), acm.ZeroAddress, nil, logger) - caller, _ := makeAccountWithCode(state, "caller", nil) - callee, _ := makeAccountWithCode(state, "callee", nil) + ourVm := NewVM(appState, newParams(), acm.ZeroAddress, nil, logger, MemoryProvider(memoryProvider)) + caller, _ := makeAccountWithCode(appState, "caller", nil) + callee, _ := makeAccountWithCode(appState, "callee", nil) gas := uint64(100000) // This attempts to store a value at the memory boundary and return it word := One256 @@ -307,7 +334,7 @@ func TestMsgSender(t *testing.T) { st.accounts[account1.Address()] = account1 st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) + ourVm := NewVM(st, newParams(), acm.ZeroAddress, nil, logger) var gas uint64 = 100000 @@ -373,7 +400,7 @@ func returnWord() []byte { return MustSplice(PUSH1, 32, PUSH1, 0, RETURN) } -func makeAccountWithCode(state acm.Updater, name string, +func makeAccountWithCode(accountUpdater state.AccountUpdater, name string, code []byte) (acm.MutableAccount, acm.Address) { address, _ := acm.AddressFromBytes([]byte(name)) account := acm.ConcreteAccount{ @@ -382,7 +409,7 @@ func makeAccountWithCode(state acm.Updater, name string, Code: code, Sequence: 0, }.MutableAccount() - state.UpdateAccount(account) + accountUpdater.UpdateAccount(account) return account, account.Address() } @@ -412,7 +439,7 @@ func runVM(eventCh chan<- *evm_events.EventDataCall, ourVm *VM, caller, callee a subscribeAddr acm.Address, contractCode []byte, gas uint64) ([]byte, error) { // we need to catch the event from the CALL to check for exceptions - emitter := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) + emitter := event.NewEmitter(logging.NewNoopLogger()) fmt.Printf("subscribe to %s\n", subscribeAddr) err := evm_events.SubscribeAccountCall(context.Background(), emitter, "test", subscribeAddr, nil, eventCh) diff --git a/execution/execution.go b/execution/execution.go index fbe69f4f4fdddacd6e9d2bc68bcfe03d08b91098..7b5a9ee7ea77cb3c5a6d4d66d68eb118ead05159 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -16,9 +16,11 @@ package execution import ( "fmt" + "runtime/debug" "sync" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/binary" bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/event" @@ -26,7 +28,6 @@ import ( "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/permission" ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" @@ -36,9 +37,9 @@ import ( const GasLimit = uint64(1000000) type BatchExecutor interface { - acm.StateIterable - acm.Updater - acm.StorageSetter + state.Iterable + state.AccountUpdater + state.StorageSetter // Execute transaction against block cache (i.e. block buffer) Execute(tx txs.Tx) error // Reset executor to underlying State @@ -59,11 +60,12 @@ type executor struct { tip bcm.Tip runCall bool state *State - stateCache acm.StateCache + stateCache state.Cache nameRegCache *NameRegCache publisher event.Publisher eventCache *event.Cache - logger logging_types.InfoTraceLogger + logger *logging.Logger + vmOptions []func(*evm.VM) } var _ BatchExecutor = (*executor)(nil) @@ -72,37 +74,45 @@ var _ BatchExecutor = (*executor)(nil) func NewBatchChecker(state *State, chainID string, tip bcm.Tip, - logger logging_types.InfoTraceLogger) BatchExecutor { + logger *logging.Logger, + options ...ExecutionOption) BatchExecutor { return newExecutor(false, state, chainID, tip, event.NewNoOpPublisher(), - logging.WithScope(logger, "NewBatchExecutor")) + logger.WithScope("NewBatchExecutor"), options...) } func NewBatchCommitter(state *State, chainID string, tip bcm.Tip, publisher event.Publisher, - logger logging_types.InfoTraceLogger) BatchCommitter { + logger *logging.Logger, + options ...ExecutionOption) BatchCommitter { + return newExecutor(true, state, chainID, tip, publisher, - logging.WithScope(logger, "NewBatchCommitter")) + logger.WithScope("NewBatchCommitter"), options...) } func newExecutor(runCall bool, - state *State, + backend *State, chainID string, tip bcm.Tip, eventFireable event.Publisher, - logger logging_types.InfoTraceLogger) *executor { - return &executor{ + logger *logging.Logger, + options ...ExecutionOption) *executor { + exe := &executor{ chainID: chainID, tip: tip, runCall: runCall, - state: state, - stateCache: acm.NewStateCache(state), - nameRegCache: NewNameRegCache(state), + state: backend, + stateCache: state.NewCache(backend), + nameRegCache: NewNameRegCache(backend), publisher: eventFireable, eventCache: event.NewEventCache(eventFireable), logger: logger.With(structure.ComponentKey, "Executor"), } + for _, option := range options { + option(exe) + } + return exe } // Accounts @@ -157,7 +167,7 @@ func (exe *executor) Commit() (hash []byte, err error) { if err != nil { return nil, err } - // flush events to listeners (XXX: note issue with blocking) + // flush events to listeners exe.eventCache.Flush() return exe.state.Hash(), nil } @@ -173,16 +183,16 @@ func (exe *executor) Reset() error { func (exe *executor) Execute(tx txs.Tx) (err error) { defer func() { if r := recover(); r != nil { - err = fmt.Errorf("recovered from panic in executor.Execute(%s): %v", tx.String(), r) + err = fmt.Errorf("recovered from panic in executor.Execute(%s): %v\n%s", tx.String(), r, + debug.Stack()) } }() txHash := tx.Hash(exe.chainID) - logger := logging.WithScope(exe.logger, "executor.Execute(tx txs.Tx)").With( + logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With( "run_call", exe.runCall, - "tx", tx.String(), "tx_hash", txHash) - logging.TraceMsg(logger, "Executing transaction", "tx", tx.String()) + logger.TraceMsg("Executing transaction", "tx", tx.String()) // TODO: do something with fees fees := uint64(0) @@ -253,12 +263,12 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { var outAcc acm.Account // Validate input - inAcc, err := acm.GetMutableAccount(exe.stateCache, tx.Input.Address) + inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address) if err != nil { return err } if inAcc == nil { - logging.InfoMsg(logger, "Cannot find input account", + logger.InfoMsg("Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } @@ -276,19 +286,19 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { - logging.InfoMsg(logger, "Cannot find public key for input account", + logger.InfoMsg("Cannot find public key for input account", "tx_input", tx.Input) return err } signBytes := acm.SignBytes(exe.chainID, tx) err = validateInput(inAcc, signBytes, tx.Input) if err != nil { - logging.InfoMsg(logger, "validateInput failed", + logger.InfoMsg("validateInput failed", "tx_input", tx.Input, structure.ErrorKey, err) return err } if tx.Input.Amount < tx.Fee { - logging.InfoMsg(logger, "Sender did not send enough to cover the fee", + logger.InfoMsg("Sender did not send enough to cover the fee", "tx_input", tx.Input) return txs.ErrTxInsufficientFunds } @@ -312,12 +322,12 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { } } - logger.Trace("output_account", outAcc) + logger.Trace.Log("output_account", outAcc) // Good! value := tx.Input.Amount - tx.Fee - logging.TraceMsg(logger, "Incrementing sequence number for CallTx", + logger.TraceMsg("Incrementing sequence number for CallTx", "tag", "sequence", "account", inAcc.Address(), "old_sequence", inAcc.Sequence(), @@ -340,7 +350,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { callee acm.MutableAccount = nil // initialized below code []byte = nil ret []byte = nil - txCache = acm.NewStateCache(exe.stateCache) + txCache = state.NewCache(exe.stateCache) params = evm.Params{ BlockHeight: exe.tip.LastBlockHeight(), BlockHash: binary.LeftPadWord256(exe.tip.LastBlockHash()), @@ -358,11 +368,11 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { // you have to wait a block to avoid a re-ordering attack // that will take your fees if outAcc == nil { - logging.InfoMsg(logger, "Call to address that does not exist", + logger.InfoMsg("Call to address that does not exist", "caller_address", inAcc.Address(), "callee_address", tx.Address) } else { - logging.InfoMsg(logger, "Call to address that holds no code", + logger.InfoMsg("Call to address that holds no code", "caller_address", inAcc.Address(), "callee_address", tx.Address) } @@ -373,44 +383,43 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { // get or create callee if createContract { // We already checked for permission - callee = evm.DeriveNewAccount(caller, permission.GlobalAccountPermissions(exe.state), + callee = evm.DeriveNewAccount(caller, state.GlobalAccountPermissions(exe.state), logger.With( "tx", tx.String(), "tx_hash", txHash, "run_call", exe.runCall, )) code = tx.Data - logging.TraceMsg(logger, "Creating new contract", + logger.TraceMsg("Creating new contract", "contract_address", callee.Address(), "init_code", code) } else { callee = acm.AsMutableAccount(outAcc) code = callee.Code() - logging.TraceMsg(logger, "Calling existing contract", + logger.TraceMsg("Calling existing contract", "contract_address", callee.Address(), "input", tx.Data, "contract_code", code) } - logger.Trace("callee", callee.Address().String()) + logger.Trace.Log("callee", callee.Address().String()) // 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 := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), - tx.Hash(exe.chainID), logger) + vmach := evm.NewVM(txCache, params, caller.Address(), tx.Hash(exe.chainID), logger, exe.vmOptions...) vmach.SetPublisher(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 { // Failure. Charge the gas fee. The 'value' was otherwise not transferred. - logging.InfoMsg(logger, "Error on execution", + logger.InfoMsg("Error on execution", structure.ErrorKey, err) goto CALL_COMPLETE } - logging.TraceMsg(logger, "Successful execution") + logger.TraceMsg("Successful execution") if createContract { callee.SetCode(ret) } @@ -420,7 +429,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { CALL_COMPLETE: // err may or may not be nil. // Create a receipt from the ret and whether it erred. - logging.TraceMsg(logger, "VM call complete", + logger.TraceMsg("VM call complete", "caller", caller, "callee", callee, "return", ret, @@ -450,7 +459,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { } if createContract { // This is done by DeriveNewAccount when runCall == true - logging.TraceMsg(logger, "Incrementing sequence number since creates contract", + logger.TraceMsg("Incrementing sequence number since creates contract", "tag", "sequence", "account", inAcc.Address(), "old_sequence", inAcc.Sequence(), @@ -464,12 +473,12 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { case *txs.NameTx: // Validate input - inAcc, err := acm.GetMutableAccount(exe.stateCache, tx.Input.Address) + inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address) if err != nil { return err } if inAcc == nil { - logging.InfoMsg(logger, "Cannot find input account", + logger.InfoMsg("Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } @@ -479,19 +488,19 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { - logging.InfoMsg(logger, "Cannot find public key for input account", + logger.InfoMsg("Cannot find public key for input account", "tx_input", tx.Input) return err } signBytes := acm.SignBytes(exe.chainID, tx) err = validateInput(inAcc, signBytes, tx.Input) if err != nil { - logging.InfoMsg(logger, "validateInput failed", + logger.InfoMsg("validateInput failed", "tx_input", tx.Input, structure.ErrorKey, err) return err } if tx.Input.Amount < tx.Fee { - logging.InfoMsg(logger, "Sender did not send enough to cover the fee", + logger.InfoMsg("Sender did not send enough to cover the fee", "tx_input", tx.Input) return txs.ErrTxInsufficientFunds } @@ -508,7 +517,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { expiresIn := value / uint64(costPerBlock) lastBlockHeight := exe.tip.LastBlockHeight() - logging.TraceMsg(logger, "New NameTx", + logger.TraceMsg("New NameTx", "value", value, "cost_per_block", costPerBlock, "expires_in", expiresIn, @@ -538,7 +547,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { if value == 0 && len(tx.Data) == 0 { // maybe we reward you for telling us we can delete this crap // (owners if not expired, anyone if expired) - logging.TraceMsg(logger, "Removing NameReg entry (no value and empty data in tx requests this)", + logger.TraceMsg("Removing NameReg entry (no value and empty data in tx requests this)", "name", entry.Name) err := exe.nameRegCache.RemoveNameRegEntry(entry.Name) if err != nil { @@ -553,7 +562,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { } entry.Expires = lastBlockHeight + expiresIn entry.Owner = tx.Input.Address - logging.TraceMsg(logger, "An old NameReg entry has expired and been reclaimed", + logger.TraceMsg("An old NameReg entry has expired and been reclaimed", "name", entry.Name, "expires_in", expiresIn, "owner", entry.Owner) @@ -567,7 +576,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { return fmt.Errorf("names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } entry.Expires = lastBlockHeight + expiresIn - logging.TraceMsg(logger, "Updated NameReg entry", + logger.TraceMsg("Updated NameReg entry", "name", entry.Name, "expires_in", expiresIn, "old_credit", oldCredit, @@ -591,7 +600,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { Data: tx.Data, Expires: lastBlockHeight + expiresIn, } - logging.TraceMsg(logger, "Creating NameReg entry", + logger.TraceMsg("Creating NameReg entry", "name", entry.Name, "expires_in", expiresIn) err := exe.nameRegCache.UpdateNameRegEntry(entry) @@ -603,7 +612,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { // TODO: something with the value sent? // Good! - logging.TraceMsg(logger, "Incrementing sequence number for NameTx", + logger.TraceMsg("Incrementing sequence number for NameTx", "tag", "sequence", "account", inAcc.Address(), "old_sequence", inAcc.Sequence(), @@ -766,12 +775,12 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { case *txs.PermissionsTx: // Validate input - inAcc, err := acm.GetMutableAccount(exe.stateCache, tx.Input.Address) + inAcc, err := state.GetMutableAccount(exe.stateCache, tx.Input.Address) if err != nil { return err } if inAcc == nil { - logging.InfoMsg(logger, "Cannot find input account", + logger.InfoMsg("Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } @@ -790,14 +799,14 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { - logging.InfoMsg(logger, "Cannot find public key for input account", + logger.InfoMsg("Cannot find public key for input account", "tx_input", tx.Input) return err } signBytes := acm.SignBytes(exe.chainID, tx) err = validateInput(inAcc, signBytes, tx.Input) if err != nil { - logging.InfoMsg(logger, "validateInput failed", + logger.InfoMsg("validateInput failed", "tx_input", tx.Input, structure.ErrorKey, err) return err @@ -805,7 +814,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { value := tx.Input.Amount - logging.TraceMsg(logger, "New PermissionsTx", + logger.TraceMsg("New PermissionsTx", "perm_args", tx.PermArgs.String()) var permAcc acm.Account @@ -824,7 +833,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { return perms.Base.Unset(*tx.PermArgs.Permission) }) case permission.SetGlobal: - permAcc, err = mutatePermissions(exe.stateCache, permission.GlobalPermissionsAddress, + permAcc, err = mutatePermissions(exe.stateCache, acm.GlobalPermissionsAddress, func(perms *ptypes.AccountPermissions) error { return perms.Base.Set(*tx.PermArgs.Permission, *tx.PermArgs.Value) }) @@ -858,7 +867,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { } // Good! - logging.TraceMsg(logger, "Incrementing sequence number for PermissionsTx", + logger.TraceMsg("Incrementing sequence number for PermissionsTx", "tag", "sequence", "account", inAcc.Address(), "old_sequence", inAcc.Sequence(), @@ -887,7 +896,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { } } -func mutatePermissions(stateReader acm.StateReader, address acm.Address, +func mutatePermissions(stateReader state.Reader, address acm.Address, mutator func(*ptypes.AccountPermissions) error) (acm.Account, error) { account, err := stateReader.GetAccount(address) @@ -1035,7 +1044,7 @@ func execBlock(s *State, block *txs.Block, blockPartsHeader txs.PartSetHeader) e // 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, +func getInputs(accountGetter state.AccountGetter, ins []*txs.TxInput) (map[acm.Address]acm.MutableAccount, error) { accounts := map[acm.Address]acm.MutableAccount{} @@ -1044,7 +1053,7 @@ func getInputs(accountGetter acm.Getter, if _, ok := accounts[in.Address]; ok { return nil, txs.ErrTxDuplicateAddress } - acc, err := acm.GetMutableAccount(accountGetter, in.Address) + acc, err := state.GetMutableAccount(accountGetter, in.Address) if err != nil { return nil, err } @@ -1060,8 +1069,8 @@ func getInputs(accountGetter acm.Getter, 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) { +func getOrMakeOutputs(accountGetter state.AccountGetter, accs map[acm.Address]acm.MutableAccount, + outs []*txs.TxOutput, logger *logging.Logger) (map[acm.Address]acm.MutableAccount, error) { if accs == nil { accs = make(map[acm.Address]acm.MutableAccount) } @@ -1073,7 +1082,7 @@ func getOrMakeOutputs(accountGetter acm.Getter, accs map[acm.Address]acm.Mutable if _, ok := accs[out.Address]; ok { return nil, txs.ErrTxDuplicateAddress } - acc, err := acm.GetMutableAccount(accountGetter, out.Address) + acc, err := state.GetMutableAccount(accountGetter, out.Address) if err != nil { return nil, err } @@ -1173,7 +1182,7 @@ func validateOutputs(outs []*txs.TxOutput) (uint64, error) { return total, nil } -func adjustByInputs(accs map[acm.Address]acm.MutableAccount, ins []*txs.TxInput, logger logging_types.InfoTraceLogger) error { +func adjustByInputs(accs map[acm.Address]acm.MutableAccount, ins []*txs.TxInput, logger *logging.Logger) error { for _, in := range ins { acc := accs[in.Address] if acc == nil { @@ -1188,7 +1197,7 @@ func adjustByInputs(accs map[acm.Address]acm.MutableAccount, ins []*txs.TxInput, if err != nil { return err } - logging.TraceMsg(logger, "Incrementing sequence number for SendTx (adjustByInputs)", + logger.TraceMsg("Incrementing sequence number for SendTx (adjustByInputs)", "tag", "sequence", "account", acc.Address(), "old_sequence", acc.Sequence(), @@ -1216,9 +1225,9 @@ func adjustByOutputs(accs map[acm.Address]acm.MutableAccount, outs []*txs.TxOutp //--------------------------------------------------------------- // Get permission on an account or fall back to global value -func HasPermission(accountGetter acm.Getter, acc acm.Account, perm ptypes.PermFlag, logger logging_types.InfoTraceLogger) bool { +func HasPermission(accountGetter state.AccountGetter, acc acm.Account, perm ptypes.PermFlag, logger *logging.Logger) bool { if perm > permission.AllPermFlags { - logging.InfoMsg(logger, + logger.InfoMsg( fmt.Sprintf("HasPermission called on invalid permission 0b%b (invalid) > 0b%b (maximum) ", perm, permission.AllPermFlags), "invalid_permission", perm, @@ -1226,21 +1235,21 @@ func HasPermission(accountGetter acm.Getter, acc acm.Account, perm ptypes.PermFl return false } - permString := permission.PermissionsString(perm) + permString := permission.String(perm) - v, err := acc.Permissions().Base.Compose(permission.GlobalAccountPermissions(accountGetter).Base).Get(perm) + v, err := acc.Permissions().Base.Compose(state.GlobalAccountPermissions(accountGetter).Base).Get(perm) if err != nil { - logging.TraceMsg(logger, "Error obtaining permission value (will default to false/deny)", + logger.TraceMsg("Error obtaining permission value (will default to false/deny)", "perm_flag", permString, structure.ErrorKey, err) } if v { - logging.TraceMsg(logger, "Account has permission", + logger.TraceMsg("Account has permission", "account_address", acc.Address, "perm_flag", permString) } else { - logging.TraceMsg(logger, "Account does not have permission", + logger.TraceMsg("Account does not have permission", "account_address", acc.Address, "perm_flag", permString) } @@ -1248,8 +1257,8 @@ func HasPermission(accountGetter acm.Getter, acc acm.Account, perm ptypes.PermFl } // TODO: for debug log the failed accounts -func hasSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, - logger logging_types.InfoTraceLogger) bool { +func hasSendPermission(accountGetter state.AccountGetter, accs map[acm.Address]acm.MutableAccount, + logger *logging.Logger) bool { for _, acc := range accs { if !HasPermission(accountGetter, acc, permission.Send, logger) { return false @@ -1258,23 +1267,23 @@ func hasSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.Mutabl return true } -func hasNamePermission(accountGetter acm.Getter, acc acm.Account, - logger logging_types.InfoTraceLogger) bool { +func hasNamePermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { return HasPermission(accountGetter, acc, permission.Name, logger) } -func hasCallPermission(accountGetter acm.Getter, acc acm.Account, - logger logging_types.InfoTraceLogger) bool { +func hasCallPermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { return HasPermission(accountGetter, acc, permission.Call, logger) } -func hasCreateContractPermission(accountGetter acm.Getter, acc acm.Account, - logger logging_types.InfoTraceLogger) bool { +func hasCreateContractPermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { return HasPermission(accountGetter, acc, permission.CreateContract, logger) } -func hasCreateAccountPermission(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, - logger logging_types.InfoTraceLogger) bool { +func hasCreateAccountPermission(accountGetter state.AccountGetter, accs map[acm.Address]acm.MutableAccount, + logger *logging.Logger) bool { for _, acc := range accs { if !HasPermission(accountGetter, acc, permission.CreateAccount, logger) { return false @@ -1283,13 +1292,13 @@ func hasCreateAccountPermission(accountGetter acm.Getter, accs map[acm.Address]a return true } -func hasBondPermission(accountGetter acm.Getter, acc acm.Account, - logger logging_types.InfoTraceLogger) bool { +func hasBondPermission(accountGetter state.AccountGetter, acc acm.Account, + logger *logging.Logger) bool { return HasPermission(accountGetter, acc, permission.Bond, logger) } -func hasBondOrSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.Account, - logger logging_types.InfoTraceLogger) bool { +func hasBondOrSendPermission(accountGetter state.AccountGetter, accs map[acm.Address]acm.Account, + logger *logging.Logger) bool { for _, acc := range accs { if !HasPermission(accountGetter, acc, permission.Bond, logger) { if !HasPermission(accountGetter, acc, permission.Send, logger) { diff --git a/execution/execution_test.go b/execution/execution_test.go index 47e913ce9e727a98a0a835a18a6491be7f23c13a..70c9fe37010d22db449edd90e59ba4d668311f12 100644 --- a/execution/execution_test.go +++ b/execution/execution_test.go @@ -23,6 +23,7 @@ import ( "time" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" . "github.com/hyperledger/burrow/binary" bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/event" @@ -33,8 +34,9 @@ import ( evm_events "github.com/hyperledger/burrow/execution/evm/events" "github.com/hyperledger/burrow/execution/evm/sha3" "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/permission/snatives" ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" "github.com/stretchr/testify/require" @@ -111,7 +113,7 @@ x - roles: has, add, rm // keys var users = makeUsers(10) -var logger = loggers.NewNoopInfoTraceLogger() +var logger = logging.NewNoopLogger() var deterministicGenesis = genesis.NewDeterministicGenesis(34059836243380576) var testGenesisDoc, testPrivAccounts, _ = deterministicGenesis. GenesisDoc(3, true, 1000, 1, true, 1000) @@ -165,8 +167,8 @@ func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.Ge } } -//func getAccount(state acm.Getter, address acm.Address) acm.MutableAccount { -// acc, _ := acm.GetMutableAccount(state, address) +//func getAccount(state state.AccountGetter, address acm.Address) acm.MutableAccount { +// acc, _ := state.GetMutableAccount(state, address) // return acc //} @@ -961,7 +963,7 @@ func TestSNativeTx(t *testing.T) { snativeArgs = snativePermTestInputTx("setGlobal", users[3], permission.CreateContract, true) testSNativeTxExpectFail(t, batchCommitter, snativeArgs) testSNativeTxExpectPass(t, batchCommitter, permission.SetGlobal, snativeArgs) - acc = getAccount(batchCommitter.stateCache, permission.GlobalPermissionsAddress) + acc = getAccount(batchCommitter.stateCache, acm.GlobalPermissionsAddress) if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { t.Fatal("expected permission to be set true") } @@ -1717,8 +1719,8 @@ func makeGenesisState(numAccounts int, randBalance bool, minBalance uint64, numV return s0, privAccounts } -func getAccount(state acm.Getter, address acm.Address) acm.MutableAccount { - acc, _ := acm.GetMutableAccount(state, address) +func getAccount(accountGetter state.AccountGetter, address acm.Address) acm.MutableAccount { + acc, _ := state.GetMutableAccount(accountGetter, address) return acc } @@ -1814,15 +1816,17 @@ func testSNativeCALL(t *testing.T, expectPass bool, batchCommitter *executor, do } } -func testSNativeTxExpectFail(t *testing.T, batchCommitter *executor, snativeArgs permission.PermArgs) { +func testSNativeTxExpectFail(t *testing.T, batchCommitter *executor, snativeArgs snatives.PermArgs) { testSNativeTx(t, false, batchCommitter, 0, snativeArgs) } -func testSNativeTxExpectPass(t *testing.T, batchCommitter *executor, perm ptypes.PermFlag, snativeArgs permission.PermArgs) { +func testSNativeTxExpectPass(t *testing.T, batchCommitter *executor, perm ptypes.PermFlag, + snativeArgs snatives.PermArgs) { testSNativeTx(t, true, batchCommitter, perm, snativeArgs) } -func testSNativeTx(t *testing.T, expectPass bool, batchCommitter *executor, perm ptypes.PermFlag, snativeArgs permission.PermArgs) { +func testSNativeTx(t *testing.T, expectPass bool, batchCommitter *executor, perm ptypes.PermFlag, + snativeArgs snatives.PermArgs) { if expectPass { acc := getAccount(batchCommitter.stateCache, users[0].Address()) acc.MutablePermissions().Base.Set(perm, true) @@ -1884,16 +1888,18 @@ func snativePermTestInputCALL(name string, user acm.PrivateAccount, perm ptypes. return } -func snativePermTestInputTx(name string, user acm.PrivateAccount, perm ptypes.PermFlag, val bool) (snativeArgs permission.PermArgs) { +func snativePermTestInputTx(name string, user acm.PrivateAccount, perm ptypes.PermFlag, + val bool) (snativeArgs snatives.PermArgs) { + switch name { case "hasBase": - snativeArgs = permission.HasBaseArgs(user.Address(), perm) + snativeArgs = snatives.HasBaseArgs(user.Address(), perm) case "unsetBase": - snativeArgs = permission.UnsetBaseArgs(user.Address(), perm) + snativeArgs = snatives.UnsetBaseArgs(user.Address(), perm) case "setBase": - snativeArgs = permission.SetBaseArgs(user.Address(), perm, val) + snativeArgs = snatives.SetBaseArgs(user.Address(), perm, val) case "setGlobal": - snativeArgs = permission.SetGlobalArgs(perm, val) + snativeArgs = snatives.SetGlobalArgs(perm, val) } return } @@ -1912,14 +1918,14 @@ func snativeRoleTestInputCALL(name string, user acm.PrivateAccount, return } -func snativeRoleTestInputTx(name string, user acm.PrivateAccount, role string) (snativeArgs permission.PermArgs) { +func snativeRoleTestInputTx(name string, user acm.PrivateAccount, role string) (snativeArgs snatives.PermArgs) { switch name { case "hasRole": - snativeArgs = permission.HasRoleArgs(user.Address(), role) + snativeArgs = snatives.HasRoleArgs(user.Address(), role) case "addRole": - snativeArgs = permission.AddRoleArgs(user.Address(), role) + snativeArgs = snatives.AddRoleArgs(user.Address(), role) case "removeRole": - snativeArgs = permission.RemoveRoleArgs(user.Address(), role) + snativeArgs = snatives.RemoveRoleArgs(user.Address(), role) } return } diff --git a/execution/options.go b/execution/options.go new file mode 100644 index 0000000000000000000000000000000000000000..8b6e458b890a5ce9dd1e07e24b0806634541b77f --- /dev/null +++ b/execution/options.go @@ -0,0 +1,11 @@ +package execution + +import "github.com/hyperledger/burrow/execution/evm" + +type ExecutionOption func(*executor) + +func VMOptions(vmOptions ...func(*evm.VM)) func(*executor) { + return func(exe *executor) { + exe.vmOptions = vmOptions + } +} diff --git a/execution/state.go b/execution/state.go index 7385267b3be6395b0b17dd6decddb9a4b0d4b01a..73fb51df903ab78f9dfaa3cf77375a270c317cdd 100644 --- a/execution/state.go +++ b/execution/state.go @@ -22,10 +22,10 @@ import ( "time" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/genesis" - logging_types "github.com/hyperledger/burrow/logging/types" - "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/logging" ptypes "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/txs" "github.com/tendermint/go-wire" @@ -52,9 +52,9 @@ var ( ) // Implements account and blockchain state -var _ acm.Updater = &State{} -var _ acm.StateIterable = &State{} -var _ acm.StateWriter = &State{} +var _ state.AccountUpdater = &State{} +var _ state.Iterable = &State{} +var _ state.Writer = &State{} type State struct { sync.RWMutex @@ -62,7 +62,7 @@ type State struct { version uint64 // TODO: tree *iavl.VersionedTree - logger logging_types.InfoTraceLogger + logger *logging.Logger } func NewState(db dbm.DB) *State { @@ -112,7 +112,7 @@ func MakeGenesisState(db dbm.DB, genesisDoc *genesis.GenesisDoc) (*State, error) globalPerms.Base.SetBit = ptypes.AllPermFlags permsAcc := &acm.ConcreteAccount{ - Address: permission.GlobalPermissionsAddress, + Address: acm.GlobalPermissionsAddress, Balance: 1337, Permissions: globalPerms, } diff --git a/execution/state_test.go b/execution/state_test.go index fb310574d557ffdb4b96abc0b07631678859c176..6ad9c32192ec5efc1cec705138a5ef83f2cb400d 100644 --- a/execution/state_test.go +++ b/execution/state_test.go @@ -18,6 +18,8 @@ import ( "testing" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tmlibs/db" ) @@ -25,8 +27,12 @@ import ( func TestState_UpdateAccount(t *testing.T) { state := NewState(db.NewMemDB()) account := acm.NewConcreteAccountFromSecret("Foo").MutableAccount() - account.SetStorageRoot([]byte{2, 3, 4}) - state.UpdateAccount(account) - err := state.Save() + account.MutablePermissions().Base.Perms = permission.SetGlobal | permission.HasRole + err := state.UpdateAccount(account) + err = state.Save() + + require.NoError(t, err) + accountOut, err := state.GetAccount(account.Address()) require.NoError(t, err) + assert.Equal(t, account, accountOut) } diff --git a/execution/transactor.go b/execution/transactor.go index f54cd02f60a3f56029e994ab5f699032cae206dc..9050949dc451ab537b6ee653e76f80636e659eea 100644 --- a/execution/transactor.go +++ b/execution/transactor.go @@ -20,7 +20,10 @@ import ( "sync" "time" + "runtime/debug" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/consensus/tendermint/codes" @@ -30,7 +33,6 @@ import ( 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" @@ -60,17 +62,17 @@ type Transactor interface { type transactor struct { txMtx sync.Mutex blockchain blockchain.Blockchain - state acm.StateIterable + state state.Iterable eventEmitter event.Emitter broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error - logger logging_types.InfoTraceLogger + logger *logging.Logger } var _ Transactor = &transactor{} -func NewTransactor(blockchain blockchain.Blockchain, state acm.StateIterable, eventEmitter event.Emitter, +func NewTransactor(blockchain blockchain.Blockchain, state state.Iterable, eventEmitter event.Emitter, broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error, - logger logging_types.InfoTraceLogger) *transactor { + logger *logging.Logger) *transactor { return &transactor{ blockchain: blockchain, @@ -83,14 +85,14 @@ func NewTransactor(blockchain blockchain.Blockchain, state acm.StateIterable, ev // 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) { +func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) (call *Call, err 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) + callee, err := state.GetMutableAccount(trans.state, toAddress) if err != nil { return nil, err } @@ -98,14 +100,18 @@ func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) ( return nil, fmt.Errorf("account %s does not exist", toAddress) } caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() - txCache := acm.NewStateCache(trans.state) + txCache := state.NewCache(trans.state) params := vmParams(trans.blockchain) - vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, - logging.WithScope(trans.logger, "Call")) + vmach := evm.NewVM(txCache, params, caller.Address(), nil, trans.logger.WithScope("Call")) vmach.SetPublisher(trans.eventEmitter) gas := params.GasLimit + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic from VM in simulated call: %v\n%s", r, debug.Stack()) + } + }() ret, err := vmach.Call(caller, callee, callee.Code(), data, 0, &gas) if err != nil { return nil, err @@ -120,11 +126,10 @@ func (trans *transactor) CallCode(fromAddress acm.Address, code, data []byte) (* // 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 := acm.NewStateCache(trans.state) + txCache := state.NewCache(trans.state) params := vmParams(trans.blockchain) - vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, - logging.WithScope(trans.logger, "CallCode")) + vmach := evm.NewVM(txCache, params, caller.Address(), nil, trans.logger.WithScope("CallCode")) gas := params.GasLimit ret, err := vmach.Call(caller, callee, code, data, 0, &gas) if err != nil { @@ -140,7 +145,7 @@ func (trans *transactor) BroadcastTxAsync(tx txs.Tx, callback func(res *abci_typ // Broadcast a transaction. func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { - trans.logger.Trace("method", "BroadcastTx", + trans.logger.Trace.Log("method", "BroadcastTx", "tx_hash", tx.Hash(trans.blockchain.ChainID()), "tx", tx.String()) responseCh := make(chan *abci_types.Response, 1) diff --git a/keys/integration/key_client_test.go b/keys/integration/key_client_test.go index f165074bc0a99f8115abd956dab5e51622234ecc..83f5d4b6282521292a2326b1d76ee41599b53d47 100644 --- a/keys/integration/key_client_test.go +++ b/keys/integration/key_client_test.go @@ -13,13 +13,13 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/keys" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) //var logger, _ = lifecycle.NewStdErrLogger() -var logger = loggers.NewNoopInfoTraceLogger() +var logger = logging.NewNoopLogger() const monaxKeysBin = "monax-keys" const keysHost = "localhost" diff --git a/keys/key_client.go b/keys/key_client.go index e53a30ba7093f5741daa4d30f3ae8fca4ca90674..c70cb4aecca9af987937d6f50675c5def19f4bce 100644 --- a/keys/key_client.go +++ b/keys/key_client.go @@ -20,7 +20,6 @@ import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" ) type KeyClient interface { @@ -61,7 +60,7 @@ var _ KeyClient = (*keyClient)(nil) type keyClient struct { requester Requester - logger logging_types.InfoTraceLogger + logger *logging.Logger } type signer struct { @@ -71,8 +70,8 @@ type signer struct { // keyClient.New returns a new monax-keys client for provided rpc location // Monax-keys connects over http request-responses -func NewKeyClient(rpcAddress string, logger logging_types.InfoTraceLogger) *keyClient { - logger = logging.WithScope(logger, "NewKeyClient") +func NewKeyClient(rpcAddress string, logger *logging.Logger) *keyClient { + logger = logger.WithScope("NewKeyClient") return &keyClient{ requester: DefaultRequester(rpcAddress, logger), logger: logger, diff --git a/keys/key_client_util.go b/keys/key_client_util.go index ea30faef9a2cc317a099835d18219af1173e74fa..89a41a366d359b1fc2cc90377fa20f5c9e004e08 100644 --- a/keys/key_client_util.go +++ b/keys/key_client_util.go @@ -22,7 +22,6 @@ import ( "net/http" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" ) // Monax-Keys server connects over http request-response structures @@ -34,14 +33,14 @@ type HTTPResponse struct { type Requester func(method string, args map[string]string) (response string, err error) -func DefaultRequester(rpcAddress string, logger logging_types.InfoTraceLogger) Requester { +func DefaultRequester(rpcAddress string, logger *logging.Logger) Requester { return func(method string, args map[string]string) (string, error) { body, err := json.Marshal(args) if err != nil { return "", err } endpoint := fmt.Sprintf("%s/%s", rpcAddress, method) - logging.TraceMsg(logger, "Sending request to key server", + logger.TraceMsg("Sending request to key server", "key_server_endpoint", endpoint, "request_body", string(body), ) @@ -57,7 +56,7 @@ func DefaultRequester(rpcAddress string, logger logging_types.InfoTraceLogger) R if res.Error != "" { return "", fmt.Errorf("response error when calling monax-keys at %s: %s", endpoint, res.Error) } - logging.TraceMsg(logger, "Received response from key server", + logger.TraceMsg("Received response from key server", "endpoint", endpoint, "request_body", string(body), "response", res, diff --git a/logging/adapters/stdlib/capture.go b/logging/adapters/stdlib/capture.go index 599a19a70aa36bc2107029ef12df1075055726c3..07512b75da0db9826fb82fbb127c02a78b8f034a 100644 --- a/logging/adapters/stdlib/capture.go +++ b/logging/adapters/stdlib/capture.go @@ -19,22 +19,21 @@ import ( "log" kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/logging" ) -func Capture(stdLibLogger log.Logger, - logger types.InfoTraceLogger) io.Writer { +func Capture(stdLibLogger log.Logger, logger *logging.Logger) io.Writer { adapter := newAdapter(logger) stdLibLogger.SetOutput(adapter) return adapter } -func CaptureRootLogger(logger types.InfoTraceLogger) io.Writer { +func CaptureRootLogger(logger *logging.Logger) io.Writer { adapter := newAdapter(logger) log.SetOutput(adapter) return adapter } -func newAdapter(logger types.InfoTraceLogger) io.Writer { - return kitlog.NewStdlibAdapter(logger) +func newAdapter(logger *logging.Logger) io.Writer { + return kitlog.NewStdlibAdapter(logger.Trace) } diff --git a/logging/config/config.go b/logging/config/config.go index 9eba4085bbb8a47a0cabd45f003e548690541a8d..dca01d2ed6049399c11a7f1d251c9a71afa874e8 100644 --- a/logging/config/config.go +++ b/logging/config/config.go @@ -9,10 +9,12 @@ import ( "encoding/json" "github.com/BurntSushi/toml" + "github.com/hyperledger/burrow/logging/loggers" ) type LoggingConfig struct { - RootSink *SinkConfig `toml:",omitempty"` + RootSink *SinkConfig `toml:",omitempty"` + ExcludeTrace bool } // For encoding a top-level '[logging]' TOML table @@ -22,7 +24,7 @@ type LoggingConfigWrapper struct { func DefaultNodeLoggingConfig() *LoggingConfig { return &LoggingConfig{ - RootSink: Sink().SetOutput(StderrOutput()), + RootSink: Sink().SetOutput(StderrOutput().SetFormat(loggers.JSONFormat)), } } @@ -53,28 +55,6 @@ func (lc *LoggingConfig) JSONString() string { return JSONString(lc) } -func LoggingConfigFromMap(loggingRootMap map[string]interface{}) (*LoggingConfig, error) { - lc := new(LoggingConfig) - buf := new(bytes.Buffer) - enc := toml.NewEncoder(buf) - // TODO: [Silas] consider using strongly typed config/struct mapping everywhere - // (!! unfortunately the way we are using viper - // to pass around a untyped bag of config means that we don't get keys mapped - // according to their metadata `toml:"Name"` tags. So we are re-encoding to toml - // and then decoding into the strongly type struct as a work-around) - // Encode the map back to TOML - err := enc.Encode(loggingRootMap) - if err != nil { - return nil, err - } - // Decode into struct into the LoggingConfig struct - _, err = toml.Decode(buf.String(), lc) - if err != nil { - return nil, err - } - return lc, nil -} - func TOMLString(v interface{}) string { buf := new(bytes.Buffer) encoder := toml.NewEncoder(buf) diff --git a/logging/config/presets/instructions.go b/logging/config/presets/instructions.go index 2844ae8bc19f8453033e536c611d3e8039580ec9..9077d9e8242dc19423e5f1faf913ca97a9e650ec 100644 --- a/logging/config/presets/instructions.go +++ b/logging/config/presets/instructions.go @@ -6,7 +6,6 @@ import ( "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). @@ -64,7 +63,7 @@ var instructions = []Instruction{ return push(stack, config.Sink().SetTransform(config.PruneTransform(structure.TraceKey, structure.RunId)), config.Sink().SetTransform(config.FilterTransform(config.IncludeWhenAllMatch, - structure.ChannelKey, types.InfoChannelName)), + structure.ChannelKey, structure.InfoChannelName)), config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, structure.ComponentKey, "Tendermint", "module", "p2p", @@ -88,7 +87,7 @@ var instructions = []Instruction{ builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { sink := peek(stack) ensureFilter(sink) - sink.Transform.FilterConfig.AddPredicate(structure.ChannelKey, types.InfoChannelName) + sink.Transform.FilterConfig.AddPredicate(structure.ChannelKey, structure.InfoChannelName) return stack, ops, nil }, }, diff --git a/logging/config/presets/instructions_test.go b/logging/config/presets/instructions_test.go index c1da2f87b96d1c4eb84afe20de2da7a97a4f9dfb..adbfe1b0a5fd4bc6863c372c4a61a60a33f0f25c 100644 --- a/logging/config/presets/instructions_test.go +++ b/logging/config/presets/instructions_test.go @@ -6,7 +6,6 @@ import ( "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" ) @@ -16,11 +15,11 @@ func TestBuildSinkConfig(t *testing.T) { require.NoError(t, err) expectedSink := config.Sink(). SetTransform(config.FilterTransform(config.IncludeWhenAnyMatches, - structure.ChannelKey, types.InfoChannelName)).SetOutput(config.StdoutOutput().SetFormat(loggers.TerminalFormat)).AddSinks( + structure.ChannelKey, structure.InfoChannelName)).SetOutput(config.StdoutOutput().SetFormat(loggers.TerminalFormat)).AddSinks( config.Sink().SetTransform(config.FilterTransform(config.NoFilterMode, - structure.ChannelKey, types.InfoChannelName)).SetOutput(config.StderrOutput()).AddSinks( + structure.ChannelKey, structure.InfoChannelName)).SetOutput(config.StderrOutput()).AddSinks( config.Sink().SetTransform(config.FilterTransform(config.NoFilterMode, - structure.ChannelKey, types.InfoChannelName)).SetOutput(config.StdoutOutput()))) + structure.ChannelKey, structure.InfoChannelName)).SetOutput(config.StdoutOutput()))) //fmt.Println(config.JSONString(expectedSink), "\n", config.JSONString(builtSink)) assert.Equal(t, config.JSONString(expectedSink), config.JSONString(builtSink)) @@ -32,7 +31,7 @@ func TestMinimalPreset(t *testing.T) { expectedSink := config.Sink(). AddSinks(config.Sink().SetTransform(config.PruneTransform(structure.TraceKey, structure.RunId)). AddSinks(config.Sink().SetTransform(config.FilterTransform(config.IncludeWhenAllMatch, - structure.ChannelKey, types.InfoChannelName)). + structure.ChannelKey, structure.InfoChannelName)). AddSinks(config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, structure.ComponentKey, "Tendermint", "module", "p2p", diff --git a/logging/config/sinks.go b/logging/config/sinks.go index 169e886f68fa075bcd0d34fc54b792f609c7d95b..aa30a69e1e07172d6e9751b7a21cd8515ded60ed 100644 --- a/logging/config/sinks.go +++ b/logging/config/sinks.go @@ -6,7 +6,6 @@ import ( "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" ) @@ -32,9 +31,10 @@ const ( // Remove key-val pairs from each log line Prune transformType = "prune" // Add key value pairs to each log line - Label transformType = "label" - Capture transformType = "capture" - Sort transformType = "sort" + Label transformType = "label" + Capture transformType = "capture" + Sort transformType = "sort" + Vectorise transformType = "vectorise" // TODO [Silas]: add 'flush on exit' transform which flushes the buffer of // CaptureLogger to its OutputLogger a non-passthrough capture when an exit @@ -199,6 +199,8 @@ func FileOutput(path string) *OutputConfig { } } +// Transforms + func CaptureTransform(name string, bufferCap int, passthrough bool) *TransformConfig { return &TransformConfig{ TransformType: Capture, @@ -275,6 +277,12 @@ func SortTransform(keys ...string) *TransformConfig { } } +func VectoriseTransform() *TransformConfig { + return &TransformConfig{ + TransformType: Vectorise, + } +} + // Logger formation func (sinkConfig *SinkConfig) BuildLogger() (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { return BuildLoggerFromSinkConfig(sinkConfig, make(map[string]*loggers.CaptureLogger)) @@ -404,6 +412,8 @@ func BuildTransformLogger(transformConfig *TransformConfig, captures map[string] return nil, nil, fmt.Errorf("sort transform specified but no SortConfig provided") } return loggers.SortLogger(outputLogger, transformConfig.SortConfig.Keys...), captures, nil + case Vectorise: + return loggers.VectorValuedLogger(outputLogger), captures, nil default: return nil, captures, fmt.Errorf("could not build logger for transform: '%s'", transformConfig.TransformType) } @@ -411,7 +421,7 @@ func BuildTransformLogger(transformConfig *TransformConfig, captures map[string] func signalPassthroughLogger(ifSignalLogger kitlog.Logger, otherwiseLogger kitlog.Logger) kitlog.Logger { return kitlog.LoggerFunc(func(keyvals ...interface{}) error { - if logging.Signal(keyvals) != "" { + if structure.Signal(keyvals) != "" { return ifSignalLogger.Log(keyvals...) } return otherwiseLogger.Log(keyvals...) diff --git a/logging/convention.go b/logging/convention.go deleted file mode 100644 index 65dc2e5b98106caa86768139dbf509e0c321e92d..0000000000000000000000000000000000000000 --- a/logging/convention.go +++ /dev/null @@ -1,69 +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 logging - -import ( - kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/structure" - "github.com/hyperledger/burrow/logging/types" -) - -// Helper functions for InfoTraceLoggers, sort of extension methods to loggers -// to centralise and establish logging conventions on top of in with the base -// logging interface - -// Record structured Info log line with a message -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{}) error { - return Msg(kitlog.LoggerFunc(logger.Trace), message, keyvals...) -} - -// Establish or extend the scope of this logger by appending scopeName to the Scope vector. -// Like With the logging scope is append only but can be used to provide parenthetical scopes by hanging on to the -// parent scope and using once the scope has been exited. The scope mechanism does is agnostic to the type of scope -// so can be used to identify certain segments of the call stack, a lexical scope, or any other nested scope. -func WithScope(logger types.InfoTraceLogger, scopeName string) types.InfoTraceLogger { - // InfoTraceLogger will collapse successive (ScopeKey, scopeName) pairs into a vector in the order which they appear - return logger.With(structure.ScopeKey, scopeName) -} - -// Record a structured log line with a message -func Msg(logger kitlog.Logger, message string, keyvals ...interface{}) error { - 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 e68a26021c129b918d8ea1b3d142d6fe441e6341..d130904c2e5b9f2bf3d21aaadd1edda29409eadb 100644 --- a/logging/lifecycle/lifecycle.go +++ b/logging/lifecycle/lifecycle.go @@ -30,7 +30,6 @@ import ( "github.com/eapache/channels" kitlog "github.com/go-kit/kit/log" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/types" "github.com/streadway/simpleuuid" ) @@ -38,8 +37,8 @@ import ( // to set up their root logger and capture any other logging output. // Obtain a logger from a LoggingConfig -func NewLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (types.InfoTraceLogger, error) { - var logger types.InfoTraceLogger +func NewLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (*logging.Logger, error) { + var logger *logging.Logger var errCh channels.Channel var err error if loggingConfig == nil { @@ -48,11 +47,14 @@ func NewLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (types.Info return nil, err } } else { - outputLogger, err := infoTraceLoggerFromLoggingConfig(loggingConfig) + outputLogger, err := loggerFromLoggingConfig(loggingConfig) if err != nil { return nil, err } logger, errCh = NewLogger(outputLogger) + if loggingConfig.ExcludeTrace { + logger.Trace = kitlog.NewNopLogger() + } } go func() { err := <-errCh.Out() @@ -66,9 +68,8 @@ func NewLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (types.Info // Hot swap logging config by replacing output loggers of passed InfoTraceLogger // with those built from loggingConfig -func SwapOutputLoggersFromLoggingConfig(logger types.InfoTraceLogger, - loggingConfig *config.LoggingConfig) error { - outputLogger, err := infoTraceLoggerFromLoggingConfig(loggingConfig) +func SwapOutputLoggersFromLoggingConfig(logger *logging.Logger, loggingConfig *config.LoggingConfig) error { + outputLogger, err := loggerFromLoggingConfig(loggingConfig) if err != nil { return err } @@ -76,38 +77,38 @@ func SwapOutputLoggersFromLoggingConfig(logger types.InfoTraceLogger, return nil } -func NewStdErrLogger() (types.InfoTraceLogger, channels.Channel, error) { - logger, err := loggers.NewStreamLogger(os.Stderr, loggers.TerminalFormat) +func NewStdErrLogger() (*logging.Logger, channels.Channel, error) { + outputLogger, err := loggers.NewStreamLogger(os.Stderr, loggers.TerminalFormat) if err != nil { return nil, nil, err } - itLogger, errCh := NewLogger(logger) - return itLogger, errCh, nil + logger, errCh := NewLogger(outputLogger) + return logger, errCh, nil } // Provided a standard logger that outputs to the supplied underlying outputLogger -func NewLogger(outputLogger kitlog.Logger) (types.InfoTraceLogger, channels.Channel) { - infoTraceLogger, errCh := loggers.NewInfoTraceLogger(outputLogger) +func NewLogger(outputLogger kitlog.Logger) (*logging.Logger, channels.Channel) { + logger, errCh := logging.NewLogger(outputLogger) // Create a random ID based on start time uuid, _ := simpleuuid.NewTime(time.Now()) var runId string if uuid != nil { runId = uuid.String() } - return logging.WithMetadata(infoTraceLogger.With(structure.RunId, runId)), errCh + return logger.With(structure.RunId, runId), errCh } -func JustLogger(logger types.InfoTraceLogger, _ channels.Channel) types.InfoTraceLogger { +func JustLogger(logger *logging.Logger, _ channels.Channel) *logging.Logger { return logger } -func CaptureStdlibLogOutput(infoTraceLogger types.InfoTraceLogger) { +func CaptureStdlibLogOutput(infoTraceLogger *logging.Logger) { stdlib.CaptureRootLogger(infoTraceLogger. With(structure.CapturedLoggingSourceKey, "stdlib_log")) } // Helpers -func infoTraceLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (kitlog.Logger, error) { +func loggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (kitlog.Logger, error) { outputLogger, _, err := loggingConfig.RootSink.BuildLogger() if err != nil { return nil, err diff --git a/logging/lifecycle/lifecycle_test.go b/logging/lifecycle/lifecycle_test.go index c38e6491406a376edd1c962ecf89aadf2405bff9..91ae8a16cbf995a751657ffe4074cc5f818dc968 100644 --- a/logging/lifecycle/lifecycle_test.go +++ b/logging/lifecycle/lifecycle_test.go @@ -13,7 +13,7 @@ func TestNewLoggerFromLoggingConfig(t *testing.T) { reader := CaptureStderr(t, func() { logger, err := NewLoggerFromLoggingConfig(nil) assert.NoError(t, err) - logger.Info("Quick", "Test") + logger.Info.Log("Quick", "Test") }) line, _, err := reader.ReadLine() assert.NoError(t, err) diff --git a/logging/logger.go b/logging/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..51346c4f18d34ebd7c60603ee562afbf724c2509 --- /dev/null +++ b/logging/logger.go @@ -0,0 +1,149 @@ +// 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 logging + +import ( + "github.com/eapache/channels" + kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging/structure" +) + +// InfoTraceLogger maintains provides two logging 'channels' that are interlaced +// to provide a coarse grained filter to distinguish human-consumable 'Info' +// messages and execution level 'Trace' messages. +type Logger struct { + // Send a log message to the Info channel, formed of a sequence of key value + // pairs. Info messages should be operationally interesting to a human who is + // monitoring the logs. But not necessarily a human who is trying to + // understand or debug the system. Any handled errors or warnings should be + // sent to the Info channel (where you may wish to tag them with a suitable + // key-value pair to categorise them as such). + Info kitlog.Logger + // Send an log message to the Trace channel, formed of a sequence of key-value + // pairs. Trace messages can be used for any state change in the system that + // may be of interest to a machine consumer or a human who is trying to debug + // the system or trying to understand the system in detail. If the messages + // are very point-like and contain little structure, consider using a metric + // instead. + Trace kitlog.Logger + Output *kitlog.SwapLogger +} + +// Create an InfoTraceLogger by passing the initial outputLogger. +func NewLogger(outputLogger kitlog.Logger) (*Logger, channels.Channel) { + // We will never halt the progress of a log emitter. If log output takes too + // long will start dropping log lines by using a ring buffer. + swapLogger := new(kitlog.SwapLogger) + swapLogger.Swap(outputLogger) + wrappedOutputLogger, errCh := wrapOutputLogger(swapLogger) + return &Logger{ + Output: swapLogger, + // logging contexts + Info: kitlog.With(wrappedOutputLogger, + structure.ChannelKey, structure.InfoChannelName, + ), + Trace: kitlog.With(wrappedOutputLogger, + structure.ChannelKey, structure.TraceChannelName, + ), + }, errCh +} + +func NewNoopLogger() *Logger { + return &Logger{ + Info: kitlog.NewNopLogger(), + Trace: kitlog.NewNopLogger(), + Output: new(kitlog.SwapLogger), + } +} + +// A logging context (see go-kit log's Context). Takes a sequence key values +// via With or WithPrefix and ensures future calls to log will have those +// contextual values appended to the call to an underlying logger. +// Values can be dynamic by passing an instance of the kitlog.Valuer interface +// This provides an interface version of the kitlog.Context struct to be used +// For implementations that wrap a kitlog.Context. In addition it makes no +// assumption about the name or signature of the logging method(s). +// See InfoTraceLogger +func (l *Logger) With(keyvals ...interface{}) *Logger { + return &Logger{ + Output: l.Output, + Info: kitlog.With(l.Info, keyvals...), + Trace: kitlog.With(l.Trace, keyvals...), + } +} + +// Establish a context on the Info channel keeping Trace the same +func (l *Logger) WithInfo(keyvals ...interface{}) *Logger { + return &Logger{ + Output: l.Output, + Info: kitlog.With(l.Info, keyvals...), + Trace: l.Trace, + } +} + +// Establish a context on the Trace channel keeping Info the same +func (l *Logger) WithTrace(keyvals ...interface{}) *Logger { + return &Logger{ + Output: l.Output, + Info: l.Info, + Trace: kitlog.With(l.Trace, keyvals...), + } +} + +func (l *Logger) WithPrefix(keyvals ...interface{}) *Logger { + return &Logger{ + Output: l.Output, + Info: kitlog.WithPrefix(l.Info, keyvals...), + Trace: kitlog.WithPrefix(l.Trace, keyvals...), + } +} + +// Hot swap the underlying outputLogger with another one to re-route messages +func (l *Logger) SwapOutput(infoLogger kitlog.Logger) { + l.Output.Swap(infoLogger) +} + +// Record structured Info lo`g line with a message +func (l *Logger) InfoMsg(message string, keyvals ...interface{}) error { + return Msg(l.Info, message, keyvals...) +} + +// Record structured Trace log line with a message +func (l *Logger) TraceMsg(message string, keyvals ...interface{}) error { + return Msg(l.Trace, message, keyvals...) +} + +// Establish or extend the scope of this logger by appending scopeName to the Scope vector. +// Like With the logging scope is append only but can be used to provide parenthetical scopes by hanging on to the +// parent scope and using once the scope has been exited. The scope mechanism does is agnostic to the type of scope +// so can be used to identify certain segments of the call stack, a lexical scope, or any other nested scope. +func (l *Logger) WithScope(scopeName string) *Logger { + // InfoTraceLogger will collapse successive (ScopeKey, scopeName) pairs into a vector in the order which they appear + return l.With(structure.ScopeKey, scopeName) +} + +// Record a structured log line with a message +func Msg(logger kitlog.Logger, message string, keyvals ...interface{}) error { + prepended := structure.CopyPrepend(keyvals, structure.MessageKey, message) + return logger.Log(prepended...) +} + +// 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 loggers.NonBlockingLogger(loggers.BurrowFormatLogger(outputLogger)) + return loggers.BurrowFormatLogger(outputLogger), channels.NewDeadChannel() +} diff --git a/logging/loggers/info_trace_logger_test.go b/logging/logger_test.go similarity index 82% rename from logging/loggers/info_trace_logger_test.go rename to logging/logger_test.go index 2356748dc102e873c86d02a327c0da1c0fa984cf..05c0858c1764171c1776ca9be766e5aed6ab3c7f 100644 --- a/logging/loggers/info_trace_logger_test.go +++ b/logging/logger_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package loggers +package logging import ( "os" @@ -23,11 +23,11 @@ import ( func TestLogger(t *testing.T) { stderrLogger := kitlog.NewLogfmtLogger(os.Stderr) - logger, _ := NewInfoTraceLogger(stderrLogger) - logger.Trace("hello", "barry") + logger, _ := NewLogger(stderrLogger) + logger.Trace.Log("hello", "barry") } func TestNewNoopInfoTraceLogger(t *testing.T) { - logger := NewNoopInfoTraceLogger() - logger.Trace("goodbye", "trevor") + logger := NewNoopLogger() + logger.Trace.Log("goodbye", "trevor") } diff --git a/logging/loggers/burrow_format_logger.go b/logging/loggers/burrow_format_logger.go index 51f025e73434f2cfdd4c46a146f503dc30acbd72..f32d0933de7c18fd8abe6d640f529460158253a1 100644 --- a/logging/loggers/burrow_format_logger.go +++ b/logging/loggers/burrow_format_logger.go @@ -45,6 +45,8 @@ func (bfl *burrowFormatLogger) Log(keyvals ...interface{}) error { func(key interface{}, value interface{}) (interface{}, interface{}) { switch v := value.(type) { case string: + case fmt.Stringer: + value = v.String() case []byte: value = fmt.Sprintf("%X", v) case time.Time: diff --git a/logging/loggers/capture_logger.go b/logging/loggers/capture_logger.go index e195c3a454c24fdd9d8056cf937197a27642b7a5..a470582afd0093fe7bdfe53b87ff7de951da92a4 100644 --- a/logging/loggers/capture_logger.go +++ b/logging/loggers/capture_logger.go @@ -16,13 +16,12 @@ type CaptureLogger struct { var _ kitlog.Logger = (*CaptureLogger)(nil) -// Capture logger captures output set to it into a buffer logger and retains -// a reference to an output logger (the logger whose input it is capturing). -// It can optionally passthrough logs to the output logger. +// Capture logger captures output sent to it in a buffer retaining +// a reference to its output logger (the logger whose input it is capturing). +// It can optionally pass logs through to the output logger. // Because it holds a reference to its output it can also be used to coordinate -// Flushing of the buffer to the output logger in exceptional circumstances only -func NewCaptureLogger(outputLogger kitlog.Logger, bufferCap channels.BufferCap, - passthrough bool) *CaptureLogger { +// Flushing of the buffer to the output logger in special circumstances. +func NewCaptureLogger(outputLogger kitlog.Logger, bufferCap channels.BufferCap, passthrough bool) *CaptureLogger { return &CaptureLogger{ bufferLogger: NewChannelLogger(bufferCap), outputLogger: outputLogger, @@ -41,17 +40,17 @@ func (cl *CaptureLogger) Log(keyvals ...interface{}) error { // Sets whether the CaptureLogger is forwarding log lines sent to it through // to its output logger. Concurrently safe. func (cl *CaptureLogger) SetPassthrough(passthrough bool) { - cl.RWMutex.Lock() + cl.Lock() cl.passthrough = passthrough - cl.RWMutex.Unlock() + cl.Unlock() } // Gets whether the CaptureLogger is forwarding log lines sent to through to its // OutputLogger. Concurrently Safe. func (cl *CaptureLogger) Passthrough() bool { - cl.RWMutex.RLock() + cl.RLock() passthrough := cl.passthrough - cl.RWMutex.RUnlock() + cl.RUnlock() return passthrough } diff --git a/logging/loggers/info_trace_logger.go b/logging/loggers/info_trace_logger.go deleted file mode 100644 index df51014787d13d90075847869a52f0a01b7d9955..0000000000000000000000000000000000000000 --- a/logging/loggers/info_trace_logger.go +++ /dev/null @@ -1,103 +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 loggers - -import ( - "github.com/eapache/channels" - kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/structure" - "github.com/hyperledger/burrow/logging/types" -) - -type infoTraceLogger struct { - infoContext kitlog.Logger - traceContext kitlog.Logger - outputLogger *kitlog.SwapLogger - outputLoggerErrors channels.Channel -} - -// Interface assertions -var _ types.InfoTraceLogger = (*infoTraceLogger)(nil) -var _ kitlog.Logger = (types.InfoTraceLogger)(nil) - -// Create an InfoTraceLogger by passing the initial outputLogger. -func NewInfoTraceLogger(outputLogger kitlog.Logger) (types.InfoTraceLogger, channels.Channel) { - // We will never halt the progress of a log emitter. If log output takes too - // long will start dropping log lines by using a ring buffer. - swapLogger := new(kitlog.SwapLogger) - swapLogger.Swap(outputLogger) - wrappedOutputLogger, errCh := wrapOutputLogger(swapLogger) - return &infoTraceLogger{ - outputLogger: swapLogger, - outputLoggerErrors: errCh, - // logging contexts - infoContext: kitlog.With(wrappedOutputLogger, - structure.ChannelKey, types.InfoChannelName, - ), - traceContext: kitlog.With(wrappedOutputLogger, - structure.ChannelKey, types.TraceChannelName, - ), - }, errCh -} - -func NewNoopInfoTraceLogger() types.InfoTraceLogger { - logger, _ := NewInfoTraceLogger(nil) - return logger -} - -func (l *infoTraceLogger) With(keyvals ...interface{}) types.InfoTraceLogger { - return &infoTraceLogger{ - outputLogger: l.outputLogger, - infoContext: kitlog.With(l.infoContext, keyvals...), - traceContext: kitlog.With(l.traceContext, keyvals...), - } -} - -func (l *infoTraceLogger) WithPrefix(keyvals ...interface{}) types.InfoTraceLogger { - return &infoTraceLogger{ - outputLogger: l.outputLogger, - infoContext: kitlog.WithPrefix(l.infoContext, keyvals...), - traceContext: kitlog.WithPrefix(l.traceContext, keyvals...), - } -} - -func (l *infoTraceLogger) Info(keyvals ...interface{}) error { - return l.infoContext.Log(keyvals...) -} - -func (l *infoTraceLogger) Trace(keyvals ...interface{}) error { - return l.traceContext.Log(keyvals...) -} - -func (l *infoTraceLogger) SwapOutput(infoLogger kitlog.Logger) { - l.outputLogger.Swap(infoLogger) -} - -// If logged to as a plain kitlog logger presume the message is for Trace -// This favours keeping Info reasonably quiet. Note that an InfoTraceLogger -// aware adapter can make its own choices, but we tend to think of logs from -// dependencies as less interesting than logs generated by us or specifically -// routed by us. -func (l *infoTraceLogger) Log(keyvals ...interface{}) error { - return l.Trace(keyvals...) -} - -// 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 outputLogger, channels.NewDeadChannel() - return VectorValuedLogger(SortLogger(BurrowFormatLogger(outputLogger), - structure.ChannelKey, structure.MessageKey, structure.TimeKey, structure.ComponentKey)), channels.NewDeadChannel() -} diff --git a/logging/loggers/output_loggers.go b/logging/loggers/output_loggers.go index c39d3873cfaf21d424e1bba17509f21e43c253eb..d22f77f8bfc446a4f79ff78466738ab9e88c472f 100644 --- a/logging/loggers/output_loggers.go +++ b/logging/loggers/output_loggers.go @@ -8,9 +8,7 @@ import ( kitlog "github.com/go-kit/kit/log" "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 ( @@ -37,7 +35,7 @@ func NewStreamLogger(writer io.Writer, format string) (kitlog.Logger, error) { case TerminalFormat: logger = term.NewLogger(writer, kitlog.NewLogfmtLogger, func(keyvals ...interface{}) term.FgBgColor { switch structure.Value(keyvals, structure.ChannelKey) { - case types.TraceChannelName: + case structure.TraceChannelName: return term.FgBgColor{Fg: term.DarkGreen} default: return term.FgBgColor{Fg: term.Yellow} @@ -52,7 +50,7 @@ func NewStreamLogger(writer io.Writer, format string) (kitlog.Logger, error) { } // Don't log signals return kitlog.LoggerFunc(func(keyvals ...interface{}) error { - if logging.Signal(keyvals) != "" { + if structure.Signal(keyvals) != "" { return nil } return logger.Log(keyvals...) @@ -69,7 +67,7 @@ func NewFileLogger(path string, formatName string) (kitlog.Logger, error) { return nil, err } return kitlog.LoggerFunc(func(keyvals ...interface{}) error { - if logging.Signal(keyvals) == structure.SyncSignal { + if structure.Signal(keyvals) == structure.SyncSignal { return f.Sync() } return streamLogger.Log(keyvals...) diff --git a/logging/loggers/output_loggers_test.go b/logging/loggers/output_loggers_test.go index 77e948fc07c0f765fa6b3d8210de3a71c49943ac..6e3d83817f923a24fb698cb226aa5fc7d8e81ac3 100644 --- a/logging/loggers/output_loggers_test.go +++ b/logging/loggers/output_loggers_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,7 +21,7 @@ func TestNewFileLogger(t *testing.T) { err = fileLogger.Log("foo", "bar") require.NoError(t, err) - err = logging.Sync(fileLogger) + err = structure.Sync(fileLogger) require.NoError(t, err) bs, err := ioutil.ReadFile(logPath) @@ -37,7 +37,7 @@ func TestNewStreamLogger(t *testing.T) { err = logger.Log("oh", "my") require.NoError(t, err) - err = logging.Sync(logger) + err = structure.Sync(logger) require.NoError(t, err) assert.Equal(t, "oh=my\n", string(buf.Bytes())) diff --git a/logging/metadata.go b/logging/metadata.go index 1699fb1a2c08e43997673ecf6789d7bc1a501262..485c8f2be568d20c70c0d9041c44d035401d4ce2 100644 --- a/logging/metadata.go +++ b/logging/metadata.go @@ -15,12 +15,9 @@ package logging 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" ) const ( @@ -32,15 +29,12 @@ const ( infoTraceLoggerCallDepth = 5 ) -var defaultTimestampUTCValuer kitlog.Valuer = func() interface{} { - return time.Now() -} - -func WithMetadata(infoTraceLogger types.InfoTraceLogger) types.InfoTraceLogger { - return infoTraceLogger.With(structure.TimeKey, defaultTimestampUTCValuer, - structure.TraceKey, TraceValuer()) +func WithMetadata(logger *Logger) *Logger { + return logger.With(structure.TimeKey, kitlog.DefaultTimestampUTC, + structure.TraceKey, TraceValuer(), + structure.CallerKey, kitlog.Caller(infoTraceLoggerCallDepth)) } func TraceValuer() kitlog.Valuer { - return func() interface{} { return stack.Trace().TrimBelow(stack.Caller(infoTraceLoggerCallDepth - 1)) } + return func() interface{} { return stack.Trace() } } diff --git a/logging/structure/structure.go b/logging/structure/structure.go index 97cfd7d3b11925ee940f6518e0e6ad7e34bf7777..41fc03128b496f5ef24d535bb0a1e1939ff86c9d 100644 --- a/logging/structure/structure.go +++ b/logging/structure/structure.go @@ -17,6 +17,8 @@ package structure import ( "encoding/json" "fmt" + + kitlog "github.com/go-kit/kit/log" ) const ( @@ -46,7 +48,9 @@ const ( // Provides special instructions (that may be ignored) to downstream loggers SignalKey = "__signal__" // The sync signal instructs sync-able loggers to sync - SyncSignal = "__sync__" + SyncSignal = "__sync__" + InfoChannelName = "Info" + TraceChannelName = "Trace" ) // Pull the specified values from a structured log line into a map. @@ -247,3 +251,22 @@ func StringifyKey(key interface{}) string { } } } + +// 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(SignalKey, 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] == SignalKey { + signal, ok := keyvals[last].(string) + if ok { + return signal + } + } + return "" +} diff --git a/logging/types/info_trace_logger.go b/logging/types/info_trace_logger.go deleted file mode 100644 index 613cf07ca00f6bc307998dcddee092938280e40f..0000000000000000000000000000000000000000 --- a/logging/types/info_trace_logger.go +++ /dev/null @@ -1,58 +0,0 @@ -package types - -import kitlog "github.com/go-kit/kit/log" - -const ( - InfoChannelName = "Info" - TraceChannelName = "Trace" - - InfoLevelName = InfoChannelName - TraceLevelName = TraceChannelName -) - -// InfoTraceLogger maintains provides two logging 'channels' that are interlaced -// to provide a coarse grained filter to distinguish human-consumable 'Info' -// messages and execution level 'Trace' messages. -type InfoTraceLogger interface { - // Send a log message to the default channel of the implementation - kitlog.Logger - - // Send an log message to the Info channel, formed of a sequence of key value - // pairs. Info messages should be operationally interesting to a human who is - // monitoring the logs. But not necessarily a human who is trying to - // understand or debug the system. Any handled errors or warnings should be - // sent to the Info channel (where you may wish to tag them with a suitable - // key-value pair to categorise them as such). - Info(keyvals ...interface{}) error - - // Send an log message to the Trace channel, formed of a sequence of key-value - // pairs. Trace messages can be used for any state change in the system that - // may be of interest to a machine consumer or a human who is trying to debug - // the system or trying to understand the system in detail. If the messages - // are very point-like and contain little structure, consider using a metric - // instead. - Trace(keyvals ...interface{}) error - - // A logging context (see go-kit log's Context). Takes a sequence key values - // via With or WithPrefix and ensures future calls to log will have those - // contextual values appended to the call to an underlying logger. - // Values can be dynamic by passing an instance of the kitlog.Valuer interface - // This provides an interface version of the kitlog.Context struct to be used - // For implementations that wrap a kitlog.Context. In addition it makes no - // assumption about the name or signature of the logging method(s). - // See InfoTraceLogger - - // Establish a context by appending contextual key-values to any existing - // contextual values - With(keyvals ...interface{}) InfoTraceLogger - - // Establish a context by prepending contextual key-values to any existing - // contextual values - WithPrefix(keyvals ...interface{}) InfoTraceLogger - - // Hot swap the underlying outputLogger with another one to re-route messages - SwapOutput(outputLogger kitlog.Logger) -} - -// Interface assertions -var _ kitlog.Logger = (InfoTraceLogger)(nil) diff --git a/permission/constants.go b/permission/constants.go deleted file mode 100644 index c795fda0f50cd608b65e3a3b22d7ba0975cebd80..0000000000000000000000000000000000000000 --- a/permission/constants.go +++ /dev/null @@ -1,10 +0,0 @@ -package permission - -import ( - "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/binary" -) - -var ( - GlobalPermissionsAddress = account.Address(binary.Zero160) -) diff --git a/permission/permissions.go b/permission/permissions.go index a314ae4c0f48b0e2afef277a8947fceff2a58635..5bfcf61c9205c1a6ae222e8110298759c8cd8a9a 100644 --- a/permission/permissions.go +++ b/permission/permissions.go @@ -18,7 +18,6 @@ import ( "fmt" "strings" - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/permission/types" ) @@ -195,21 +194,3 @@ func PermStringToFlag(perm string) (types.PermFlag, error) { return 0, fmt.Errorf("unknown permission %s", perm) } } - -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 { - if state == nil { - return types.AccountPermissions{ - Roles: []string{}, - } - } - return GlobalPermissionsAccount(state).Permissions() -} diff --git a/permission/snatives.go b/permission/snatives/snatives.go similarity index 72% rename from permission/snatives.go rename to permission/snatives/snatives.go index a29aeac949047c94015b8808995454dee4a35c96..2323d83b641b49ec8065b578499a663efe74303e 100644 --- a/permission/snatives.go +++ b/permission/snatives/snatives.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package permission +package snatives import ( "fmt" "strings" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/permission/types" ) @@ -35,12 +36,12 @@ type PermArgs struct { func (pa PermArgs) String() string { body := make([]string, 0, 5) - body = append(body, fmt.Sprintf("PermFlag: %s", PermissionsString(pa.PermFlag))) + body = append(body, fmt.Sprintf("PermFlag: %s", permission.String(pa.PermFlag))) if pa.Address != nil { body = append(body, fmt.Sprintf("Address: %s", *pa.Address)) } if pa.Permission != nil { - body = append(body, fmt.Sprintf("Permission: %s", PermissionsString(*pa.Permission))) + body = append(body, fmt.Sprintf("Permission: %s", permission.String(*pa.Permission))) } if pa.Role != nil { body = append(body, fmt.Sprintf("Role: %s", *pa.Role)) @@ -54,10 +55,10 @@ func (pa PermArgs) String() string { func (pa PermArgs) EnsureValid() error { pf := pa.PermFlag // Address - if pa.Address == nil && pf != SetGlobal { + if pa.Address == nil && pf != permission.SetGlobal { return fmt.Errorf("PermArgs for PermFlag %v requires Address to be provided but was nil", pf) } - if pf == HasRole || pf == AddRole || pf == RemoveRole { + if pf == permission.HasRole || pf == permission.AddRole || pf == permission.RemoveRole { // Role if pa.Role == nil { return fmt.Errorf("PermArgs for PermFlag %v requires Role to be provided but was nil", pf) @@ -66,48 +67,48 @@ func (pa PermArgs) EnsureValid() error { } else if pa.Permission == nil { return fmt.Errorf("PermArgs for PermFlag %v requires Permission to be provided but was nil", pf) // Value - } else if (pf == SetBase || pf == SetGlobal) && pa.Value == nil { + } else if (pf == permission.SetBase || pf == permission.SetGlobal) && pa.Value == nil { return fmt.Errorf("PermArgs for PermFlag %v requires Value to be provided but was nil", pf) } return nil } -func HasBaseArgs(address acm.Address, permission types.PermFlag) PermArgs { +func HasBaseArgs(address acm.Address, permFlag types.PermFlag) PermArgs { return PermArgs{ - PermFlag: HasBase, + PermFlag: permission.HasBase, Address: &address, - Permission: &permission, + Permission: &permFlag, } } -func SetBaseArgs(address acm.Address, permission types.PermFlag, value bool) PermArgs { +func SetBaseArgs(address acm.Address, permFlag types.PermFlag, value bool) PermArgs { return PermArgs{ - PermFlag: SetBase, + PermFlag: permission.SetBase, Address: &address, - Permission: &permission, + Permission: &permFlag, Value: &value, } } -func UnsetBaseArgs(address acm.Address, permission types.PermFlag) PermArgs { +func UnsetBaseArgs(address acm.Address, permFlag types.PermFlag) PermArgs { return PermArgs{ - PermFlag: UnsetBase, + PermFlag: permission.UnsetBase, Address: &address, - Permission: &permission, + Permission: &permFlag, } } -func SetGlobalArgs(permission types.PermFlag, value bool) PermArgs { +func SetGlobalArgs(permFlag types.PermFlag, value bool) PermArgs { return PermArgs{ - PermFlag: SetGlobal, - Permission: &permission, + PermFlag: permission.SetGlobal, + Permission: &permFlag, Value: &value, } } func HasRoleArgs(address acm.Address, role string) PermArgs { return PermArgs{ - PermFlag: HasRole, + PermFlag: permission.HasRole, Address: &address, Role: &role, } @@ -115,7 +116,7 @@ func HasRoleArgs(address acm.Address, role string) PermArgs { func AddRoleArgs(address acm.Address, role string) PermArgs { return PermArgs{ - PermFlag: AddRole, + PermFlag: permission.AddRole, Address: &address, Role: &role, } @@ -123,7 +124,7 @@ func AddRoleArgs(address acm.Address, role string) PermArgs { func RemoveRoleArgs(address acm.Address, role string) PermArgs { return PermArgs{ - PermFlag: RemoveRole, + PermFlag: permission.RemoveRole, Address: &address, Role: &role, } diff --git a/permission/snatives_test.go b/permission/snatives/snatives_test.go similarity index 65% rename from permission/snatives_test.go rename to permission/snatives/snatives_test.go index f9126a8d14121d3839081eb91934090962df0201..76545151df366826e1f24deb5a6f100af8e48b2d 100644 --- a/permission/snatives_test.go +++ b/permission/snatives/snatives_test.go @@ -1,18 +1,19 @@ -package permission +package snatives import ( "testing" + "github.com/hyperledger/burrow/permission" "github.com/stretchr/testify/assert" ) func TestPermArgs_String(t *testing.T) { role := "foo" value := true - permission := AddRole | RemoveRole + permFlag := permission.AddRole | permission.RemoveRole permArgs := PermArgs{ - PermFlag: SetBase, - Permission: &permission, + PermFlag: permission.SetBase, + Permission: &permFlag, Role: &role, Value: &value, } diff --git a/permission/util.go b/permission/util.go index c2289e4335a0b0206d3fd792e81ec1ce5409b422..656761cd83030127313ede68c528ffe8973b00cd 100644 --- a/permission/util.go +++ b/permission/util.go @@ -115,7 +115,7 @@ func BasePermissionsString(basePermissions types.BasePermissions) string { return strings.Join(permStrings, " | ") } -func PermissionsString(permFlag types.PermFlag) string { +func String(permFlag types.PermFlag) string { permStrings, err := PermFlagToStringList(permFlag) if err != nil { return UnknownString diff --git a/process/process.go b/process/process.go index 960f5b7ef3579ef2e13250cade835a234c220363..b0058d280388146feeda2b251cc3c2ea2bde3e43 100644 --- a/process/process.go +++ b/process/process.go @@ -18,8 +18,9 @@ func (sf ShutdownFunc) Shutdown(ctx context.Context) error { } type Launcher struct { - Name string - Launch func() (Process, error) + Name string + Disabled bool + Launch func() (Process, error) } type listenersServer struct { diff --git a/project/history.go b/project/history.go index 3c337c2fa8b914e8b73a8da3739fad7003e935a2..d4d1b5aeb2c14da54e11cb5489a9453c4b60e199 100644 --- a/project/history.go +++ b/project/history.go @@ -4,6 +4,23 @@ import ( "github.com/monax/relic" ) +// Can be used to set the commit hash version of the binary at build time with: +// `go build -ldflags "-X github.com/hyperledger/burrow/project.commit=$(git rev-parse --short HEAD)" ./cmd/burrow` + +var commit = "" + +func Commit() string { + return commit +} + +func FullVersion() string { + version := History.CurrentVersion().String() + if commit != "" { + return version + "+commit." + commit + } + return version +} + // The releases described by version string and changes, newest release first. // The current release is taken to be the first release in the slice, and its // version determines the single authoritative version for the next release. @@ -11,6 +28,8 @@ import ( // To cut a new release add a release to the front of this slice then run the // release tagging script: ./scripts/tag_release.sh var History relic.ImmutableHistory = relic.NewHistory("Hyperledger Burrow").MustDeclareReleases( + "0.18.1", + `Fixes for deployment`, "0.18.0", `Prerelease`, "0.17.1", diff --git a/rpc/config.go b/rpc/config.go index 0b35c840b6be1d3f7191272f9cab469126c769cd..89936dec7e18fd410ef853d82da6226a2e24b174 100644 --- a/rpc/config.go +++ b/rpc/config.go @@ -3,22 +3,31 @@ package rpc import "github.com/hyperledger/burrow/rpc/v0/server" type RPCConfig struct { - V0 *V0Config `json:",omitempty" toml:",omitempty"` - TM *TMConfig `json:",omitempty" toml:",omitempty"` + V0 *V0Config `json:",omitempty" toml:",omitempty"` + TM *TMConfig `json:",omitempty" toml:",omitempty"` + Profiler *ProfilerConfig `json:",omitempty" toml:",omitempty"` } type TMConfig struct { + Disabled bool ListenAddress string } type V0Config struct { - Server *server.ServerConfig + Disabled bool + Server *server.ServerConfig +} + +type ProfilerConfig struct { + Disabled bool + ListenAddress string } func DefaultRPCConfig() *RPCConfig { return &RPCConfig{ - TM: DefaultTMConfig(), - V0: DefaultV0Config(), + TM: DefaultTMConfig(), + V0: DefaultV0Config(), + Profiler: DefaultProfilerConfig(), } } func DefaultV0Config() *V0Config { @@ -32,3 +41,10 @@ func DefaultTMConfig() *TMConfig { ListenAddress: ":46657", } } + +func DefaultProfilerConfig() *ProfilerConfig { + return &ProfilerConfig{ + Disabled: true, + ListenAddress: ":6060", + } +} diff --git a/rpc/result.go b/rpc/result.go index b43d3749f198e0fed2335efaa309c9b3e50d6fce..2e51a2e65f7c205d1b505f771a430d2d54556576 100644 --- a/rpc/result.go +++ b/rpc/result.go @@ -135,6 +135,21 @@ type ResultGetAccount struct { Account *acm.ConcreteAccount } +type AccountHumanReadable struct { + Address acm.Address + PublicKey acm.PublicKey + Sequence uint64 + Balance uint64 + Code []string + StorageRoot string + Permissions []string + Roles []string +} + +type ResultGetAccountHumanReadable struct { + Account *AccountHumanReadable +} + type ResultBroadcastTx struct { txs.Receipt } diff --git a/rpc/service.go b/rpc/service.go index c91e7645ce90ec09fad5236c4267f5b977a14a04..13d698346c30f135f78793536539b3263cff6eb9 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -19,6 +19,7 @@ import ( "fmt" acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/binary" bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/consensus/tendermint/query" @@ -26,73 +27,36 @@ import ( "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/permission" "github.com/hyperledger/burrow/project" "github.com/hyperledger/burrow/txs" tm_types "github.com/tendermint/tendermint/types" + "github.com/tmthrgd/go-hex" ) // Magic! Should probably be configurable, but not shouldn't be so huge we // end up DoSing ourselves. const MaxBlockLookback = 100 -type SubscribableService interface { - // Events - Subscribe(ctx context.Context, subscriptionID string, eventID string, callback func(*ResultEvent) bool) error - Unsubscribe(ctx context.Context, subscriptionID string) error -} - // Base service that provides implementation for all underlying RPC methods -type Service interface { - SubscribableService - // Transact - Transactor() execution.Transactor - // List mempool transactions pass -1 for all unconfirmed transactions - ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) - // Status - Status() (*ResultStatus, error) - NetInfo() (*ResultNetInfo, error) - // Accounts - GetAccount(address acm.Address) (*ResultGetAccount, error) - ListAccounts(predicate func(acm.Account) bool) (*ResultListAccounts, error) - GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) - DumpStorage(address acm.Address) (*ResultDumpStorage, error) - // Blockchain - Genesis() (*ResultGenesis, error) - ChainId() (*ResultChainId, error) - GetBlock(height uint64) (*ResultGetBlock, error) - ListBlocks(minHeight, maxHeight uint64) (*ResultListBlocks, error) - // Consensus - ListValidators() (*ResultListValidators, error) - DumpConsensusState() (*ResultDumpConsensusState, error) - Peers() (*ResultPeers, error) - // Names - GetName(name string) (*ResultGetName, error) - ListNames(predicate func(*execution.NameRegEntry) bool) (*ResultListNames, error) - // Private keys and signing - GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) -} - -type service struct { +type Service struct { ctx context.Context - state acm.StateIterable + iterable state.Iterable subscribable event.Subscribable nameReg execution.NameRegIterable blockchain bcm.Blockchain transactor execution.Transactor nodeView query.NodeView - logger logging_types.InfoTraceLogger + logger *logging.Logger } -var _ Service = &service{} - -func NewService(ctx context.Context, state acm.StateIterable, nameReg execution.NameRegIterable, +func NewService(ctx context.Context, iterable state.Iterable, nameReg execution.NameRegIterable, subscribable event.Subscribable, blockchain bcm.Blockchain, transactor execution.Transactor, - nodeView query.NodeView, logger logging_types.InfoTraceLogger) *service { + nodeView query.NodeView, logger *logging.Logger) *Service { - return &service{ + return &Service{ ctx: ctx, - state: state, + iterable: iterable, nameReg: nameReg, subscribable: subscribable, blockchain: blockchain, @@ -103,8 +67,8 @@ func NewService(ctx context.Context, state acm.StateIterable, nameReg execution. } // Provides a sub-service with only the subscriptions methods -func NewSubscribableService(subscribable event.Subscribable, logger logging_types.InfoTraceLogger) *service { - return &service{ +func NewSubscribableService(subscribable event.Subscribable, logger *logging.Logger) *Service { + return &Service{ ctx: context.Background(), subscribable: subscribable, logger: logger.With(structure.ComponentKey, "Service"), @@ -113,11 +77,11 @@ func NewSubscribableService(subscribable event.Subscribable, logger logging_type // Transacting... -func (s *service) Transactor() execution.Transactor { +func (s *Service) Transactor() execution.Transactor { return s.transactor } -func (s *service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) { +func (s *Service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) { // Get all transactions for now transactions, err := s.nodeView.MempoolTransactions(maxTxs) if err != nil { @@ -133,11 +97,11 @@ func (s *service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, err }, nil } -func (s *service) Subscribe(ctx context.Context, subscriptionID string, eventID string, +func (s *Service) Subscribe(ctx context.Context, subscriptionID string, eventID string, callback func(resultEvent *ResultEvent) bool) error { queryBuilder := event.QueryForEventID(eventID) - logging.InfoMsg(s.logger, "Subscribing to events", + s.logger.InfoMsg("Subscribing to events", "query", queryBuilder.String(), "subscription_id", subscriptionID, "event_id", eventID) @@ -145,7 +109,7 @@ func (s *service) Subscribe(ctx context.Context, subscriptionID string, eventID func(message interface{}) bool { resultEvent, err := NewResultEvent(eventID, message) if err != nil { - logging.InfoMsg(s.logger, "Received event that could not be mapped to ResultEvent", + s.logger.InfoMsg("Received event that could not be mapped to ResultEvent", structure.ErrorKey, err, "subscription_id", subscriptionID, "event_id", eventID) @@ -155,8 +119,8 @@ func (s *service) Subscribe(ctx context.Context, subscriptionID string, eventID }) } -func (s *service) Unsubscribe(ctx context.Context, subscriptionID string) error { - logging.InfoMsg(s.logger, "Unsubscribing from events", +func (s *Service) Unsubscribe(ctx context.Context, subscriptionID string) error { + s.logger.InfoMsg("Unsubscribing from events", "subscription_id", subscriptionID) err := s.subscribable.UnsubscribeAll(ctx, subscriptionID) if err != nil { @@ -165,7 +129,7 @@ func (s *service) Unsubscribe(ctx context.Context, subscriptionID string) error return nil } -func (s *service) Status() (*ResultStatus, error) { +func (s *Service) Status() (*ResultStatus, error) { tip := s.blockchain.Tip() latestHeight := tip.LastBlockHeight() var ( @@ -193,7 +157,7 @@ func (s *service) Status() (*ResultStatus, error) { }, nil } -func (s *service) ChainId() (*ResultChainId, error) { +func (s *Service) ChainId() (*ResultChainId, error) { return &ResultChainId{ ChainName: s.blockchain.GenesisDoc().ChainName, ChainId: s.blockchain.ChainID(), @@ -201,7 +165,7 @@ func (s *service) ChainId() (*ResultChainId, error) { }, nil } -func (s *service) Peers() (*ResultPeers, error) { +func (s *Service) Peers() (*ResultPeers, error) { peers := make([]*Peer, s.nodeView.Peers().Size()) for i, peer := range s.nodeView.Peers().List() { peers[i] = &Peer{ @@ -214,7 +178,7 @@ func (s *service) Peers() (*ResultPeers, error) { }, nil } -func (s *service) NetInfo() (*ResultNetInfo, error) { +func (s *Service) NetInfo() (*ResultNetInfo, error) { listening := s.nodeView.IsListening() listeners := []string{} for _, listener := range s.nodeView.Listeners() { @@ -231,27 +195,27 @@ func (s *service) NetInfo() (*ResultNetInfo, error) { }, nil } -func (s *service) Genesis() (*ResultGenesis, error) { +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) +func (s *Service) GetAccount(address acm.Address) (*ResultGetAccount, error) { + acc, err := s.iterable.GetAccount(address) if err != nil { return nil, err } - s.logger.Trace("method", "GetAccount", + s.logger.Trace.Log("method", "GetAccount", "address", address, "sequence", acc.Sequence()) return &ResultGetAccount{Account: acm.AsConcreteAccount(acc)}, nil } -func (s *service) ListAccounts(predicate func(acm.Account) bool) (*ResultListAccounts, error) { +func (s *Service) ListAccounts(predicate func(acm.Account) bool) (*ResultListAccounts, error) { accounts := make([]*acm.ConcreteAccount, 0) - s.state.IterateAccounts(func(account acm.Account) (stop bool) { + s.iterable.IterateAccounts(func(account acm.Account) (stop bool) { if predicate(account) { accounts = append(accounts, acm.AsConcreteAccount(account)) } @@ -264,8 +228,8 @@ func (s *service) ListAccounts(predicate func(acm.Account) bool) (*ResultListAcc }, nil } -func (s *service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) { - account, err := s.state.GetAccount(address) +func (s *Service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) { + account, err := s.iterable.GetAccount(address) if err != nil { return nil, err } @@ -273,7 +237,7 @@ func (s *service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage return nil, fmt.Errorf("UnknownAddress: %s", address) } - value, err := s.state.GetStorage(address, binary.LeftPadWord256(key)) + value, err := s.iterable.GetStorage(address, binary.LeftPadWord256(key)) if err != nil { return nil, err } @@ -283,8 +247,8 @@ func (s *service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage return &ResultGetStorage{Key: key, Value: value.UnpadLeft()}, nil } -func (s *service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { - account, err := s.state.GetAccount(address) +func (s *Service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { + account, err := s.iterable.GetAccount(address) if err != nil { return nil, err } @@ -292,7 +256,7 @@ func (s *service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { return nil, fmt.Errorf("UnknownAddress: %X", address) } var storageItems []StorageItem - s.state.IterateStorage(address, func(key, value binary.Word256) (stop bool) { + s.iterable.IterateStorage(address, func(key, value binary.Word256) (stop bool) { storageItems = append(storageItems, StorageItem{Key: key.UnpadLeft(), Value: value.UnpadLeft()}) return }) @@ -302,8 +266,38 @@ func (s *service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { }, nil } +func (s *Service) GetAccountHumanReadable(address acm.Address) (*ResultGetAccountHumanReadable, error) { + acc, err := s.iterable.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return &ResultGetAccountHumanReadable{}, nil + } + tokens, err := acc.Code().Tokens() + if acc == nil { + return &ResultGetAccountHumanReadable{}, nil + } + perms, err := permission.BasePermissionsToStringList(acc.Permissions().Base) + if acc == nil { + return &ResultGetAccountHumanReadable{}, nil + } + return &ResultGetAccountHumanReadable{ + Account: &AccountHumanReadable{ + Address: acc.Address(), + PublicKey: acc.PublicKey(), + Sequence: acc.Sequence(), + Balance: acc.Balance(), + Code: tokens, + StorageRoot: hex.EncodeUpperToString(acc.StorageRoot()), + Permissions: perms, + Roles: acc.Permissions().Roles, + }, + }, nil +} + // Name registry -func (s *service) GetName(name string) (*ResultGetName, error) { +func (s *Service) GetName(name string) (*ResultGetName, error) { entry, err := s.nameReg.GetNameRegEntry(name) if err != nil { return nil, err @@ -314,7 +308,7 @@ func (s *service) GetName(name string) (*ResultGetName, error) { return &ResultGetName{Entry: entry}, nil } -func (s *service) ListNames(predicate func(*execution.NameRegEntry) bool) (*ResultListNames, error) { +func (s *Service) ListNames(predicate func(*execution.NameRegEntry) bool) (*ResultListNames, error) { var names []*execution.NameRegEntry s.nameReg.IterateNameRegEntries(func(entry *execution.NameRegEntry) (stop bool) { if predicate(entry) { @@ -328,7 +322,7 @@ func (s *service) ListNames(predicate func(*execution.NameRegEntry) bool) (*Resu }, nil } -func (s *service) GetBlock(height uint64) (*ResultGetBlock, error) { +func (s *Service) GetBlock(height uint64) (*ResultGetBlock, error) { return &ResultGetBlock{ Block: s.nodeView.BlockStore().LoadBlock(int64(height)), BlockMeta: s.nodeView.BlockStore().LoadBlockMeta(int64(height)), @@ -340,7 +334,7 @@ func (s *service) GetBlock(height uint64) (*ResultGetBlock, error) { // 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) ListBlocks(minHeight, maxHeight uint64) (*ResultListBlocks, error) { +func (s *Service) ListBlocks(minHeight, maxHeight uint64) (*ResultListBlocks, error) { latestHeight := s.blockchain.Tip().LastBlockHeight() if minHeight == 0 { @@ -365,7 +359,7 @@ func (s *service) ListBlocks(minHeight, maxHeight uint64) (*ResultListBlocks, er }, nil } -func (s *service) ListValidators() (*ResultListValidators, error) { +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() @@ -380,7 +374,7 @@ func (s *service) ListValidators() (*ResultListValidators, error) { }, nil } -func (s *service) DumpConsensusState() (*ResultDumpConsensusState, error) { +func (s *Service) DumpConsensusState() (*ResultDumpConsensusState, error) { peerRoundState, err := s.nodeView.PeerRoundStates() if err != nil { return nil, err @@ -391,7 +385,7 @@ func (s *service) DumpConsensusState() (*ResultDumpConsensusState, error) { }, nil } -func (s *service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) { +func (s *Service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) { privateAccount, err := acm.GeneratePrivateAccount() if err != nil { return nil, err diff --git a/rpc/tm/integration/shared.go b/rpc/tm/integration/shared.go index d7ad492993dc992d02412d5b61ba299fcd514d73..2849634c4418dd5e1219ca256fe8c7ac93bbc037 100644 --- a/rpc/tm/integration/shared.go +++ b/rpc/tm/integration/shared.go @@ -35,9 +35,9 @@ import ( "github.com/hyperledger/burrow/core" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/rpc" tm_client "github.com/hyperledger/burrow/rpc/tm/client" @@ -80,7 +80,7 @@ func TestWrapper(runner func() int) int { os.Chdir(testDir) tmConf := tm_config.DefaultConfig() - logger := loggers.NewNoopInfoTraceLogger() + logger := logging.NewNoopLogger() if debugLogging { var err error // Change config as needed @@ -107,7 +107,8 @@ func TestWrapper(runner func() int) int { privValidator := validator.NewPrivValidatorMemory(privateAccounts[0], privateAccounts[0]) genesisDoc = testGenesisDoc() - kernel, err := core.NewKernel(context.Background(), privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), logger) + kernel, err := core.NewKernel(context.Background(), privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), + nil, logger) if err != nil { panic(err) } diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go index 7da5d22b553dba8d0a91746a1b853da5522ac267..719f8cd1e4c927a6e98c6649db5b77327eff61fd 100644 --- a/rpc/tm/methods.go +++ b/rpc/tm/methods.go @@ -9,7 +9,6 @@ import ( "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/txs" gorpc "github.com/tendermint/tendermint/rpc/lib/server" @@ -18,6 +17,7 @@ import ( // Method names const ( + BroadcastTx = "broadcast_tx" Subscribe = "subscribe" Unsubscribe = "unsubscribe" @@ -26,19 +26,19 @@ const ( NetInfo = "net_info" // Accounts - ListAccounts = "list_accounts" - GetAccount = "get_account" - GetStorage = "get_storage" - DumpStorage = "dump_storage" + ListAccounts = "list_accounts" + GetAccount = "get_account" + GetStorage = "get_storage" + DumpStorage = "dump_storage" + GetAccountHuman = "get_account_human" // Simulated call Call = "call" CallCode = "call_code" // Names - GetName = "get_name" - ListNames = "list_names" - BroadcastTx = "broadcast_tx" + GetName = "get_name" + ListNames = "list_names" // Blockchain Genesis = "genesis" @@ -58,8 +58,8 @@ const ( const SubscriptionTimeoutSeconds = 5 * time.Second -func GetRoutes(service rpc.Service, logger logging_types.InfoTraceLogger) map[string]*gorpc.RPCFunc { - logger = logging.WithScope(logger, "GetRoutes") +func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*gorpc.RPCFunc { + logger = logger.WithScope("GetRoutes") return map[string]*gorpc.RPCFunc{ // Transact BroadcastTx: gorpc.NewRPCFunc(func(tx txs.Wrapper) (*rpc.ResultBroadcastTx, error) { @@ -107,7 +107,7 @@ func GetRoutes(service rpc.Service, logger logging_types.InfoTraceLogger) map[st keepAlive := wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse( EventResponseID(wsCtx.Request.ID, eventID), resultEvent)) if !keepAlive { - logging.InfoMsg(logger, "dropping subscription because could not write to websocket", + logger.InfoMsg("dropping subscription because could not write to websocket", "subscription_id", subscriptionID, "event_id", eventID) } @@ -146,9 +146,10 @@ func GetRoutes(service rpc.Service, logger logging_types.InfoTraceLogger) map[st }) }, ""), - GetAccount: gorpc.NewRPCFunc(service.GetAccount, "address"), - GetStorage: gorpc.NewRPCFunc(service.GetStorage, "address,key"), - DumpStorage: gorpc.NewRPCFunc(service.DumpStorage, "address"), + GetAccount: gorpc.NewRPCFunc(service.GetAccount, "address"), + GetStorage: gorpc.NewRPCFunc(service.GetStorage, "address,key"), + DumpStorage: gorpc.NewRPCFunc(service.DumpStorage, "address"), + GetAccountHuman: gorpc.NewRPCFunc(service.GetAccountHumanReadable, "address"), // Blockchain Genesis: gorpc.NewRPCFunc(service.Genesis, ""), diff --git a/rpc/tm/server.go b/rpc/tm/server.go index 25f5f5cd8baae698fa74d7ab53af6290e6d2604c..4df1e3db5a9114ace449ddd5e6ba112788513aea 100644 --- a/rpc/tm/server.go +++ b/rpc/tm/server.go @@ -20,14 +20,14 @@ import ( "github.com/hyperledger/burrow/consensus/tendermint" "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/logging" "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" ) -func StartServer(service rpc.Service, pattern, listenAddress string, emitter event.Emitter, - logger logging_types.InfoTraceLogger) (net.Listener, error) { +func StartServer(service *rpc.Service, pattern, listenAddress string, emitter event.Emitter, + logger *logging.Logger) (net.Listener, error) { logger = logger.With(structure.ComponentKey, "RPC_TM") routes := GetRoutes(service, logger) diff --git a/rpc/v0/json_service.go b/rpc/v0/json_service.go index 50cad08a41326059d6cd6838d74ee3aab2b49eea..cac693d2a0bd5960aed26ec0cd223e5687ef858f 100644 --- a/rpc/v0/json_service.go +++ b/rpc/v0/json_service.go @@ -21,7 +21,6 @@ import ( "github.com/gin-gonic/gin" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/v0/server" ) @@ -82,20 +81,20 @@ func (jrs *JsonRpcServer) handleFunc(c *gin.Context) { // Used for Burrow. Implements server.HttpService type JSONService struct { codec rpc.Codec - service rpc.Service + service *rpc.Service eventSubs *Subscriptions defaultHandlers map[string]RequestHandlerFunc - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Create a new JSON-RPC 2.0 service for burrow (tendermint). -func NewJSONService(codec rpc.Codec, service rpc.Service, logger logging_types.InfoTraceLogger) server.HttpService { +func NewJSONService(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) server.HttpService { tmhttps := &JSONService{ codec: codec, service: service, eventSubs: NewSubscriptions(service), - logger: logging.WithScope(logger, "NewJSONService"), + logger: logger.WithScope("NewJSONService"), } dhMap := GetMethods(codec, service, tmhttps.logger) @@ -132,7 +131,7 @@ func (js *JSONService) Process(r *http.Request, w http.ResponseWriter) { mName := req.Method if handler, ok := js.defaultHandlers[mName]; ok { - logging.TraceMsg(js.logger, "Request received", + js.logger.TraceMsg("Request received", "id", req.Id, "method", req.Method, "params", string(req.Params)) diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go index aa4416b998757c5adc72856ea0c38ba380b04080..ef994e04dc76916301f9428fdf6255948c7cf2c2 100644 --- a/rpc/v0/methods.go +++ b/rpc/v0/methods.go @@ -17,7 +17,7 @@ package v0 import ( acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/execution" - logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/filters" "github.com/hyperledger/burrow/txs" @@ -63,7 +63,7 @@ const ( type RequestHandlerFunc func(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) // Private. Create a method name -> method handler map. -func GetMethods(codec rpc.Codec, service rpc.Service, logger logging_types.InfoTraceLogger) map[string]RequestHandlerFunc { +func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) map[string]RequestHandlerFunc { accountFilterFactory := filters.NewAccountFilterFactory() nameRegFilterFactory := filters.NewNameRegFilterFactory() diff --git a/rpc/v0/server/server.go b/rpc/v0/server/server.go index e79e717be00ee8aaab75fe4fd9bc4796af7c7317..9edc900f03d42fe5640e2edf8108dc1a11b2cfdc 100644 --- a/rpc/v0/server/server.go +++ b/rpc/v0/server/server.go @@ -24,7 +24,6 @@ import ( "github.com/gin-gonic/gin" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/tommy351/gin-cors" "gopkg.in/tylerb/graceful.v1" ) @@ -59,7 +58,7 @@ type ServeProcess struct { startListenChans []chan struct{} stopListenChans []chan struct{} srv *graceful.Server - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Initializes all the servers and starts listening for connections. @@ -120,7 +119,7 @@ func (serveProcess *ServeProcess) Start() error { lst = l } serveProcess.srv = srv - logging.InfoMsg(serveProcess.logger, "Server started.", + serveProcess.logger.InfoMsg("Server started.", "chain_id", serveProcess.config.ChainId, "address", serveProcess.config.Bind.Address, "port", serveProcess.config.Bind.Port) @@ -139,14 +138,14 @@ func (serveProcess *ServeProcess) Start() error { // calls 'Stop' on the process. go func() { <-serveProcess.stopChan - logging.InfoMsg(serveProcess.logger, "Close signal sent to server.") + serveProcess.logger.InfoMsg("Close signal sent to server.") serveProcess.srv.Stop(killTime) }() // Listen to the servers stop event. It is triggered when // the server has been fully shut down. go func() { <-serveProcess.srv.StopChan() - logging.InfoMsg(serveProcess.logger, "Server stop event fired. Good bye.") + serveProcess.logger.InfoMsg("Server stop event fired. Good bye.") for _, c := range serveProcess.stopListenChans { c <- struct{}{} } @@ -198,7 +197,7 @@ func (serveProcess *ServeProcess) StopEventChannel() <-chan struct{} { } // Creates a new serve process. -func NewServeProcess(config *ServerConfig, logger logging_types.InfoTraceLogger, +func NewServeProcess(config *ServerConfig, logger *logging.Logger, servers ...Server) (*ServeProcess, error) { var scfg ServerConfig if config == nil { @@ -216,14 +215,14 @@ func NewServeProcess(config *ServerConfig, logger logging_types.InfoTraceLogger, startListenChans: startListeners, stopListenChans: stopListeners, srv: nil, - logger: logging.WithScope(logger, "ServeProcess"), + logger: logger.WithScope("ServeProcess"), } return sp, nil } // Used to enable log15 logging instead of the default Gin logging. -func logHandler(logger logging_types.InfoTraceLogger) gin.HandlerFunc { - logger = logging.WithScope(logger, "ginLogHandler") +func logHandler(logger *logging.Logger) gin.HandlerFunc { + logger = logger.WithScope("ginLogHandler") return func(c *gin.Context) { path := c.Request.URL.Path @@ -236,7 +235,7 @@ func logHandler(logger logging_types.InfoTraceLogger) gin.HandlerFunc { statusCode := c.Writer.Status() comment := c.Errors.String() - logger.Info("client_ip", clientIP, + logger.Info.Log("client_ip", clientIP, "status_code", statusCode, "method", method, "path", path, diff --git a/rpc/v0/server/websocket.go b/rpc/v0/server/websocket.go index b54e84ea827517a3781e6f6ed7a301db4ae868fe..cda4f7def80b26957378e6d789db9a1e4e735f9c 100644 --- a/rpc/v0/server/websocket.go +++ b/rpc/v0/server/websocket.go @@ -24,7 +24,6 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" ) // TODO too much fluff. Should probably phase gorilla out and move closer @@ -55,7 +54,7 @@ type WebSocketServer struct { sessionManager *SessionManager config *ServerConfig allOrigins bool - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Create a new server. @@ -64,11 +63,11 @@ type WebSocketServer struct { // upgraded to websockets. Requesting a websocket connection will fail with a 503 if // the server is at capacity. func NewWebSocketServer(maxSessions uint16, service WebSocketService, - logger logging_types.InfoTraceLogger) *WebSocketServer { + logger *logging.Logger) *WebSocketServer { return &WebSocketServer{ maxSessions: maxSessions, sessionManager: NewSessionManager(maxSessions, service, logger), - logger: logging.WithScope(logger, "WebSocketServer"), + logger: logger.WithScope("WebSocketServer"), } } @@ -114,7 +113,7 @@ func (wsServer *WebSocketServer) handleFunc(c *gin.Context) { if uErr != nil { errMsg := "Failed to upgrade to websocket connection" http.Error(w, fmt.Sprintf("%s: %s", errMsg, uErr.Error()), 400) - logging.InfoMsg(wsServer.logger, errMsg, "error", uErr) + wsServer.logger.InfoMsg(errMsg, "error", uErr) return } @@ -123,12 +122,12 @@ func (wsServer *WebSocketServer) handleFunc(c *gin.Context) { if cErr != nil { errMsg := "Failed to establish websocket connection" http.Error(w, fmt.Sprintf("%s: %s", errMsg, cErr.Error()), 503) - logging.InfoMsg(wsServer.logger, errMsg, "error", cErr) + wsServer.logger.InfoMsg(errMsg, "error", cErr) return } // Start the connection. - logging.InfoMsg(wsServer.logger, "New websocket connection", + wsServer.logger.InfoMsg("New websocket connection", "session_id", session.id) session.Open() } @@ -152,13 +151,13 @@ type WSSession struct { service WebSocketService opened bool closed bool - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Write a text message to the client. func (wsSession *WSSession) Write(msg []byte) error { if wsSession.closed { - logging.InfoMsg(wsSession.logger, "Attempting to write to closed session.") + wsSession.logger.InfoMsg("Attempting to write to closed session.") return fmt.Errorf("Session is closed") } wsSession.writeChan <- msg @@ -191,7 +190,7 @@ func (wsSession *WSSession) Close() { wsSession.closed = true wsSession.wsConn.Close() wsSession.sessionManager.removeSession(wsSession.id) - logging.InfoMsg(wsSession.logger, "Closing websocket connection.", + wsSession.logger.InfoMsg("Closing websocket connection.", "remaining_active_sessions", len(wsSession.sessionManager.activeSessions)) wsSession.sessionManager.notifyClosed(wsSession) } @@ -240,14 +239,14 @@ func (wsSession *WSSession) readPump() { // Read error. if err != nil { // Socket could have been gracefully closed, so not really an error. - logging.InfoMsg(wsSession.logger, + wsSession.logger.InfoMsg( "Socket closed. Removing.", "error", err) wsSession.writeCloseChan <- struct{}{} return } if msgType != websocket.TextMessage { - logging.InfoMsg(wsSession.logger, + wsSession.logger.InfoMsg( "Receiving non text-message from client, closing.") wsSession.writeCloseChan <- struct{}{} return @@ -292,7 +291,7 @@ func (wsSession *WSSession) writePump() { err := wsSession.wsConn.WriteMessage(websocket.TextMessage, msg) if err != nil { // Could be due to the socket being closed so not really an error. - logging.InfoMsg(wsSession.logger, + wsSession.logger.InfoMsg( "Writing to socket failed. Closing.") return } @@ -320,12 +319,12 @@ type SessionManager struct { service WebSocketService openEventChans []chan *WSSession closeEventChans []chan *WSSession - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Create a new WebsocketManager. func NewSessionManager(maxSessions uint16, wss WebSocketService, - logger logging_types.InfoTraceLogger) *SessionManager { + logger *logging.Logger) *SessionManager { return &SessionManager{ maxSessions: maxSessions, activeSessions: make(map[uint]*WSSession), @@ -334,7 +333,7 @@ func NewSessionManager(maxSessions uint16, wss WebSocketService, service: wss, openEventChans: []chan *WSSession{}, closeEventChans: []chan *WSSession{}, - logger: logging.WithScope(logger, "SessionManager"), + logger: logger.WithScope("SessionManager"), } } @@ -417,8 +416,7 @@ func (sessionManager *SessionManager) createSession(wsConn *websocket.Conn) (*WS writeChan: make(chan []byte, maxMessageSize), writeCloseChan: make(chan struct{}), service: sessionManager.service, - logger: logging.WithScope(sessionManager.logger, "WSSession"). - With("session_id", newId), + logger: sessionManager.logger.WithScope("WSSession").With("session_id", newId), } sessionManager.activeSessions[conn.id] = conn return conn, nil diff --git a/rpc/v0/subscriptions.go b/rpc/v0/subscriptions.go index dbfab6f76fd6ef83e48dce613e0154f0c62da146..8bb91864790cfc3d08c6d790c5da96ca0ed2a2ec 100644 --- a/rpc/v0/subscriptions.go +++ b/rpc/v0/subscriptions.go @@ -62,12 +62,12 @@ func (subsCache *SubscriptionsCache) poll() []interface{} { // Catches events that callers subscribe to and adds them to an array ready to be polled. type Subscriptions struct { mtx *sync.RWMutex - service rpc.Service + service *rpc.Service subs map[string]*SubscriptionsCache reap bool } -func NewSubscriptions(service rpc.Service) *Subscriptions { +func NewSubscriptions(service *rpc.Service) *Subscriptions { es := &Subscriptions{ mtx: &sync.RWMutex{}, service: service, diff --git a/rpc/v0/subscriptions_test.go b/rpc/v0/subscriptions_test.go index 45d2130a32a97804c7391a02cc5bdb2371b69de4..c3032dd6f861a394a2caa3388f51c2f89b83a7dd 100644 --- a/rpc/v0/subscriptions_test.go +++ b/rpc/v0/subscriptions_test.go @@ -22,7 +22,7 @@ import ( "time" "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/stretchr/testify/assert" ) @@ -36,8 +36,8 @@ func TestSubReaping(t *testing.T) { reaperThreshold = 200 * time.Millisecond reaperTimeout = 100 * time.Millisecond - mee := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) - eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, loggers.NewNoopInfoTraceLogger())) + mee := event.NewEmitter(logging.NewNoopLogger()) + eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, logging.NewNoopLogger())) doneChan := make(chan error) go func() { for i := 0; i < NUM_SUBS; i++ { @@ -80,8 +80,8 @@ func TestSubManualClose(t *testing.T) { reaperThreshold = 10000 * time.Millisecond reaperTimeout = 10000 * time.Millisecond - mee := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) - eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, loggers.NewNoopInfoTraceLogger())) + mee := event.NewEmitter(logging.NewNoopLogger()) + eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, logging.NewNoopLogger())) doneChan := make(chan error) go func() { for i := 0; i < NUM_SUBS; i++ { @@ -128,8 +128,8 @@ func TestSubFlooding(t *testing.T) { reaperTimeout = 10000 * time.Millisecond // Crank it up. Now pressure is 10 times higher on each sub. mockInterval = 1 * time.Millisecond - mee := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) - eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, loggers.NewNoopInfoTraceLogger())) + mee := event.NewEmitter(logging.NewNoopLogger()) + eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, logging.NewNoopLogger())) doneChan := make(chan error) go func() { for i := 0; i < NUM_SUBS; i++ { diff --git a/rpc/v0/websocket_service.go b/rpc/v0/websocket_service.go index 17b84e4409b54ed63d4db14c0a85217f7d02283a..ed75f1ce0cd0ce3bb22373468a6dcb59fbee2519 100644 --- a/rpc/v0/websocket_service.go +++ b/rpc/v0/websocket_service.go @@ -22,7 +22,6 @@ import ( "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" - logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/rpc/v0/server" ) @@ -30,17 +29,17 @@ import ( // Used for Burrow. Implements WebSocketService. type WebsocketService struct { codec rpc.Codec - service rpc.Service + service *rpc.Service defaultHandlers map[string]RequestHandlerFunc - logger logging_types.InfoTraceLogger + logger *logging.Logger } // Create a new websocket service. -func NewWebsocketService(codec rpc.Codec, service rpc.Service, logger logging_types.InfoTraceLogger) server.WebSocketService { +func NewWebsocketService(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) server.WebSocketService { tmwss := &WebsocketService{ codec: codec, service: service, - logger: logging.WithScope(logger, "NewWebsocketService"), + logger: logger.WithScope("NewWebsocketService"), } dhMap := GetMethods(codec, service, tmwss.logger) // Events @@ -55,7 +54,7 @@ func (ws *WebsocketService) Process(msg []byte, session *server.WSSession) { defer func() { if r := recover(); r != nil { err := fmt.Errorf("panic in WebsocketService.Process(): %v", r) - logging.InfoMsg(ws.logger, "Panic in WebsocketService.Process()", structure.ErrorKey, err) + ws.logger.InfoMsg("Panic in WebsocketService.Process()", structure.ErrorKey, err) if !session.Closed() { ws.writeError(err.Error(), "", rpc.INTERNAL_ERROR, session) } diff --git a/txs/tx.go b/txs/tx.go index 1133e948718f5bcc0188c1e6127fa12f90f2ce61..e79c1e457fdc625f83b42aad39974237e97111db 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -21,7 +21,7 @@ import ( "io" acm "github.com/hyperledger/burrow/account" - ptypes "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/permission/snatives" "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" "golang.org/x/crypto/ripemd160" @@ -166,7 +166,7 @@ type ( PermissionsTx struct { Input *TxInput - PermArgs ptypes.PermArgs + PermArgs snatives.PermArgs txHashMemoizer } diff --git a/txs/tx_test.go b/txs/tx_test.go index 3a10bc394cd5b5f1ffb5382a8b8ccb53c9fe1b27..73c7da8e113baae7ef6f6ad8a45cbf85185dd5d9 100644 --- a/txs/tx_test.go +++ b/txs/tx_test.go @@ -22,6 +22,7 @@ import ( acm "github.com/hyperledger/burrow/account" ptypes "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/permission/snatives" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -189,7 +190,7 @@ func TestPermissionsTxSignable(t *testing.T) { Amount: 12345, Sequence: 250, }, - PermArgs: ptypes.SetBaseArgs(makeAddress("address1"), 1, true), + PermArgs: snatives.SetBaseArgs(makeAddress("address1"), 1, true), } signBytes := acm.SignBytes(chainID, permsTx) @@ -220,7 +221,7 @@ func TestTxWrapper_MarshalJSON(t *testing.T) { func TestNewPermissionsTxWithSequence(t *testing.T) { privateKey := acm.PrivateKeyFromSecret("Shhh...") - args := ptypes.SetBaseArgs(privateKey.PublicKey().Address(), ptypes.HasRole, true) + args := snatives.SetBaseArgs(privateKey.PublicKey().Address(), ptypes.HasRole, true) permTx := NewPermissionsTxWithSequence(privateKey.PublicKey(), args, 1) testTxMarshalJSON(t, permTx) } diff --git a/txs/tx_utils.go b/txs/tx_utils.go index a9056d51e07329161083ef433f3fa210e7f988c7..dd5ddae2455d1e718e05535d07b8376aa4081b56 100644 --- a/txs/tx_utils.go +++ b/txs/tx_utils.go @@ -18,7 +18,8 @@ import ( "fmt" acm "github.com/hyperledger/burrow/account" - ptypes "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/permission/snatives" ) //---------------------------------------------------------------------------- @@ -31,7 +32,7 @@ func NewSendTx() *SendTx { } } -func (tx *SendTx) AddInput(st acm.Getter, pubkey acm.PublicKey, amt uint64) error { +func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { addr := pubkey.Address() acc, err := st.GetAccount(addr) if err != nil { @@ -74,7 +75,7 @@ func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.PrivateAccoun //---------------------------------------------------------------------------- // CallTx interface for creating tx -func NewCallTx(st acm.Getter, from acm.PublicKey, to *acm.Address, data []byte, +func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte, amt, gasLimit, fee uint64) (*CallTx, error) { addr := from.Address() @@ -116,7 +117,7 @@ func (tx *CallTx) Sign(chainID string, privAccount acm.PrivateAccount) { //---------------------------------------------------------------------------- // NameTx interface for creating tx -func NewNameTx(st acm.Getter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) { +func NewNameTx(st state.AccountGetter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) { addr := from.Address() acc, err := st.GetAccount(addr) if err != nil { @@ -162,7 +163,7 @@ func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) { }, nil } -func (tx *BondTx) AddInput(st acm.Getter, pubkey acm.PublicKey, amt uint64) error { +func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { addr := pubkey.Address() acc, err := st.GetAccount(addr) if err != nil { @@ -237,7 +238,7 @@ func (tx *RebondTx) Sign(chainID string, privAccount acm.PrivateAccount) { //---------------------------------------------------------------------------- // PermissionsTx interface for creating tx -func NewPermissionsTx(st acm.Getter, from acm.PublicKey, args ptypes.PermArgs) (*PermissionsTx, error) { +func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) { addr := from.Address() acc, err := st.GetAccount(addr) if err != nil { @@ -251,7 +252,7 @@ func NewPermissionsTx(st acm.Getter, from acm.PublicKey, args ptypes.PermArgs) ( return NewPermissionsTxWithSequence(from, args, sequence), nil } -func NewPermissionsTxWithSequence(from acm.PublicKey, args ptypes.PermArgs, sequence uint64) *PermissionsTx { +func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx { input := &TxInput{ Address: from.Address(), Amount: 1, // NOTE: amounts can't be 0 ...