diff --git a/.circleci/config.yml b/.circleci/config.yml index f09a13f05a04ca6fb8512662d9bb9d05ed95d3b6..3de942fc59b4470c9e5d70eba903c1919a09ad69 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ defaults: &defaults working_directory: /go/src/github.com/hyperledger/burrow docker: - - image: circleci/golang:1.9.3 + - image: circleci/golang:1.10.1 tag_filters: &tags_filters tags: diff --git a/Gopkg.lock b/Gopkg.lock index 99eff5eeb676cebb4462eedf0560de196c54c395..f0fafcb1a93d0e3afa0e45c62c618442186f4c54 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,6 +7,12 @@ revision = "b26d9c308763d68093482582cea63d69be07a0f0" version = "v0.3.0" +[[projects]] + name = "github.com/OneOfOne/xxhash" + packages = ["."] + revision = "4e9e81466dc37c9fd94ce9ecf42020ef54112203" + version = "v1.2.1" + [[projects]] branch = "master" name = "github.com/btcsuite/btcd" @@ -474,6 +480,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "5db3ac43679cafd5f68104e93a248b62266d313aef2d8ba3bd7f3986a5a99834" + inputs-digest = "f8f72aa18c801bd7e61464decf6e8108d2f38410ec921a8e7015a1e5b8878db3" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index b1f9779a1f7fc121303e95d73ea11a438bde1664..0ac9a88a7b6b402d618eb66362e80a7a57bb13d4 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -71,10 +71,6 @@ name = "github.com/tendermint/iavl" version = "~0.5.0" -[[constraint]] - name = "github.com/tendermint/merkleeyes" - version = "~0.2.4" - [[constraint]] name = "github.com/tendermint/tendermint" version = "~0.15.0" diff --git a/Makefile b/Makefile index 77bcfd8d2fbc49f8aa49a77f49a5ea6fcf470d02..b899206273c5bf25357bcf0113b50e670ad8c063 100644 --- a/Makefile +++ b/Makefile @@ -147,8 +147,9 @@ test: check .PHONY: test_integration test_integration: @go get github.com/monax/bosmarmot/keys/cmd/monax-keys - @go test ./keys/integration -tags integration - @go test ./rpc/tm/integration -tags integration + @go test -tags integration ./keys/integration + @go test -tags integration ./rpc/v0/integration + @go test -tags integration ./rpc/tm/integration # Run integration test from bosmarmot (separated from other integration tests so we can # make exception when this test fails when we make a breaking change in Burrow) diff --git a/account/account.go b/account/account.go index ab6e987480246167cc4373884ac5bcfc7482a968..725eda71a79ebc99cbf849ece47cee5f22f877a1 100644 --- a/account/account.go +++ b/account/account.go @@ -156,8 +156,8 @@ func (acc *ConcreteAccount) String() string { return "Account{nil}" } - return fmt.Sprintf("Account{Address: %s; PublicKey: %v Balance: %v; CodeBytes: %v; StorageRoot: 0x%X; Permissions: %s}", - acc.Address, acc.PublicKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions) + return fmt.Sprintf("Account{Address: %s; Sequence: %v; PublicKey: %v Balance: %v; CodeBytes: %v; StorageRoot: 0x%X; Permissions: %s}", + acc.Address, acc.Sequence, acc.PublicKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions) } // ConcreteAccount diff --git a/account/private_account.go b/account/private_account.go index 9b705a02d518ede77b9047bc4288473259fff961..3ca56cd7f0852ed348c08f10b972c23557090790 100644 --- a/account/private_account.go +++ b/account/private_account.go @@ -20,12 +20,16 @@ import ( "github.com/tendermint/go-wire" ) -type PrivateAccount interface { +type AddressableSigner interface { Addressable - PrivateKey() PrivateKey Signer } +type PrivateAccount interface { + AddressableSigner + PrivateKey() PrivateKey +} + // type ConcretePrivateAccount struct { Address Address @@ -82,25 +86,25 @@ func (pa ConcretePrivateAccount) Sign(msg []byte) (Signature, error) { return pa.PrivateKey.Sign(msg) } -func ChainSign(pa PrivateAccount, chainID string, o Signable) Signature { - sig, err := pa.Sign(SignBytes(chainID, o)) +func ChainSign(signer Signer, chainID string, o Signable) (Signature, error) { + sig, err := signer.Sign(SignBytes(chainID, o)) if err != nil { - panic(err) + return Signature{}, err } - return sig + return sig, nil } func (pa *ConcretePrivateAccount) String() string { return fmt.Sprintf("ConcretePrivateAccount{%s}", pa.Address) } -// Convert slice of ConcretePrivateAccounts to slice of PrivateAccounts -func PrivateAccounts(concretePrivateAccounts []*ConcretePrivateAccount) []PrivateAccount { - privateAccounts := make([]PrivateAccount, len(concretePrivateAccounts)) +// Convert slice of ConcretePrivateAccounts to slice of SigningAccounts +func SigningAccounts(concretePrivateAccounts []*ConcretePrivateAccount) []AddressableSigner { + signingAccounts := make([]AddressableSigner, len(concretePrivateAccounts)) for i, cpa := range concretePrivateAccounts { - privateAccounts[i] = cpa.PrivateAccount() + signingAccounts[i] = cpa.PrivateAccount() } - return privateAccounts + return signingAccounts } // Generates a new account with private key. diff --git a/account/state/memory_state.go b/account/state/memory_state.go new file mode 100644 index 0000000000000000000000000000000000000000..35f1ad696e5e4820c751f973e1148a120980caed --- /dev/null +++ b/account/state/memory_state.go @@ -0,0 +1,80 @@ +package state + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" +) + +type MemoryState struct { + Accounts map[acm.Address]acm.Account + Storage map[acm.Address]map[binary.Word256]binary.Word256 +} + +var _ IterableWriter = &MemoryState{} + +// Get an in-memory state Iterable +func NewMemoryState() *MemoryState { + return &MemoryState{ + Accounts: make(map[acm.Address]acm.Account), + Storage: make(map[acm.Address]map[binary.Word256]binary.Word256), + } +} + +func (ms *MemoryState) GetAccount(address acm.Address) (acm.Account, error) { + return ms.Accounts[address], nil +} + +func (ms *MemoryState) UpdateAccount(updatedAccount acm.Account) error { + if updatedAccount == nil { + return fmt.Errorf("UpdateAccount passed nil account in MemoryState") + } + ms.Accounts[updatedAccount.Address()] = updatedAccount + return nil +} + +func (ms *MemoryState) RemoveAccount(address acm.Address) error { + delete(ms.Accounts, address) + return nil +} + +func (ms *MemoryState) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + storage, ok := ms.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 +} + +func (ms *MemoryState) SetStorage(address acm.Address, key, value binary.Word256) error { + storage, ok := ms.Storage[address] + if !ok { + storage = make(map[binary.Word256]binary.Word256) + ms.Storage[address] = storage + } + storage[key] = value + return nil +} + +func (ms *MemoryState) IterateAccounts(consumer func(acm.Account) (stop bool)) (stopped bool, err error) { + for _, acc := range ms.Accounts { + if consumer(acc) { + return true, nil + } + } + return false, nil +} + +func (ms *MemoryState) IterateStorage(address acm.Address, consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) { + for key, value := range ms.Storage[address] { + if consumer(key, value) { + return true, nil + } + } + return false, nil +} diff --git a/account/state/state_cache.go b/account/state/state_cache.go index 04107211e78eb756221a7c02293d8f3f3a66a8ca..a3675177e53bfe12a4ae3d1f5d84bf3abad58a6d 100644 --- a/account/state/state_cache.go +++ b/account/state/state_cache.go @@ -32,6 +32,7 @@ type Cache interface { type stateCache struct { sync.RWMutex + name string backend Reader accounts map[acm.Address]*accountInfo } @@ -44,13 +45,25 @@ type accountInfo struct { updated bool } +type CacheOption func(*stateCache) + // 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 NewCache(backend Reader) Cache { - return &stateCache{ +func NewCache(backend Reader, options ...CacheOption) Cache { + cache := &stateCache{ backend: backend, accounts: make(map[acm.Address]*accountInfo), } + for _, option := range options { + option(cache) + } + return cache +} + +func Name(name string) CacheOption { + return func(cache *stateCache) { + cache.name = name + } } func (cache *stateCache) GetAccount(address acm.Address) (acm.Account, error) { @@ -233,6 +246,13 @@ func (cache *stateCache) Flush(state IterableWriter) error { return nil } +func (cache *stateCache) String() string { + if cache.name == "" { + return fmt.Sprintf("StateCache{Length: %v}", len(cache.accounts)) + } + return fmt.Sprintf("StateCache{Name: %v; Length: %v}", cache.name, len(cache.accounts)) +} + // Get the cache accountInfo item creating it if necessary func (cache *stateCache) get(address acm.Address) (*accountInfo, error) { cache.RLock() diff --git a/account/state/state_cache_test.go b/account/state/state_cache_test.go index a0c5bc80ec8afc715c962d6d45a722ba4b75a4d0..af02081f5fda1bdc4b0ae95411f8389677f8471e 100644 --- a/account/state/state_cache_test.go +++ b/account/state/state_cache_test.go @@ -1,9 +1,10 @@ package state import ( - "fmt" "testing" + "fmt" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/execution/evm/asm" @@ -15,7 +16,7 @@ import ( func TestStateCache_GetAccount(t *testing.T) { // Build backend states for read and write readBackend := testAccounts() - writeBackend := NewCache(newTestState()) + writeBackend := NewCache(NewMemoryState()) cache := NewCache(readBackend) acc := readBackend.Accounts[addressOf("acc1")] @@ -32,9 +33,25 @@ func TestStateCache_GetAccount(t *testing.T) { assert.Equal(t, acm.AsConcreteAccount(acc), acm.AsConcreteAccount(accOut)) } +func TestStateCache_Miss(t *testing.T) { + readBackend := testAccounts() + cache := NewCache(readBackend) + + acc1Address := addressOf("acc1") + acc1, err := cache.GetAccount(acc1Address) + require.NoError(t, err) + fmt.Println(acc1) + + acc1Exp := readBackend.Accounts[acc1Address] + assert.Equal(t, acc1Exp, acc1) + acc8, err := cache.GetAccount(addressOf("acc8")) + require.NoError(t, err) + assert.Nil(t, acc8) +} + func TestStateCache_UpdateAccount(t *testing.T) { // Build backend states for read and write - backend := NewCache(newTestState()) + backend := NewCache(NewMemoryState()) cache := NewCache(backend) // Create acccount accNew := acm.NewConcreteAccountFromSecret("accNew") @@ -102,7 +119,7 @@ func TestStateCache_Sync(t *testing.T) { func TestStateCache_get(t *testing.T) { } -func testAccounts() *testState { +func testAccounts() *MemoryState { acc1 := acm.NewConcreteAccountFromSecret("acc1") acc1.Permissions.Base.Perms = permission.AddRole | permission.Send acc1.Permissions.Base.SetBit = acc1.Permissions.Base.Perms @@ -124,22 +141,8 @@ 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 -} - -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() +func account(acc acm.Account, keyvals ...string) *MemoryState { + ts := NewMemoryState() ts.Accounts[acc.Address()] = acc ts.Storage[acc.Address()] = make(map[binary.Word256]binary.Word256) for i := 0; i < len(keyvals); i += 2 { @@ -148,8 +151,8 @@ func account(acc acm.Account, keyvals ...string) *testState { return ts } -func combine(states ...*testState) *testState { - ts := newTestState() +func combine(states ...*MemoryState) *MemoryState { + ts := NewMemoryState() for _, state := range states { for _, acc := range state.Accounts { ts.Accounts[acc.Address()] = acc @@ -162,19 +165,3 @@ func combine(states ...*testState) *testState { func word(str string) binary.Word256 { return binary.LeftPadWord256([]byte(str)) } - -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/blockchain/blockchain.go b/blockchain/blockchain.go index dae49f81adc865be17b0c5f4d9d6fc1a9dee0975..c81f4524e0e516be3bdea4494033030b90b92d46 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -31,8 +31,6 @@ var stateKey = []byte("BlockchainState") // Immutable Root of blockchain type Root interface { - // ChainID precomputed from GenesisDoc - ChainID() string // GenesisHash precomputed from GenesisDoc GenesisHash() []byte GenesisDoc() genesis.GenesisDoc @@ -40,6 +38,8 @@ type Root interface { // Immutable pointer to the current tip of the blockchain type Tip interface { + // ChainID precomputed from GenesisDoc + ChainID() string // All Last* references are to the block last committed LastBlockHeight() uint64 LastBlockTime() time.Time @@ -66,12 +66,12 @@ type MutableBlockchain interface { } type root struct { - chainID string genesisHash []byte genesisDoc genesis.GenesisDoc } type tip struct { + chainID string lastBlockHeight uint64 lastBlockTime time.Time lastBlockHash []byte @@ -132,12 +132,9 @@ func NewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) *blockchain { } root := NewRoot(genesisDoc) return &blockchain{ - db: db, - root: root, - tip: &tip{ - lastBlockTime: root.genesisDoc.GenesisTime, - appHashAfterLastBlock: root.genesisHash, - }, + db: db, + root: root, + tip: NewTip(genesisDoc.ChainID(), root.genesisDoc.GenesisTime, root.genesisHash), validators: validators, } } @@ -159,19 +156,17 @@ func LoadBlockchain(db dbm.DB) (*blockchain, error) { func NewRoot(genesisDoc *genesis.GenesisDoc) *root { return &root{ - chainID: genesisDoc.ChainID(), genesisHash: genesisDoc.Hash(), genesisDoc: *genesisDoc, } } -// Create -func NewTip(lastBlockHeight uint64, lastBlockTime time.Time, lastBlockHash []byte, appHashAfterLastBlock []byte) *tip { +// Create genesis Tip +func NewTip(chainID string, genesisTime time.Time, genesisHash []byte) *tip { return &tip{ - lastBlockHeight: lastBlockHeight, - lastBlockTime: lastBlockTime, - lastBlockHash: lastBlockHash, - appHashAfterLastBlock: appHashAfterLastBlock, + chainID: chainID, + lastBlockTime: genesisTime, + appHashAfterLastBlock: genesisHash, } } @@ -239,10 +234,6 @@ func Decode(encodedState []byte) (*PersistedState, error) { return persistedState, nil } -func (r *root) ChainID() string { - return r.chainID -} - func (r *root) GenesisHash() []byte { return r.genesisHash } @@ -251,6 +242,10 @@ func (r *root) GenesisDoc() genesis.GenesisDoc { return r.genesisDoc } +func (t *tip) ChainID() string { + return t.chainID +} + func (t *tip) LastBlockHeight() uint64 { return t.lastBlockHeight } diff --git a/cmd/burrow/main.go b/cmd/burrow/main.go index 5074510fd81418f55fc01b7ce01e5ce6b0359a4e..bacc1ccd4e9261fbaa0346cd1c0e06063a6171c3 100644 --- a/cmd/burrow/main.go +++ b/cmd/burrow/main.go @@ -229,7 +229,12 @@ func main() { } } - conf.GenesisDoc.ChainName = *chainNameOpt + if *chainNameOpt != "" { + if conf.GenesisDoc == nil { + fatalf("Unable to set ChainName since no GenesisDoc/GenesisSpec provided.") + } + conf.GenesisDoc.ChainName = *chainNameOpt + } if *jsonOutOpt { os.Stdout.WriteString(conf.JSONString()) diff --git a/config/config.go b/config/config.go index 160f240632db14485053c0f20a91a77f3678e97e..fa39dba4e8dffc5c6b6211fd7eb1c4c9d27377ae 100644 --- a/config/config.go +++ b/config/config.go @@ -61,15 +61,14 @@ func (conf *BurrowConfig) Kernel(ctx context.Context) (*core.Kernel, error) { 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) + return core.NewKernel(ctx, keyClient, 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 1bc0700c3cafbd9a214cbe42350c812fcdd66391..99320fb3b9b558d1d1a845bb507e37882b7eb93e 100644 --- a/consensus/tendermint/abci/app.go +++ b/consensus/tendermint/abci/app.go @@ -2,7 +2,6 @@ package abci import ( "fmt" - "sync" "time" bcm "github.com/hyperledger/burrow/blockchain" @@ -19,7 +18,6 @@ import ( const responseInfoName = "Burrow" type abciApp struct { - mtx sync.Mutex // State blockchain bcm.MutableBlockchain checker execution.BatchExecutor @@ -68,8 +66,6 @@ func (app *abciApp) Query(reqQuery abci_types.RequestQuery) (respQuery abci_type } func (app *abciApp) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { - app.mtx.Lock() - defer app.mtx.Unlock() tx, err := app.txDecoder.DecodeTx(txBytes) if err != nil { app.logger.TraceMsg("CheckTx decoding error", @@ -92,7 +88,7 @@ func (app *abciApp) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { "creates_contract", receipt.CreatesContract) return abci_types.ResponseCheckTx{ Code: codes.EncodingErrorCode, - Log: fmt.Sprintf("Could not execute transaction: %s, error: %v", tx, err), + Log: fmt.Sprintf("CheckTx could not execute transaction: %s, error: %v", tx, err), } } @@ -119,8 +115,6 @@ func (app *abciApp) BeginBlock(block abci_types.RequestBeginBlock) (respBeginBlo } func (app *abciApp) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { - app.mtx.Lock() - defer app.mtx.Unlock() tx, err := app.txDecoder.DecodeTx(txBytes) if err != nil { app.logger.TraceMsg("DeliverTx decoding error", @@ -142,7 +136,7 @@ func (app *abciApp) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { "creates_contract", receipt.CreatesContract) return abci_types.ResponseDeliverTx{ Code: codes.TxExecutionErrorCode, - Log: fmt.Sprintf("Could not execute transaction: %s, error: %s", tx, err), + Log: fmt.Sprintf("DeliverTx could not execute transaction: %s, error: %s", tx, err), } } @@ -164,8 +158,6 @@ func (app *abciApp) EndBlock(reqEndBlock abci_types.RequestEndBlock) (respEndBlo } func (app *abciApp) Commit() abci_types.ResponseCommit { - app.mtx.Lock() - defer app.mtx.Unlock() tip := app.blockchain.Tip() app.logger.InfoMsg("Committing block", "tag", "Commit", @@ -177,13 +169,12 @@ func (app *abciApp) Commit() abci_types.ResponseCommit { "last_block_time", tip.LastBlockTime(), "last_block_hash", tip.LastBlockHash()) - err := app.checker.Reset() - if err != nil { - return abci_types.ResponseCommit{ - Code: codes.CommitErrorCode, - Log: fmt.Sprintf("Could not reset check cache during commit: %s", err), - } - } + // Commit state before resetting check cache so that the emptied cache servicing some RPC requests will fall through + // to committed state when check state is reset + // TODO: determine why the ordering of updates does not experience invalid sequence number during recheck. It + // seems there is nothing to stop a Transact transaction from querying the checker cache before it has been replayed + // all transactions and so would formulate a transaction with the same sequence number as one in mempool. + // However this is not observed in v0_tests.go - we need to understand why or create a test that exposes this appHash, err := app.committer.Commit() if err != nil { return abci_types.ResponseCommit{ @@ -201,6 +192,14 @@ func (app *abciApp) Commit() abci_types.ResponseCommit { } } + err = app.checker.Reset() + if err != nil { + return abci_types.ResponseCommit{ + Code: codes.CommitErrorCode, + Log: fmt.Sprintf("Could not reset check cache during commit: %s", err), + } + } + // Perform a sanity check our block height if app.blockchain.LastBlockHeight() != uint64(app.block.Header.Height) { app.logger.InfoMsg("Burrow block height disagrees with Tendermint block height", @@ -214,6 +213,7 @@ func (app *abciApp) Commit() abci_types.ResponseCommit { app.blockchain.LastBlockHeight(), app.block.Header.Height), } } + return abci_types.ResponseCommit{ Code: codes.TxExecutionSuccessCode, Data: appHash, diff --git a/core/integration/test_wrapper.go b/core/integration/test_wrapper.go new file mode 100644 index 0000000000000000000000000000000000000000..20875d1723db28cfedbfde7d6d31c14b7420704b --- /dev/null +++ b/core/integration/test_wrapper.go @@ -0,0 +1,129 @@ +// +build integration + +// Space above here matters +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "context" + "fmt" + "os" + "strconv" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/consensus/tendermint/validator" + "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/keys/mock" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/config" + "github.com/hyperledger/burrow/logging/lifecycle" + "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/rpc" + tm_config "github.com/tendermint/tendermint/config" +) + +const ( + chainName = "Integration_Test_Chain" + testDir = "./test_scratch/tm_test" +) + +// Enable logger output during tests +var debugLogging = false + +// We use this to wrap tests +func TestWrapper(privateAccounts []acm.PrivateAccount, genesisDoc *genesis.GenesisDoc, runner func(*core.Kernel) int) int { + fmt.Println("Running with integration TestWrapper (core/integration/test_wrapper.go)...") + + os.RemoveAll(testDir) + os.MkdirAll(testDir, 0777) + os.Chdir(testDir) + + tmConf := tm_config.DefaultConfig() + logger := logging.NewNoopLogger() + if debugLogging { + var err error + // Change config as needed + logger, err = lifecycle.NewLoggerFromLoggingConfig(&config.LoggingConfig{ + ExcludeTrace: false, + RootSink: config.Sink(). + SetTransform(config.FilterTransform(config.IncludeWhenAnyMatches, + //"","", + "method", "GetAccount", + "method", "BroadcastTx", + "tag", "sequence", + "tag", "Commit", + "tag", "CheckTx", + "tag", "DeliverTx", + )). + //AddSinks(config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, "run_call", "false")). + AddSinks(config.Sink().SetTransform(config.PruneTransform("log_channel", "trace", "scope", "returns", "run_id", "args")). + AddSinks(config.Sink().SetTransform(config.SortTransform("tx_hash", "time", "message", "method")). + SetOutput(config.StdoutOutput()))), + }) + if err != nil { + panic(err) + } + } + + validatorAccount := privateAccounts[0] + privValidator := validator.NewPrivValidatorMemory(validatorAccount, validatorAccount) + keyClient := mock.NewMockKeyClient(privateAccounts...) + kernel, err := core.NewKernel(context.Background(), keyClient, privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), + nil, logger) + if err != nil { + panic(err) + } + // Sometimes better to not shutdown as logging errors on shutdown may obscure real issue + defer func() { + //kernel.Shutdown(context.Background()) + }() + + err = kernel.Boot() + if err != nil { + panic(err) + } + + return runner(kernel) +} + +func TestGenesisDoc(addressables []acm.PrivateAccount) *genesis.GenesisDoc { + accounts := make(map[string]acm.Account, len(addressables)) + for i, pa := range addressables { + account := acm.FromAddressable(pa) + account.AddToBalance(1 << 32) + account.SetPermissions(permission.AllAccountPermissions.Clone()) + accounts[fmt.Sprintf("user_%v", i)] = account + } + genesisTime, err := time.Parse("02-01-2006", "27-10-2017") + if err != nil { + panic("could not parse test genesis time") + } + return genesis.MakeGenesisDocFromAccounts(chainName, nil, genesisTime, accounts, + map[string]acm.Validator{ + "genesis_validator": acm.AsValidator(accounts["user_0"]), + }) +} + +// Deterministic account generation helper. Pass number of accounts to make +func MakePrivateAccounts(n int) []acm.PrivateAccount { + accounts := make([]acm.PrivateAccount, n) + for i := 0; i < n; i++ { + accounts[i] = acm.GeneratePrivateAccountFromSecret("mysecret" + strconv.Itoa(i)) + } + return accounts +} diff --git a/core/kernel.go b/core/kernel.go index c033098e393abbc52dc8254e7acb6a652863f2eb..1a1fed0c465be49b492a0984ac4fc84b6620ced1 100644 --- a/core/kernel.go +++ b/core/kernel.go @@ -32,6 +32,7 @@ import ( "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" "github.com/hyperledger/burrow/process" @@ -63,9 +64,9 @@ type Kernel struct { shutdownOnce sync.Once } -func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesisDoc *genesis.GenesisDoc, - tmConf *tm_config.Config, rpcConfig *rpc.RPCConfig, exeOptions []execution.ExecutionOption, - logger *logging.Logger) (*Kernel, error) { +func NewKernel(ctx context.Context, keyClient keys.KeyClient, privValidator tm_types.PrivValidator, + genesisDoc *genesis.GenesisDoc, tmConf *tm_config.Config, rpcConfig *rpc.RPCConfig, + exeOptions []execution.ExecutionOption, logger *logging.Logger) (*Kernel, error) { logger = logger.WithScope("NewKernel()").With(structure.TimeKey, kitlog.DefaultTimestampUTC) tmLogger := logger.With(structure.CallerKey, kitlog.Caller(LoggingCallerDepth+1)) @@ -100,10 +101,12 @@ func NewKernel(ctx context.Context, privValidator tm_types.PrivValidator, genesi return nil, err } txCodec := txs.NewGoWireCodec() - transactor := execution.NewTransactor(blockchain, state, emitter, tendermint.BroadcastTxAsyncFunc(tmNode, txCodec), + transactor := execution.NewTransactor(blockchain, emitter, tendermint.BroadcastTxAsyncFunc(tmNode, txCodec), logger) - service := rpc.NewService(ctx, state, state, emitter, blockchain, transactor, query.NewNodeView(tmNode, txCodec), logger) + nameReg := state + service := rpc.NewService(ctx, state, nameReg, checker, committer, emitter, blockchain, keyClient, transactor, + query.NewNodeView(tmNode, txCodec), logger) launchers := []process.Launcher{ { diff --git a/core/kernel_test.go b/core/kernel_test.go index aa1af7efc1aefc498cef965014a6d163d6e7f5ab..e1e4d65d17c0416ae3ea0a613e35632c059915e6 100644 --- a/core/kernel_test.go +++ b/core/kernel_test.go @@ -12,6 +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/keys/mock" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/rpc" "github.com/stretchr/testify/assert" @@ -64,7 +65,8 @@ func bootWaitBlocksShutdown(privValidator tm_types.PrivValidator, genesisDoc *ge 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(), nil, logger) + kern, err := NewKernel(context.Background(), mock.NewMockKeyClient(), privValidator, genesisDoc, tmConf, + rpc.DefaultRPCConfig(), nil, logger) if err != nil { return err } diff --git a/event/convention.go b/event/convention.go index 450ebf734fa0783d08d4b6608e4b57ea14244eb5..33a66a501f7e686898ecbaf86654ce8eb601ee52 100644 --- a/event/convention.go +++ b/event/convention.go @@ -11,6 +11,7 @@ const ( MessageTypeKey = "MessageType" TxTypeKey = "TxType" TxHashKey = "TxHash" + StackDepthKey = "StackDepth" ) // Get a query that matches events with a specific eventID diff --git a/execution/accounts.go b/execution/accounts.go new file mode 100644 index 0000000000000000000000000000000000000000..eaa01a188060befb672b6d8f542b27e59abd7f42 --- /dev/null +++ b/execution/accounts.go @@ -0,0 +1,95 @@ +package execution + +import ( + "fmt" + "sync" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/keys" + burrow_sync "github.com/hyperledger/burrow/sync" +) + +type Accounts struct { + burrow_sync.RingMutex + state.Reader + keyClient keys.KeyClient +} + +type SigningAccount struct { + acm.Account + acm.Signer +} + +type SequentialSigningAccount struct { + accountLocker sync.Locker + getter func() (*SigningAccount, error) +} + +func NewAccounts(reader state.Reader, keyClient keys.KeyClient, mutexCount int) *Accounts { + return &Accounts{ + // TODO: use the no hash variant of RingMutex after it has a test + RingMutex: *burrow_sync.NewRingMutexXXHash(mutexCount), + Reader: reader, + keyClient: keyClient, + } +} +func (accs *Accounts) SigningAccount(address acm.Address, signer acm.Signer) (*SigningAccount, error) { + account, err := state.GetMutableAccount(accs.Reader, address) + if err != nil { + return nil, err + } + // If the account is unknown to us return a zeroed account + if account == nil { + account = acm.ConcreteAccount{ + Address: address, + }.MutableAccount() + } + pubKey, err := accs.keyClient.PublicKey(address) + if err != nil { + return nil, err + } + account.SetPublicKey(pubKey) + return &SigningAccount{ + Account: account, + Signer: signer, + }, nil +} + +func (accs *Accounts) SequentialSigningAccount(address acm.Address) *SequentialSigningAccount { + signer := keys.Signer(accs.keyClient, address) + return &SequentialSigningAccount{ + accountLocker: accs.Mutex(address.Bytes()), + getter: func() (*SigningAccount, error) { + return accs.SigningAccount(address, signer) + }, + } +} + +func (accs *Accounts) SequentialSigningAccountFromPrivateKey(privateKeyBytes []byte) (*SequentialSigningAccount, error) { + if len(privateKeyBytes) != 64 { + return nil, fmt.Errorf("private key is not of the right length: %d\n", len(privateKeyBytes)) + } + privateAccount, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privateKeyBytes) + if err != nil { + return nil, err + } + return &SequentialSigningAccount{ + accountLocker: accs.Mutex(privateAccount.Address().Bytes()), + getter: func() (*SigningAccount, error) { + return accs.SigningAccount(privateAccount.Address(), privateAccount) + }, + }, nil +} + +type UnlockFunc func() + +func (ssa *SequentialSigningAccount) Lock() (*SigningAccount, UnlockFunc, error) { + ssa.accountLocker.Lock() + account, err := ssa.getter() + if err != nil { + ssa.accountLocker.Unlock() + return nil, nil, err + } + return account, ssa.accountLocker.Unlock, err +} diff --git a/execution/events/events.go b/execution/events/events.go index efbe1f244debb97a195c3d48b2cf06c9e2cb8e72..6f8c394fc20f095d3c8292552c00fb5011c02ac0 100644 --- a/execution/events/events.go +++ b/execution/events/events.go @@ -29,9 +29,13 @@ type EventDataTx struct { // For re-use var sendTxQuery = event.NewQueryBuilder(). - AndEquals(event.MessageTypeKey, reflect.TypeOf(EventDataTx{}).String()). + AndEquals(event.MessageTypeKey, reflect.TypeOf(&EventDataTx{}).String()). AndEquals(event.TxTypeKey, reflect.TypeOf(&txs.SendTx{}).String()) +var callTxQuery = event.NewQueryBuilder(). + AndEquals(event.MessageTypeKey, reflect.TypeOf(&EventDataTx{}).String()). + AndEquals(event.TxTypeKey, reflect.TypeOf(&txs.CallTx{}).String()) + type eventDataTx struct { Tx txs.Wrapper Return []byte @@ -60,7 +64,6 @@ func (edTx *EventDataTx) UnmarshalJSON(data []byte) error { } // Publish/Subscribe - func SubscribeAccountOutputSendTx(ctx context.Context, subscribable event.Subscribable, subscriber string, address acm.Address, txHash []byte, ch chan<- *txs.SendTx) error { diff --git a/execution/evm/abi/types.go b/execution/evm/abi/types.go index 09ac0e4c364b73a48514b1f5007fe3704b7e182d..0c7f300dddde3545cc47aa9378f5d9d2f61a6a6f 100644 --- a/execution/evm/abi/types.go +++ b/execution/evm/abi/types.go @@ -14,6 +14,8 @@ package abi +import "github.com/hyperledger/burrow/execution/evm/sha3" + // Ethereum defines types and calling conventions for the ABI // (application binary interface) here: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // We make a start of representing them here @@ -51,3 +53,13 @@ type ( Address [AddressLength]byte FunctionSelector [FunctionSelectorLength]byte ) + +func FunctionID(signature string) FunctionSelector { + return FirstFourBytes(sha3.Sha3([]byte(signature))) +} + +func FirstFourBytes(byteSlice []byte) [4]byte { + var bs [4]byte + copy(bs[:], byteSlice[:4]) + return bs +} diff --git a/execution/evm/events/events.go b/execution/evm/events/events.go index af90a399e8fcd203456684026df64eca7c825ef5..a9308bb5b075cfd6e6fe59a8de7d663ea86392e5 100644 --- a/execution/evm/events/events.go +++ b/execution/evm/events/events.go @@ -33,11 +33,12 @@ func EventStringLogEvent(addr acm.Address) string { return fmt.Sprintf("Log/% // EventDataCall fires when we call a contract, and when a contract calls another contract type EventDataCall struct { - CallData *CallData - Origin acm.Address - TxID []byte - Return []byte - Exception string + CallData *CallData + Origin acm.Address + TxHash []byte + StackDepth int + Return []byte + Exception string } type CallData struct { @@ -58,9 +59,11 @@ type EventDataLog struct { // Publish/Subscribe -// Subscribe to account call event - if TxHash is provided listens for a specifc Tx otherwise captures all +// Subscribe to account call event - if TxHash is provided listens for a specifc Tx otherwise captures all, if +// stackDepth is greater than or equal to 0 captures calls at a specific stack depth (useful for capturing the return +// of the root call over recursive calls func SubscribeAccountCall(ctx context.Context, subscribable event.Subscribable, subscriber string, address acm.Address, - txHash []byte, ch chan<- *EventDataCall) error { + txHash []byte, stackDepth int, ch chan<- *EventDataCall) error { query := event.QueryForEventID(EventStringAccountCall(address)) @@ -68,6 +71,10 @@ func SubscribeAccountCall(ctx context.Context, subscribable event.Subscribable, query = query.AndEquals(event.TxHashKey, hex.EncodeUpperToString(txHash)) } + if stackDepth >= 0 { + query = query.AndEquals(event.StackDepthKey, stackDepth) + } + return event.SubscribeCallback(ctx, subscribable, subscriber, query, func(message interface{}) bool { eventDataCall, ok := message.(*EventDataCall) if ok { @@ -93,7 +100,11 @@ func SubscribeLogEvent(ctx context.Context, subscribable event.Subscribable, sub func PublishAccountCall(publisher event.Publisher, address acm.Address, eventDataCall *EventDataCall) error { return event.PublishWithEventID(publisher, EventStringAccountCall(address), eventDataCall, - map[string]interface{}{"address": address, event.TxHashKey: hex.EncodeUpperToString(eventDataCall.TxID)}) + map[string]interface{}{ + "address": address, + event.TxHashKey: hex.EncodeUpperToString(eventDataCall.TxHash), + event.StackDepthKey: eventDataCall.StackDepth, + }) } func PublishLogEvent(publisher event.Publisher, address acm.Address, eventDataLog *EventDataLog) error { diff --git a/execution/evm/snative.go b/execution/evm/snative.go index 34375a0adf50b456ad74383a31f0936f0e1f223c..1bb3e89cf7ddd0fbafde63c0328cd29c9084c514 100644 --- a/execution/evm/snative.go +++ b/execution/evm/snative.go @@ -248,7 +248,7 @@ func (contract *SNativeContractDescription) Dispatch(state state.Writer, caller "identifier but arguments are only %v bytes long", len(args)) } - function, err := contract.FunctionByID(firstFourBytes(args)) + function, err := contract.FunctionByID(abi.FirstFourBytes(args)) if err != nil { return nil, err } @@ -325,7 +325,7 @@ func (function *SNativeFunctionDescription) Signature() string { // Get function calling identifier FunctionSelector func (function *SNativeFunctionDescription) ID() abi.FunctionSelector { - return firstFourBytes(sha3.Sha3([]byte(function.Signature()))) + return abi.FunctionID(function.Signature()) } // Get number of function arguments @@ -566,9 +566,3 @@ func byteFromBool(b bool) byte { } return 0x0 } - -func firstFourBytes(byteSlice []byte) [4]byte { - var bs [4]byte - copy(bs[:], byteSlice[:4]) - return bs -} diff --git a/execution/evm/snative_test.go b/execution/evm/snative_test.go index 39c31e7232972d7394da2af1c013cbe839c3ffd5..7c25aaf7910d884869ff6a43719a6f3120f9885c 100644 --- a/execution/evm/snative_test.go +++ b/execution/evm/snative_test.go @@ -123,7 +123,7 @@ func funcIDFromHex(t *testing.T, hexString string) abi.FunctionSelector { t.Fatalf("FunctionSelector must be 4 bytes but '%s' is %v bytes", hexString, len(bs)) } - return firstFourBytes(bs) + return abi.FirstFourBytes(bs) } func permFlagToWord256(permFlag ptypes.PermFlag) Word256 { diff --git a/execution/evm/vm.go b/execution/evm/vm.go index f94cf768ea702cde7fd208e7fb117cc96ab68d46..5597fe037f7425bde516b25dbd557eaed98f9dd0 100644 --- a/execution/evm/vm.go +++ b/execution/evm/vm.go @@ -159,11 +159,18 @@ func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, ca // fire the post call event (including exception if applicable) if vm.publisher != nil { events.PublishAccountCall(vm.publisher, calleeAddress, &events.EventDataCall{ - &events.CallData{Caller: callerAddress, Callee: calleeAddress, Data: input, Value: value, Gas: *gas}, - vm.origin, - vm.txHash, - *output, - *exception, + CallData: &events.CallData{ + Caller: callerAddress, + Callee: calleeAddress, + Data: input, + Value: value, + Gas: *gas, + }, + Origin: vm.origin, + TxHash: vm.txHash, + StackDepth: vm.stackDepth, + Return: *output, + Exception: *exception, }) } } diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go index 82d79012b6127a72fe7f4ae112613041145c8bcf..27ce077083f3aea74d99c513e551f78e101799f5 100644 --- a/execution/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -442,7 +442,8 @@ func runVM(eventCh chan<- *evm_events.EventDataCall, ourVm *VM, caller, callee a emitter := event.NewEmitter(logging.NewNoopLogger()) fmt.Printf("subscribe to %s\n", subscribeAddr) - err := evm_events.SubscribeAccountCall(context.Background(), emitter, "test", subscribeAddr, nil, eventCh) + err := evm_events.SubscribeAccountCall(context.Background(), emitter, "test", subscribeAddr, + nil, -1, eventCh) if err != nil { return nil, err } diff --git a/execution/execution.go b/execution/execution.go index 58277636ad5dab3f1242bb5444ea1be3d0a93143..82db5e94cb4f3ebaeed8b0a306f1b3ac990eed8c 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -53,7 +53,7 @@ type BatchCommitter interface { } type executor struct { - sync.Mutex + sync.RWMutex chainID string tip bcm.Tip runCall bool @@ -69,42 +69,32 @@ type executor struct { var _ BatchExecutor = (*executor)(nil) // Wraps a cache of what is variously known as the 'check cache' and 'mempool' -func NewBatchChecker(state *State, - chainID string, - tip bcm.Tip, - logger *logging.Logger, +func NewBatchChecker(backend *State, chainID string, tip bcm.Tip, logger *logging.Logger, options ...ExecutionOption) BatchExecutor { - return newExecutor(false, state, chainID, tip, event.NewNoOpPublisher(), + + return newExecutor("CheckCache", false, backend, chainID, tip, event.NewNoOpPublisher(), logger.WithScope("NewBatchExecutor"), options...) } -func NewBatchCommitter(state *State, - chainID string, - tip bcm.Tip, - publisher event.Publisher, - logger *logging.Logger, +func NewBatchCommitter(backend *State, chainID string, tip bcm.Tip, publisher event.Publisher, logger *logging.Logger, options ...ExecutionOption) BatchCommitter { - return newExecutor(true, state, chainID, tip, publisher, + return newExecutor("CommitCache", true, backend, chainID, tip, publisher, logger.WithScope("NewBatchCommitter"), options...) } -func newExecutor(runCall bool, - backend *State, - chainID string, - tip bcm.Tip, - eventFireable event.Publisher, - logger *logging.Logger, - options ...ExecutionOption) *executor { +func newExecutor(name string, runCall bool, backend *State, chainID string, tip bcm.Tip, publisher event.Publisher, + logger *logging.Logger, options ...ExecutionOption) *executor { + exe := &executor{ chainID: chainID, tip: tip, runCall: runCall, state: backend, - stateCache: state.NewCache(backend), + stateCache: state.NewCache(backend, state.Name(name)), nameRegCache: NewNameRegCache(backend), - publisher: eventFireable, - eventCache: event.NewEventCache(eventFireable), + publisher: publisher, + eventCache: event.NewEventCache(publisher), logger: logger.With(structure.ComponentKey, "Executor"), } for _, option := range options { @@ -115,11 +105,15 @@ func newExecutor(runCall bool, // Accounts func (exe *executor) GetAccount(address acm.Address) (acm.Account, error) { + exe.RLock() + defer exe.RUnlock() return exe.stateCache.GetAccount(address) } // Storage func (exe *executor) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + exe.RLock() + defer exe.RUnlock() return exe.stateCache.GetStorage(address, key) } @@ -128,7 +122,7 @@ func (exe *executor) Commit() (hash []byte, err error) { defer exe.Unlock() defer func() { if r := recover(); r != nil { - err = fmt.Errorf("recovered from panic in executor.Commit(): %v", r) + err = fmt.Errorf("recovered from panic in executor.Commit(): %v\n%v", r, debug.Stack()) } }() // flush the caches @@ -151,6 +145,8 @@ func (exe *executor) Commit() (hash []byte, err error) { } func (exe *executor) Reset() error { + exe.Lock() + defer exe.Unlock() exe.stateCache.Reset(exe.state) exe.nameRegCache.Reset(exe.state) return nil @@ -224,7 +220,6 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { exe.stateCache.UpdateAccount(acc) } - // if the exe.eventCache is nil, nothing will happen if exe.eventCache != nil { for _, i := range tx.Inputs { events.PublishAccountInput(exe.eventCache, i.Address, txHash, tx, nil, "") @@ -251,6 +246,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { return txs.ErrTxInvalidAddress } + // Calling a nil destination is defined as requesting contract creation createContract := tx.Address == nil if createContract { if !hasCreateContractPermission(exe.stateCache, inAcc, logger) { @@ -328,7 +324,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { callee acm.MutableAccount = nil // initialized below code []byte = nil ret []byte = nil - txCache = state.NewCache(exe.stateCache) + txCache = state.NewCache(exe.stateCache, state.Name("TxCache")) params = evm.Params{ BlockHeight: exe.tip.LastBlockHeight(), BlockHash: binary.LeftPadWord256(exe.tip.LastBlockHash()), diff --git a/execution/execution_test.go b/execution/execution_test.go index 70c9fe37010d22db449edd90e59ba4d668311f12..50ce476712b1182f84a7dc725369223efe6b0412 100644 --- a/execution/execution_test.go +++ b/execution/execution_test.go @@ -119,8 +119,8 @@ var testGenesisDoc, testPrivAccounts, _ = deterministicGenesis. GenesisDoc(3, true, 1000, 1, true, 1000) var testChainID = testGenesisDoc.ChainID() -func makeUsers(n int) []acm.PrivateAccount { - users := make([]acm.PrivateAccount, n) +func makeUsers(n int) []acm.AddressableSigner { + users := make([]acm.AddressableSigner, n) for i := 0; i < n; i++ { secret := "mysecret" + strconv.Itoa(i) users[i] = acm.GeneratePrivateAccountFromSecret(secret) @@ -129,12 +129,12 @@ func makeUsers(n int) []acm.PrivateAccount { } func makeExecutor(state *State) *executor { - return newExecutor(true, state, testChainID, bcm.NewBlockchain(nil, testGenesisDoc), event.NewEmitter(logger), - logger) + return newExecutor("makeExecutorCache", true, state, testChainID, + bcm.NewBlockchain(nil, testGenesisDoc), event.NewEmitter(logger), logger) } func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.GenesisDoc { - genAccounts := []genesis.Account{} + var genAccounts []genesis.Account for _, user := range users[:5] { accountPermCopy := accountPerm // Create new instance for custom overridability. genAccounts = append(genAccounts, genesis.Account{ @@ -192,7 +192,7 @@ func TestSendFails(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[1].Address(), 5) - tx.SignInput(testChainID, 0, users[0]) + require.NoError(t, tx.Sign(testChainID, users[0])) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -205,7 +205,7 @@ func TestSendFails(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[4].Address(), 5) - tx.SignInput(testChainID, 0, users[2]) + require.NoError(t, tx.Sign(testChainID, users[2])) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -218,7 +218,7 @@ func TestSendFails(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[4].Address(), 5) - tx.SignInput(testChainID, 0, users[3]) + require.NoError(t, tx.Sign(testChainID, users[3])) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -234,7 +234,7 @@ func TestSendFails(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[6].Address(), 5) - tx.SignInput(testChainID, 0, users[3]) + require.NoError(t, tx.Sign(testChainID, users[3])) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -366,7 +366,7 @@ func TestSendPermission(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[1].Address(), 5) - tx.SignInput(testChainID, 0, users[0]) + require.NoError(t, tx.Sign(testChainID, users[0])) if err := batchCommitter.Execute(tx); err != nil { t.Fatal("Transaction failed", err) } @@ -380,8 +380,7 @@ func TestSendPermission(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[2].Address(), 10) - tx.SignInput(testChainID, 0, users[0]) - tx.SignInput(testChainID, 1, users[1]) + require.NoError(t, tx.Sign(testChainID, users[:2]...)) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -651,7 +650,7 @@ func TestCreateAccountPermission(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[6].Address(), 5) - tx.SignInput(testChainID, 0, users[0]) + require.NoError(t, tx.Sign(testChainID, users[0])) if err := batchCommitter.Execute(tx); err != nil { t.Fatal("Transaction failed", err) } @@ -665,8 +664,7 @@ func TestCreateAccountPermission(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[7].Address(), 10) - tx.SignInput(testChainID, 0, users[0]) - tx.SignInput(testChainID, 1, users[1]) + require.NoError(t, tx.Sign(testChainID, users[:2]...)) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -683,8 +681,7 @@ func TestCreateAccountPermission(t *testing.T) { } tx.AddOutput(users[7].Address(), 4) tx.AddOutput(users[4].Address(), 6) - tx.SignInput(testChainID, 0, users[0]) - tx.SignInput(testChainID, 1, users[1]) + require.NoError(t, tx.Sign(testChainID, users[:2]...)) if err := batchCommitter.Execute(tx); err == nil { t.Fatal("Expected error") } else { @@ -703,8 +700,7 @@ func TestCreateAccountPermission(t *testing.T) { t.Fatal(err) } tx.AddOutput(users[7].Address(), 10) - tx.SignInput(testChainID, 0, users[0]) - tx.SignInput(testChainID, 1, users[1]) + require.NoError(t, tx.Sign(testChainID, users[:2]...)) if err := batchCommitter.Execute(tx); err != nil { t.Fatal("Unexpected error", err) } @@ -719,8 +715,7 @@ func TestCreateAccountPermission(t *testing.T) { } tx.AddOutput(users[7].Address(), 7) tx.AddOutput(users[4].Address(), 3) - tx.SignInput(testChainID, 0, users[0]) - tx.SignInput(testChainID, 1, users[1]) + require.NoError(t, tx.Sign(testChainID, users[:2]...)) if err := batchCommitter.Execute(tx); err != nil { t.Fatal("Unexpected error", err) } @@ -1002,7 +997,7 @@ func TestTxSequence(t *testing.T) { tx := txs.NewSendTx() tx.AddInputWithSequence(acc0PubKey, 1, sequence) tx.AddOutput(acc1.Address(), 1) - tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) stateCopy := state.Copy(dbm.NewMemDB()) err := execTxWithState(stateCopy, tx) if i == 1 { @@ -1216,19 +1211,19 @@ func TestNameTxs(t *testing.T) { // Test creating a contract from futher down the call stack /* contract Factory { - address a; - function create() returns (address){ - a = new PreFactory(); - return a; - } + address a; + function create() returns (address){ + a = new PreFactory(); + return a; + } } contract PreFactory{ - address a; - function create(Factory c) returns (address) { - a = c.create(); - return a; - } + address a; + function create(Factory c) returns (address) { + a = c.create(); + return a; + } } */ @@ -1270,7 +1265,7 @@ func TestCreates(t *testing.T) { Data: createData, } - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -1294,7 +1289,7 @@ func TestCreates(t *testing.T) { Data: createData, } - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err = execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -1311,9 +1306,9 @@ func TestCreates(t *testing.T) { /* contract Caller { - function send(address x){ - x.send(msg.value); - } + function send(address x){ + x.send(msg.value); + } } */ var callerCode, _ = hex.DecodeString("60606040526000357c0100000000000000000000000000000000000000000000000000000000900480633e58c58c146037576035565b005b604b6004808035906020019091905050604d565b005b8073ffffffffffffffffffffffffffffffffffffffff16600034604051809050600060405180830381858888f19350505050505b5056") @@ -1349,7 +1344,7 @@ func TestContractSend(t *testing.T) { Data: sendData, } - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -1391,7 +1386,7 @@ func TestMerklePanic(t *testing.T) { }, } - tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(stateSendTx, tx) if err != nil { t.Errorf("Got error in executing send transaction, %v", err) @@ -1417,7 +1412,7 @@ func TestMerklePanic(t *testing.T) { GasLimit: 10, } - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(stateCallTx, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -1458,7 +1453,7 @@ func TestTxs(t *testing.T) { }, } - tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(stateSendTx, tx) if err != nil { t.Errorf("Got error in executing send transaction, %v", err) @@ -1492,7 +1487,7 @@ func TestTxs(t *testing.T) { GasLimit: 10, } - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(stateCallTx, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -1543,7 +1538,7 @@ proof-of-work chain as proof of what happened while they were gone ` Data: entryData, } - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err := execTxWithState(stateNameTx, tx) if err != nil { @@ -1566,7 +1561,7 @@ proof-of-work chain as proof of what happened while they were gone ` // test a bad string tx.Data = string([]byte{0, 1, 2, 3, 127, 128, 129, 200, 251}) tx.Input.Sequence += 1 - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) err = execTxWithState(stateNameTx, tx) if _, ok := err.(txs.ErrTxInvalidString); !ok { t.Errorf("Expected invalid string error. Got: %s", err.Error()) @@ -1648,7 +1643,7 @@ func TestSelfDestruct(t *testing.T) { // send call tx with no data, cause self-destruct tx := txs.NewCallTxWithSequence(acc0PubKey, addressPtr(acc1), nil, sendingAmount, 1000, 0, acc0.Sequence()+1) - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) // we use cache instead of execTxWithState so we can run the tx twice exe := NewBatchCommitter(state, testChainID, bcm.NewBlockchain(nil, testGenesisDoc), event.NewNoOpPublisher(), logger) @@ -1659,7 +1654,7 @@ func TestSelfDestruct(t *testing.T) { // if we do it again, we won't get an error, but the self-destruct // shouldn't happen twice and the caller should lose fee tx.Input.Sequence += 1 - tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + require.NoError(t, tx.Sign(testChainID, privAccounts[0])) if err := exe.Execute(tx); err != nil { t.Errorf("Got error in executing call transaction, %v", err) } @@ -1681,7 +1676,8 @@ func TestSelfDestruct(t *testing.T) { } func execTxWithStateAndBlockchain(state *State, tip bcm.Tip, tx txs.Tx) error { - exe := newExecutor(true, state, testChainID, tip, event.NewNoOpPublisher(), logger) + exe := newExecutor("execTxWithStateAndBlockchainCache", true, state, testChainID, tip, + event.NewNoOpPublisher(), logger) if err := exe.Execute(tx); err != nil { return err } else { @@ -1708,7 +1704,7 @@ func execTxWithStateNewBlock(state *State, blockchain bcm.MutableBlockchain, tx } func makeGenesisState(numAccounts int, randBalance bool, minBalance uint64, numValidators int, randBonded bool, - minBonded int64) (*State, []acm.PrivateAccount) { + minBonded int64) (*State, []acm.AddressableSigner) { testGenesisDoc, privAccounts, _ := deterministicGenesis.GenesisDoc(numAccounts, randBalance, minBalance, numValidators, randBonded, minBonded) s0, err := MakeGenesisState(dbm.NewMemDB(), testGenesisDoc) @@ -1865,7 +1861,7 @@ func permNameToFuncID(name string) []byte { return id[:] } -func snativePermTestInputCALL(name string, user acm.PrivateAccount, perm ptypes.PermFlag, +func snativePermTestInputCALL(name string, user acm.AddressableSigner, perm ptypes.PermFlag, val bool) (addr acm.Address, pF ptypes.PermFlag, data []byte) { addr = permissionsContract.Address() switch name { @@ -1888,7 +1884,7 @@ func snativePermTestInputCALL(name string, user acm.PrivateAccount, perm ptypes. return } -func snativePermTestInputTx(name string, user acm.PrivateAccount, perm ptypes.PermFlag, +func snativePermTestInputTx(name string, user acm.AddressableSigner, perm ptypes.PermFlag, val bool) (snativeArgs snatives.PermArgs) { switch name { @@ -1904,7 +1900,7 @@ func snativePermTestInputTx(name string, user acm.PrivateAccount, perm ptypes.Pe return } -func snativeRoleTestInputCALL(name string, user acm.PrivateAccount, +func snativeRoleTestInputCALL(name string, user acm.AddressableSigner, role string) (addr acm.Address, pF ptypes.PermFlag, data []byte) { addr = permissionsContract.Address() data = user.Address().Word256().Bytes() @@ -1918,7 +1914,7 @@ func snativeRoleTestInputCALL(name string, user acm.PrivateAccount, return } -func snativeRoleTestInputTx(name string, user acm.PrivateAccount, role string) (snativeArgs snatives.PermArgs) { +func snativeRoleTestInputTx(name string, user acm.AddressableSigner, role string) (snativeArgs snatives.PermArgs) { switch name { case "hasRole": snativeArgs = snatives.HasRoleArgs(user.Address(), role) diff --git a/execution/state.go b/execution/state.go index 73fb51df903ab78f9dfaa3cf77375a270c317cdd..7b98a32ab84e92e9c239b8cab17ba04afc791ec7 100644 --- a/execution/state.go +++ b/execution/state.go @@ -185,6 +185,9 @@ func (s *State) GetAccount(address acm.Address) (acm.Account, error) { func (s *State) UpdateAccount(account acm.Account) error { s.Lock() defer s.Unlock() + if account == nil { + return fmt.Errorf("UpdateAccount passed nil account in execution.State") + } // TODO: find a way to implement something equivalent to this so we can set the account StorageRoot //storageRoot := s.tree.SubTreeHash(prefixedKey(storagePrefix, account.Address().Bytes())) // Alternatively just abandon and diff --git a/execution/transactor.go b/execution/transactor.go index 9050949dc451ab537b6ee653e76f80636e659eea..9ffc547760af366f9ae4222592ac5af44c226ed8 100644 --- a/execution/transactor.go +++ b/execution/transactor.go @@ -17,10 +17,8 @@ package execution import ( "context" "fmt" - "sync" - "time" - "runtime/debug" + "time" acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" @@ -45,38 +43,20 @@ type Call struct { GasUsed uint64 } -type Transactor interface { - Call(fromAddress, toAddress acm.Address, data []byte) (*Call, error) - CallCode(fromAddress acm.Address, code, data []byte) (*Call, error) - BroadcastTx(tx txs.Tx) (*txs.Receipt, error) - BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error - Transact(privKey []byte, address *acm.Address, data []byte, gasLimit, fee uint64) (*txs.Receipt, error) - TransactAndHold(privKey []byte, address *acm.Address, data []byte, gasLimit, fee uint64) (*evm_events.EventDataCall, error) - Send(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) - SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) - TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) - SignTx(tx txs.Tx, privAccounts []acm.PrivateAccount) (txs.Tx, error) -} - // Transactor is the controller/middleware for the v0 RPC -type transactor struct { - txMtx sync.Mutex - blockchain blockchain.Blockchain - state state.Iterable +type Transactor struct { + tip blockchain.Tip eventEmitter event.Emitter broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error logger *logging.Logger } -var _ Transactor = &transactor{} - -func NewTransactor(blockchain blockchain.Blockchain, state state.Iterable, eventEmitter event.Emitter, +func NewTransactor(tip blockchain.Tip, eventEmitter event.Emitter, broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error, - logger *logging.Logger) *transactor { + logger *logging.Logger) *Transactor { - return &transactor{ - blockchain: blockchain, - state: state, + return &Transactor{ + tip: tip, eventEmitter: eventEmitter, broadcastTxAsync: broadcastTxAsync, logger: logger.With(structure.ComponentKey, "Transactor"), @@ -85,14 +65,16 @@ func NewTransactor(blockchain blockchain.Blockchain, state state.Iterable, event // 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 *Call, err error) { +func (trans *Transactor) Call(reader state.Reader, 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 := state.GetMutableAccount(trans.state, toAddress) + callee, err := state.GetMutableAccount(reader, toAddress) if err != nil { return nil, err } @@ -100,8 +82,8 @@ 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 := state.NewCache(trans.state) - params := vmParams(trans.blockchain) + txCache := state.NewCache(reader) + params := vmParams(trans.tip) vmach := evm.NewVM(txCache, params, caller.Address(), nil, trans.logger.WithScope("Call")) vmach.SetPublisher(trans.eventEmitter) @@ -122,12 +104,12 @@ func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) ( // Run the given code on an isolated and unpersisted state // Cannot be used to create new contracts. -func (trans *transactor) CallCode(fromAddress acm.Address, code, data []byte) (*Call, error) { +func (trans *Transactor) CallCode(reader state.Reader, fromAddress acm.Address, code, data []byte) (*Call, error) { // This was being run against CheckTx cache, need to understand the reasoning callee := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() - txCache := state.NewCache(trans.state) - params := vmParams(trans.blockchain) + txCache := state.NewCache(reader) + params := vmParams(trans.tip) vmach := evm.NewVM(txCache, params, caller.Address(), nil, trans.logger.WithScope("CallCode")) gas := params.GasLimit @@ -139,14 +121,15 @@ func (trans *transactor) CallCode(fromAddress acm.Address, code, data []byte) (* return &Call{Return: ret, GasUsed: gasUsed}, nil } -func (trans *transactor) BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error { +func (trans *Transactor) BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error { return trans.broadcastTxAsync(tx, callback) } -// Broadcast a transaction. -func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { +// Broadcast a transaction and waits for a response from the mempool. Transactions to BroadcastTx will block during +// various mempool operations (managed by Tendermint) including mempool Reap, Commit, and recheckTx. +func (trans *Transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { trans.logger.Trace.Log("method", "BroadcastTx", - "tx_hash", tx.Hash(trans.blockchain.ChainID()), + "tx_hash", tx.Hash(trans.tip.ChainID()), "tx", tx.String()) responseCh := make(chan *abci_types.Response, 1) err := trans.BroadcastTxAsync(tx, func(res *abci_types.Response) { @@ -177,43 +160,24 @@ func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { } // Orders calls to BroadcastTx using lock (waits for response from core before releasing) -func (trans *transactor) Transact(privKey []byte, address *acm.Address, data []byte, gasLimit, - fee uint64) (*txs.Receipt, error) { - - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) - } - trans.txMtx.Lock() - defer trans.txMtx.Unlock() - pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) - if err != nil { - return nil, err - } - // [Silas] This is puzzling, if the account doesn't exist the CallTx will fail, so what's the point in this? - acc, err := trans.state.GetAccount(pa.Address()) +func (trans *Transactor) Transact(sequentialSigningAccount *SequentialSigningAccount, address *acm.Address, data []byte, + gasLimit, fee uint64) (*txs.Receipt, error) { + // Note we rely on back pressure from BroadcastTx when serialising access to from a particular input account while + // the mempool rechecks (and so the checker, having just be reset does not yet have the latest sequence numbers + // for some accounts). BroadcastTx will block when the mempool is rechecking or during a commit so provided + // subsequent Transacts from the same input account block on those ahead of it we are able to stream transactions + // continuously with sequential sequence numbers. By taking this lock we ensure this. + inputAccount, unlock, err := sequentialSigningAccount.Lock() if err != nil { return nil, err } - sequence := uint64(1) - if acc != nil { - sequence = acc.Sequence() + uint64(1) - } - // TODO: [Silas] we should consider revising this method and removing fee, or - // possibly adding an amount parameter. It is non-sensical to just be able to - // set the fee. Our support of fees in general is questionable since at the - // moment all we do is deduct the fee effectively leaking token. It is possible - // someone may be using the sending of native token to payable functions but - // they can be served by broadcasting a token. - - // We hard-code the amount to be equal to the fee which means the CallTx we - // generate transfers 0 value, which is the most sensible default since in - // recent solidity compilers the EVM generated will throw an error if value - // is transferred to a non-payable function. + defer unlock() + txInput := &txs.TxInput{ - Address: pa.Address(), + Address: inputAccount.Address(), Amount: fee, - Sequence: sequence, - PublicKey: pa.PublicKey(), + Sequence: inputAccount.Sequence() + 1, + PublicKey: inputAccount.PublicKey(), } tx := &txs.CallTx{ Input: txInput, @@ -224,17 +188,17 @@ func (trans *transactor) Transact(privKey []byte, address *acm.Address, data []b } // Got ourselves a tx. - txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) - if errS != nil { - return nil, errS + err = tx.Sign(trans.tip.ChainID(), inputAccount) + if err != nil { + return nil, err } - return trans.BroadcastTx(txS) + return trans.BroadcastTx(tx) } -func (trans *transactor) TransactAndHold(privKey []byte, address *acm.Address, data []byte, gasLimit, +func (trans *Transactor) TransactAndHold(sequentialSigningAccount *SequentialSigningAccount, address *acm.Address, data []byte, gasLimit, fee uint64) (*evm_events.EventDataCall, error) { - receipt, err := trans.Transact(privKey, address, data, gasLimit, fee) + receipt, err := trans.Transact(sequentialSigningAccount, address, data, gasLimit, fee) if err != nil { return nil, err } @@ -249,7 +213,7 @@ func (trans *transactor) TransactAndHold(privKey []byte, address *acm.Address, d } err = evm_events.SubscribeAccountCall(context.Background(), trans.eventEmitter, subID, receipt.ContractAddress, - receipt.TxHash, wc) + receipt.TxHash, 0, wc) if err != nil { return nil, err } @@ -271,55 +235,33 @@ func (trans *transactor) TransactAndHold(privKey []byte, address *acm.Address, d } } -func (trans *transactor) Send(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", - len(privKey)) - } +func (trans *Transactor) Send(sequentialSigningAccount *SequentialSigningAccount, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { + tx := txs.NewSendTx() - pk := &[64]byte{} - copy(pk[:], privKey) - trans.txMtx.Lock() - defer trans.txMtx.Unlock() - pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) - if err != nil { - return nil, err - } - cache := trans.state - acc, err := cache.GetAccount(pa.Address()) + inputAccount, unlock, err := sequentialSigningAccount.Lock() if err != nil { return nil, err } - sequence := uint64(1) - if acc != nil { - sequence = acc.Sequence() + uint64(1) - } - - tx := txs.NewSendTx() - + defer unlock() txInput := &txs.TxInput{ - Address: pa.Address(), + Address: inputAccount.Address(), Amount: amount, - Sequence: sequence, - PublicKey: pa.PublicKey(), + Sequence: inputAccount.Sequence() + 1, + PublicKey: inputAccount.PublicKey(), } - tx.Inputs = append(tx.Inputs, txInput) - txOutput := &txs.TxOutput{Address: toAddress, Amount: amount} - tx.Outputs = append(tx.Outputs, txOutput) - // Got ourselves a tx. - txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) - if errS != nil { - return nil, errS + err = tx.Sign(trans.tip.ChainID(), inputAccount) + if err != nil { + return nil, err } - return trans.BroadcastTx(txS) + return trans.BroadcastTx(tx) } -func (trans *transactor) SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { - receipt, err := trans.Send(privKey, toAddress, amount) +func (trans *Transactor) SendAndHold(sequentialSigningAccount *SequentialSigningAccount, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { + receipt, err := trans.Send(sequentialSigningAccount, toAddress, amount) if err != nil { return nil, err } @@ -341,17 +283,12 @@ func (trans *transactor) SendAndHold(privKey []byte, toAddress acm.Address, amou timer := time.NewTimer(BlockingTimeoutSeconds * time.Second) defer timer.Stop() - pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) - if err != nil { - return nil, err - } - select { case <-timer.C: return nil, fmt.Errorf("transaction timed out TxHash: %X", receipt.TxHash) case sendTx := <-wc: // This is a double check - we subscribed to this tx's hash so something has gone wrong if the amounts don't match - if sendTx.Inputs[0].Address == pa.Address() && sendTx.Inputs[0].Amount == amount { + if sendTx.Inputs[0].Amount == amount { return receipt, nil } return nil, fmt.Errorf("received SendTx but hash doesn't seem to match what we subscribed to, "+ @@ -359,83 +296,34 @@ func (trans *transactor) SendAndHold(privKey []byte, toAddress acm.Address, amou } } -func (trans *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) { +func (trans *Transactor) TransactNameReg(sequentialSigningAccount *SequentialSigningAccount, name, data string, amount, + fee uint64) (*txs.Receipt, error) { - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) - } - trans.txMtx.Lock() - defer trans.txMtx.Unlock() - pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + inputAccount, unlock, err := sequentialSigningAccount.Lock() if err != nil { return nil, err } - cache := trans.state // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - acc, err := cache.GetAccount(pa.Address()) + defer unlock() + // Formulate and sign + tx := txs.NewNameTxWithSequence(inputAccount.PublicKey(), name, data, amount, fee, inputAccount.Sequence()+1) + err = tx.Sign(trans.tip.ChainID(), inputAccount) if err != nil { return nil, err } - sequence := uint64(1) - if acc == nil { - sequence = acc.Sequence() + uint64(1) - } - tx := txs.NewNameTxWithSequence(pa.PublicKey(), name, data, amount, fee, sequence) - // Got ourselves a tx. - txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) - if errS != nil { - return nil, errS - } - return trans.BroadcastTx(txS) + return trans.BroadcastTx(tx) } // Sign a transaction -func (trans *transactor) SignTx(tx txs.Tx, privAccounts []acm.PrivateAccount) (txs.Tx, error) { +func (trans *Transactor) SignTx(tx txs.Tx, signingAccounts []acm.AddressableSigner) (txs.Tx, error) { // more checks? - - for i, privAccount := range privAccounts { - if privAccount == nil || privAccount.PrivateKey().Unwrap() == nil { - return nil, fmt.Errorf("invalid (empty) privAccount @%v", i) - } - } - chainID := trans.blockchain.ChainID() - switch tx.(type) { - case *txs.NameTx: - nameTx := tx.(*txs.NameTx) - nameTx.Input.PublicKey = privAccounts[0].PublicKey() - nameTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, nameTx) - case *txs.SendTx: - sendTx := tx.(*txs.SendTx) - for i, input := range sendTx.Inputs { - input.PublicKey = privAccounts[i].PublicKey() - input.Signature = acm.ChainSign(privAccounts[i], chainID, sendTx) - } - case *txs.CallTx: - callTx := tx.(*txs.CallTx) - callTx.Input.PublicKey = privAccounts[0].PublicKey() - callTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, callTx) - case *txs.BondTx: - bondTx := tx.(*txs.BondTx) - // the first privaccount corresponds to the BondTx pub key. - // the rest to the inputs - bondTx.Signature = acm.ChainSign(privAccounts[0], chainID, bondTx) - for i, input := range bondTx.Inputs { - input.PublicKey = privAccounts[i+1].PublicKey() - input.Signature = acm.ChainSign(privAccounts[i+1], chainID, bondTx) - } - case *txs.UnbondTx: - unbondTx := tx.(*txs.UnbondTx) - unbondTx.Signature = acm.ChainSign(privAccounts[0], chainID, unbondTx) - case *txs.RebondTx: - rebondTx := tx.(*txs.RebondTx) - rebondTx.Signature = acm.ChainSign(privAccounts[0], chainID, rebondTx) - default: - return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) + err := tx.Sign(trans.tip.ChainID(), signingAccounts...) + if err != nil { + return nil, err } return tx, nil } -func vmParams(blockchain blockchain.Blockchain) evm.Params { - tip := blockchain.Tip() +func vmParams(tip blockchain.Tip) evm.Params { return evm.Params{ BlockHeight: tip.LastBlockHeight(), BlockHash: binary.LeftPadWord256(tip.LastBlockHash()), diff --git a/execution/transactor_test.go b/execution/transactor_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5f0689a513feb04731cc9170b01ebf6a928b7c66 --- /dev/null +++ b/execution/transactor_test.go @@ -0,0 +1,46 @@ +package execution + +import ( + "testing" + "time" + + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/txs" + "github.com/tendermint/abci/types" +) + +func TestTransactor_TransactAndHold(t *testing.T) { +} + +type testTransactor struct { + ResponseCh chan<- *types.Response + state.IterableWriter + event.Emitter + *Transactor +} + +func newTestTransactor(txProcessor func(tx txs.Tx) (*types.Response, error)) testTransactor { + st := state.NewMemoryState() + emitter := event.NewEmitter(logger) + trans := NewTransactor(blockchain.NewTip(testChainID, time.Time{}, nil), + emitter, func(tx txs.Tx, callback func(res *types.Response)) error { + res, err := txProcessor(tx) + if err != nil { + return err + } + callback(res) + return nil + }, logger) + + return testTransactor{ + IterableWriter: st, + Emitter: emitter, + Transactor: trans, + } +} + +func TestTransactor_Transact(t *testing.T) { + //trans := newTestTransactor() +} diff --git a/genesis/deterministic_genesis.go b/genesis/deterministic_genesis.go index 402a588bdc2afb617404b82469b46c159f3d820a..de80e258883a9598a7ab32ce5310952d6d9ad95b 100644 --- a/genesis/deterministic_genesis.go +++ b/genesis/deterministic_genesis.go @@ -21,10 +21,10 @@ func NewDeterministicGenesis(seed int64) *deterministicGenesis { } func (dg *deterministicGenesis) GenesisDoc(numAccounts int, randBalance bool, minBalance uint64, numValidators int, - randBonded bool, minBonded int64) (*GenesisDoc, []acm.PrivateAccount, []acm.PrivateAccount) { + randBonded bool, minBonded int64) (*GenesisDoc, []acm.AddressableSigner, []acm.AddressableSigner) { accounts := make([]Account, numAccounts) - privAccounts := make([]acm.PrivateAccount, numAccounts) + privAccounts := make([]acm.AddressableSigner, numAccounts) defaultPerms := permission.DefaultAccountPermissions for i := 0; i < numAccounts; i++ { account, privAccount := dg.Account(randBalance, minBalance) @@ -38,7 +38,7 @@ func (dg *deterministicGenesis) GenesisDoc(numAccounts int, randBalance bool, mi privAccounts[i] = privAccount } validators := make([]Validator, numValidators) - privValidators := make([]acm.PrivateAccount, numValidators) + privValidators := make([]acm.AddressableSigner, numValidators) for i := 0; i < numValidators; i++ { validator := acm.GeneratePrivateAccountFromSecret(fmt.Sprintf("val_%v", i)) privValidators[i] = validator @@ -65,7 +65,7 @@ func (dg *deterministicGenesis) GenesisDoc(numAccounts int, randBalance bool, mi } -func (dg *deterministicGenesis) Account(randBalance bool, minBalance uint64) (acm.Account, acm.PrivateAccount) { +func (dg *deterministicGenesis) Account(randBalance bool, minBalance uint64) (acm.Account, acm.AddressableSigner) { privateKey, err := acm.GeneratePrivateKey(dg.random) if err != nil { panic(fmt.Errorf("could not generate private key deterministically")) diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go index 2254c1105c7577c8dfef4e3f98068ce841c03e5a..96a614cac83ebe8367a84e76fad91563a1198375 100644 --- a/keys/mock/key_client_mock.go +++ b/keys/mock/key_client_mock.go @@ -59,6 +59,19 @@ func newMockKey() (*MockKey, error) { return key, nil } +func mockKeyFromPrivateAccount(privateAccount acm.PrivateAccount) *MockKey { + _, ok := privateAccount.PrivateKey().Unwrap().(crypto.PrivKeyEd25519) + if !ok { + panic(fmt.Errorf("mock key client only supports ed25519 private keys at present")) + } + key := &MockKey{ + Address: privateAccount.Address(), + PublicKey: privateAccount.PublicKey().RawBytes(), + } + copy(key.PrivateKey[:], privateAccount.PrivateKey().RawBytes()) + return key +} + func (mockKey *MockKey) Sign(message []byte) (acm.Signature, error) { return acm.SignatureFromBytes(ed25519.Sign(&mockKey.PrivateKey, message)[:]) } @@ -73,10 +86,14 @@ type MockKeyClient struct { knownKeys map[acm.Address]*MockKey } -func NewMockKeyClient() *MockKeyClient { - return &MockKeyClient{ +func NewMockKeyClient(privateAccounts ...acm.PrivateAccount) *MockKeyClient { + client := &MockKeyClient{ knownKeys: make(map[acm.Address]*MockKey), } + for _, pa := range privateAccounts { + client.knownKeys[pa.Address()] = mockKeyFromPrivateAccount(pa) + } + return client } func (mock *MockKeyClient) NewKey() acm.Address { diff --git a/logging/config/config.go b/logging/config/config.go index dca01d2ed6049399c11a7f1d251c9a71afa874e8..8616aeb7ac0aec1a18098c127f9bc6daf343d8af 100644 --- a/logging/config/config.go +++ b/logging/config/config.go @@ -15,6 +15,7 @@ import ( type LoggingConfig struct { RootSink *SinkConfig `toml:",omitempty"` ExcludeTrace bool + NonBlocking bool } // For encoding a top-level '[logging]' TOML table diff --git a/logging/lifecycle/lifecycle.go b/logging/lifecycle/lifecycle.go index d130904c2e5b9f2bf3d21aaadd1edda29409eadb..f17622e694153483e4cccd99aa686687cb430982 100644 --- a/logging/lifecycle/lifecycle.go +++ b/logging/lifecycle/lifecycle.go @@ -38,64 +38,56 @@ import ( // Obtain a logger from a LoggingConfig func NewLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (*logging.Logger, error) { - var logger *logging.Logger - var errCh channels.Channel - var err error if loggingConfig == nil { - logger, errCh, err = NewStdErrLogger() - if err != nil { - return nil, err - } + return NewStdErrLogger() } else { - outputLogger, err := loggerFromLoggingConfig(loggingConfig) + outputLogger, errCh, err := loggerFromLoggingConfig(loggingConfig) if err != nil { return nil, err } - logger, errCh = NewLogger(outputLogger) + logger := NewLogger(outputLogger) if loggingConfig.ExcludeTrace { logger.Trace = kitlog.NewNopLogger() } + go func() { + err := <-errCh.Out() + if err != nil { + fmt.Printf("Logging error: %v", err) + } + }() + return logger, nil } - go func() { - err := <-errCh.Out() - if err != nil { - fmt.Printf("Logging error: %v", err) - } - }() - - return logger, nil } // Hot swap logging config by replacing output loggers of passed InfoTraceLogger // with those built from loggingConfig -func SwapOutputLoggersFromLoggingConfig(logger *logging.Logger, loggingConfig *config.LoggingConfig) error { - outputLogger, err := loggerFromLoggingConfig(loggingConfig) +func SwapOutputLoggersFromLoggingConfig(logger *logging.Logger, loggingConfig *config.LoggingConfig) (error, channels.Channel) { + outputLogger, errCh, err := loggerFromLoggingConfig(loggingConfig) if err != nil { - return err + return err, channels.NewDeadChannel() } logger.SwapOutput(outputLogger) - return nil + return nil, errCh } -func NewStdErrLogger() (*logging.Logger, channels.Channel, error) { +func NewStdErrLogger() (*logging.Logger, error) { outputLogger, err := loggers.NewStreamLogger(os.Stderr, loggers.TerminalFormat) if err != nil { - return nil, nil, err + return nil, err } - logger, errCh := NewLogger(outputLogger) - return logger, errCh, nil + return NewLogger(outputLogger), nil } // Provided a standard logger that outputs to the supplied underlying outputLogger -func NewLogger(outputLogger kitlog.Logger) (*logging.Logger, channels.Channel) { - logger, errCh := logging.NewLogger(outputLogger) +func NewLogger(outputLogger kitlog.Logger) *logging.Logger { + logger := 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 logger.With(structure.RunId, runId), errCh + return logger.With(structure.RunId, runId) } func JustLogger(logger *logging.Logger, _ channels.Channel) *logging.Logger { @@ -103,15 +95,19 @@ func JustLogger(logger *logging.Logger, _ channels.Channel) *logging.Logger { } func CaptureStdlibLogOutput(infoTraceLogger *logging.Logger) { - stdlib.CaptureRootLogger(infoTraceLogger. - With(structure.CapturedLoggingSourceKey, "stdlib_log")) + stdlib.CaptureRootLogger(infoTraceLogger.With(structure.CapturedLoggingSourceKey, "stdlib_log")) } // Helpers -func loggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (kitlog.Logger, error) { +func loggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (kitlog.Logger, channels.Channel, error) { outputLogger, _, err := loggingConfig.RootSink.BuildLogger() if err != nil { - return nil, err + return nil, nil, err + } + var errCh channels.Channel = channels.NewDeadChannel() + var logger kitlog.Logger = loggers.BurrowFormatLogger(outputLogger) + if loggingConfig.NonBlocking { + logger, errCh = loggers.NonBlockingLogger(logger) } - return outputLogger, nil + return logger, errCh, err } diff --git a/logging/logger.go b/logging/logger.go index 51346c4f18d34ebd7c60603ee562afbf724c2509..3382293b9e1dc2c2dba8c2e022bdb635df6bd414 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -15,9 +15,7 @@ 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" ) @@ -43,22 +41,21 @@ type Logger struct { } // Create an InfoTraceLogger by passing the initial outputLogger. -func NewLogger(outputLogger kitlog.Logger) (*Logger, channels.Channel) { +func NewLogger(outputLogger kitlog.Logger) *Logger { // 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, + Info: kitlog.With(swapLogger, structure.ChannelKey, structure.InfoChannelName, ), - Trace: kitlog.With(wrappedOutputLogger, + Trace: kitlog.With(swapLogger, structure.ChannelKey, structure.TraceChannelName, ), - }, errCh + } } func NewNoopLogger() *Logger { @@ -140,10 +137,3 @@ 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/logger_test.go b/logging/logger_test.go index 05c0858c1764171c1776ca9be766e5aed6ab3c7f..0829fb39c389e9f15c8d64dfda160b68f230d0f4 100644 --- a/logging/logger_test.go +++ b/logging/logger_test.go @@ -23,7 +23,7 @@ import ( func TestLogger(t *testing.T) { stderrLogger := kitlog.NewLogfmtLogger(os.Stderr) - logger, _ := NewLogger(stderrLogger) + logger := NewLogger(stderrLogger) logger.Trace.Log("hello", "barry") } diff --git a/rpc/jsonrpc.go b/rpc/jsonrpc.go index 17b683beb81c67d4d9778ab15153fd9e36f6dfb8..b9b52e910f3070e00472b3b3b35876a638cdcb4d 100644 --- a/rpc/jsonrpc.go +++ b/rpc/jsonrpc.go @@ -16,6 +16,7 @@ package rpc import ( "encoding/json" + "fmt" ) // JSON-RPC 2.0 error codes. @@ -66,6 +67,10 @@ type ( } ) +func (err RPCError) Error() string { + return fmt.Sprintf("Error %v: %s", err.Code, err.Message) +} + // Create a new RPC request. This is the generic struct that is passed to RPC // methods func NewRPCRequest(id string, method string, params json.RawMessage) *RPCRequest { diff --git a/rpc/service.go b/rpc/service.go index 13d698346c30f135f78793536539b3263cff6eb9..28965eded7324281a5e136bab5933392d36f073e 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -18,6 +18,8 @@ import ( "context" "fmt" + "sync" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/binary" @@ -25,6 +27,7 @@ import ( "github.com/hyperledger/burrow/consensus/tendermint/query" "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" "github.com/hyperledger/burrow/permission" @@ -36,33 +39,39 @@ import ( // Magic! Should probably be configurable, but not shouldn't be so huge we // end up DoSing ourselves. -const MaxBlockLookback = 100 +const MaxBlockLookback = 1000 +const AccountsRingMutexCount = 100 // Base service that provides implementation for all underlying RPC methods type Service struct { - ctx context.Context - iterable state.Iterable - subscribable event.Subscribable - nameReg execution.NameRegIterable - blockchain bcm.Blockchain - transactor execution.Transactor - nodeView query.NodeView - logger *logging.Logger + ctx context.Context + committedState state.Iterable + commitLocker sync.Locker + nameReg execution.NameRegIterable + accounts *execution.Accounts + mempoolAccounts *execution.Accounts + subscribable event.Subscribable + blockchain bcm.Blockchain + transactor *execution.Transactor + nodeView query.NodeView + logger *logging.Logger } -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.Logger) *Service { +func NewService(ctx context.Context, committedState state.Iterable, nameReg execution.NameRegIterable, + checker state.Reader, committer execution.BatchCommitter, subscribable event.Subscribable, blockchain bcm.Blockchain, + keyClient keys.KeyClient, transactor *execution.Transactor, nodeView query.NodeView, logger *logging.Logger) *Service { return &Service{ - ctx: ctx, - iterable: iterable, - nameReg: nameReg, - subscribable: subscribable, - blockchain: blockchain, - transactor: transactor, - nodeView: nodeView, - logger: logger.With(structure.ComponentKey, "Service"), + ctx: ctx, + committedState: committedState, + accounts: execution.NewAccounts(committedState, keyClient, AccountsRingMutexCount), + mempoolAccounts: execution.NewAccounts(checker, keyClient, AccountsRingMutexCount), + nameReg: nameReg, + subscribable: subscribable, + blockchain: blockchain, + transactor: transactor, + nodeView: nodeView, + logger: logger.With(structure.ComponentKey, "Service"), } } @@ -75,12 +84,33 @@ func NewSubscribableService(subscribable event.Subscribable, logger *logging.Log } } -// Transacting... - -func (s *Service) Transactor() execution.Transactor { +// Get a Transactor providing methods for delegating signing and the core BroadcastTx function for publishing +// transactions to the network +func (s *Service) Transactor() *execution.Transactor { return s.transactor } +// By providing certain methods on the Transactor (such as Transact, Send, etc) with the (non-final) MempoolAccounts +// rather than the committed (final) Accounts state the transactor can assign a sequence number based on all of the txs +// it has seen since the last block - provided these transactions are successfully committed (via DeliverTx) then +// subsequent transactions will have valid sequence numbers. This allows Burrow to coordinate sequencing and signing +// for a key it holds or is provided - it is down to the key-holder to manage the mutual information between transactions +// concurrent within a new block window. + +// Get the latest committed account state and signing accounts +func (s *Service) Accounts() *execution.Accounts { + return s.accounts +} + +// Get pending account state residing in the mempool +func (s *Service) MempoolAccounts() *execution.Accounts { + return s.mempoolAccounts +} + +func (s *Service) CommitLocker() sync.Locker { + return s.commitLocker +} + func (s *Service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) { // Get all transactions for now transactions, err := s.nodeView.MempoolTransactions(maxTxs) @@ -180,7 +210,7 @@ func (s *Service) Peers() (*ResultPeers, error) { func (s *Service) NetInfo() (*ResultNetInfo, error) { listening := s.nodeView.IsListening() - listeners := []string{} + var listeners []string for _, listener := range s.nodeView.Listeners() { listeners = append(listeners, listener.String()) } @@ -203,7 +233,7 @@ func (s *Service) Genesis() (*ResultGenesis, error) { // Accounts func (s *Service) GetAccount(address acm.Address) (*ResultGetAccount, error) { - acc, err := s.iterable.GetAccount(address) + acc, err := s.accounts.GetAccount(address) if err != nil { return nil, err } @@ -215,7 +245,7 @@ func (s *Service) GetAccount(address acm.Address) (*ResultGetAccount, error) { func (s *Service) ListAccounts(predicate func(acm.Account) bool) (*ResultListAccounts, error) { accounts := make([]*acm.ConcreteAccount, 0) - s.iterable.IterateAccounts(func(account acm.Account) (stop bool) { + s.committedState.IterateAccounts(func(account acm.Account) (stop bool) { if predicate(account) { accounts = append(accounts, acm.AsConcreteAccount(account)) } @@ -229,7 +259,7 @@ func (s *Service) ListAccounts(predicate func(acm.Account) bool) (*ResultListAcc } func (s *Service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) { - account, err := s.iterable.GetAccount(address) + account, err := s.accounts.GetAccount(address) if err != nil { return nil, err } @@ -237,7 +267,7 @@ func (s *Service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage return nil, fmt.Errorf("UnknownAddress: %s", address) } - value, err := s.iterable.GetStorage(address, binary.LeftPadWord256(key)) + value, err := s.accounts.GetStorage(address, binary.LeftPadWord256(key)) if err != nil { return nil, err } @@ -248,7 +278,7 @@ func (s *Service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage } func (s *Service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { - account, err := s.iterable.GetAccount(address) + account, err := s.accounts.GetAccount(address) if err != nil { return nil, err } @@ -256,7 +286,7 @@ func (s *Service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { return nil, fmt.Errorf("UnknownAddress: %X", address) } var storageItems []StorageItem - s.iterable.IterateStorage(address, func(key, value binary.Word256) (stop bool) { + s.committedState.IterateStorage(address, func(key, value binary.Word256) (stop bool) { storageItems = append(storageItems, StorageItem{Key: key.UnpadLeft(), Value: value.UnpadLeft()}) return }) @@ -267,7 +297,7 @@ func (s *Service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { } func (s *Service) GetAccountHumanReadable(address acm.Address) (*ResultGetAccountHumanReadable, error) { - acc, err := s.iterable.GetAccount(address) + acc, err := s.accounts.GetAccount(address) if err != nil { return nil, err } diff --git a/rpc/tm/integration/client_test.go b/rpc/tm/integration/client_test.go index a827524f9de0ac13e438dd2f8d76ad002fda442f..268953bc695c506ca61d9f8357dd99aaaee820ef 100644 --- a/rpc/tm/integration/client_test.go +++ b/rpc/tm/integration/client_test.go @@ -59,14 +59,15 @@ func TestBroadcastTx(t *testing.T) { require.NoError(t, err) assert.False(t, receipt.CreatesContract, "This tx should not create a contract") assert.NotEmpty(t, receipt.TxHash, "Failed to compute tx hash") - n, errp := new(int), new(error) - buf := new(bytes.Buffer) + + buf, n, errp := new(bytes.Buffer), new(int), new(error) hasher := ripemd160.New() tx.WriteSignBytes(genesisDoc.ChainID(), buf, n, errp) assert.NoError(t, *errp) txSignBytes := buf.Bytes() hasher.Write(txSignBytes) txHashExpected := hasher.Sum(nil) + if bytes.Compare(receipt.TxHash, txHashExpected) != 0 { t.Fatalf("The receipt hash '%x' does not equal the ripemd160 hash of the "+ "transaction signed bytes calculated in the test: '%x'", diff --git a/rpc/tm/integration/shared_test.go b/rpc/tm/integration/main_test.go similarity index 82% rename from rpc/tm/integration/shared_test.go rename to rpc/tm/integration/main_test.go index 9e8f1d17d1dc0d498b145a7dbe9767c6549eb643..9833d45c9668313b8104da27c639286373b376d0 100644 --- a/rpc/tm/integration/shared_test.go +++ b/rpc/tm/integration/main_test.go @@ -21,11 +21,14 @@ import ( "os" "testing" "time" + + "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/core/integration" ) // Needs to be in a _test.go file to be picked up func TestMain(m *testing.M) { - returnValue := TestWrapper(func() int { + returnValue := integration.TestWrapper(privateAccounts, genesisDoc, func(*core.Kernel) int { return m.Run() }) diff --git a/rpc/tm/integration/shared.go b/rpc/tm/integration/shared.go index 2849634c4418dd5e1219ca256fe8c7ac93bbc037..e9c1981f3554daaf5af1d065ac41d3db7b53ae62 100644 --- a/rpc/tm/integration/shared.go +++ b/rpc/tm/integration/shared.go @@ -19,139 +19,38 @@ package integration import ( "bytes" - "context" - "fmt" "hash/fnv" - "strconv" "testing" - "os" - - "time" - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" - "github.com/hyperledger/burrow/consensus/tendermint/validator" - "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/core/integration" "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/permission" "github.com/hyperledger/burrow/rpc" tm_client "github.com/hyperledger/burrow/rpc/tm/client" "github.com/hyperledger/burrow/txs" "github.com/stretchr/testify/require" - tm_config "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/rpc/lib/client" ) const ( - chainName = "RPC_Test_Chain" rpcAddr = "0.0.0.0:46657" websocketAddr = rpcAddr websocketEndpoint = "/websocket" - testDir = "./test_scratch/tm_test" ) -// Enable logger output during tests -var debugLogging = true - // global variables for use across all tests var ( - privateAccounts = makePrivateAccounts(5) // make keys + privateAccounts = integration.MakePrivateAccounts(5) // make keys jsonRpcClient = rpcclient.NewJSONRPCClient(rpcAddr) httpClient = rpcclient.NewURIClient(rpcAddr) clients = map[string]tm_client.RPCClient{ "JSONRPC": jsonRpcClient, "HTTP": httpClient, } - // Initialised in initGlobalVariables - genesisDoc = new(genesis.GenesisDoc) + genesisDoc = integration.TestGenesisDoc(privateAccounts) ) -// We use this to wrap tests -func TestWrapper(runner func() int) int { - fmt.Println("Running with integration TestWrapper (rpc/tm/integration/shared.go)...") - - os.RemoveAll(testDir) - os.MkdirAll(testDir, 0777) - os.Chdir(testDir) - - tmConf := tm_config.DefaultConfig() - logger := logging.NewNoopLogger() - if debugLogging { - var err error - // Change config as needed - logger, err = lifecycle.NewLoggerFromLoggingConfig(&config.LoggingConfig{ - RootSink: config.Sink(). - SetTransform(config.FilterTransform(config.IncludeWhenAnyMatches, - //"","", - "method", "GetAccount", - "method", "BroadcastTx", - "tag", "sequence", - "tag", "Commit", - "tag", "CheckTx", - "tag", "DeliverTx", - )). - //AddSinks(config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, "run_call", "false")). - AddSinks(config.Sink().SetTransform(config.PruneTransform("log_channel", "trace", "scope", "returns", "run_id", "args")). - AddSinks(config.Sink().SetTransform(config.SortTransform("tx_hash", "time", "message", "method")). - SetOutput(config.StdoutOutput()))), - }) - if err != nil { - panic(err) - } - } - - privValidator := validator.NewPrivValidatorMemory(privateAccounts[0], privateAccounts[0]) - genesisDoc = testGenesisDoc() - kernel, err := core.NewKernel(context.Background(), privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), - nil, logger) - if err != nil { - panic(err) - } - // Sometimes better to not shutdown as logging errors on shutdown may obscure real issue - defer func() { - //kernel.Shutdown(context.Background()) - }() - - err = kernel.Boot() - if err != nil { - panic(err) - } - - return runner() -} - -func testGenesisDoc() *genesis.GenesisDoc { - accounts := make(map[string]acm.Account, len(privateAccounts)) - for i, pa := range privateAccounts { - account := acm.FromAddressable(pa) - account.AddToBalance(1 << 32) - account.SetPermissions(permission.AllAccountPermissions.Clone()) - accounts[fmt.Sprintf("user_%v", i)] = account - } - genesisTime, err := time.Parse("02-01-2006", "27-10-2017") - if err != nil { - panic("could not parse test genesis time") - } - return genesis.MakeGenesisDocFromAccounts(chainName, nil, genesisTime, accounts, - map[string]acm.Validator{ - "genesis_validator": acm.AsValidator(accounts["user_0"]), - }) -} - -// Deterministic account generation helper. Pass number of accounts to make -func makePrivateAccounts(n int) []acm.PrivateAccount { - accounts := make([]acm.PrivateAccount, n) - for i := 0; i < n; i++ { - accounts[i] = acm.GeneratePrivateAccountFromSecret("mysecret" + strconv.Itoa(i)) - } - return accounts -} - //------------------------------------------------------------------------------- // some default transaction functions @@ -165,7 +64,7 @@ func makeDefaultSendTx(t *testing.T, client tm_client.RPCClient, addr acm.Addres func makeDefaultSendTxSigned(t *testing.T, client tm_client.RPCClient, addr acm.Address, amt uint64) *txs.SendTx { tx := makeDefaultSendTx(t, client, addr, amt) - tx.SignInput(genesisDoc.ChainID(), 0, privateAccounts[0]) + require.NoError(t, tx.Sign(genesisDoc.ChainID(), privateAccounts[0])) return tx } @@ -174,14 +73,14 @@ func makeDefaultCallTx(t *testing.T, client tm_client.RPCClient, addr *acm.Addre sequence := getSequence(t, client, privateAccounts[0].Address()) tx := txs.NewCallTxWithSequence(privateAccounts[0].PublicKey(), addr, code, amt, gasLim, fee, sequence+1) - tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + require.NoError(t, tx.Sign(genesisDoc.ChainID(), privateAccounts[0])) return tx } func makeDefaultNameTx(t *testing.T, client tm_client.RPCClient, name, value string, amt, fee uint64) *txs.NameTx { sequence := getSequence(t, client, privateAccounts[0].Address()) tx := txs.NewNameTxWithSequence(privateAccounts[0].PublicKey(), name, value, amt, fee, sequence+1) - tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + require.NoError(t, tx.Sign(genesisDoc.ChainID(), privateAccounts[0])) return tx } diff --git a/rpc/tm/integration/websocket_helpers.go b/rpc/tm/integration/websocket_helpers.go index ec1c7ff2cfc22e50aa1b6c4840697fe0e72a316c..cbaa5749343add811a116d0628e5d0a92e800988 100644 --- a/rpc/tm/integration/websocket_helpers.go +++ b/rpc/tm/integration/websocket_helpers.go @@ -302,9 +302,9 @@ func unmarshalValidateCall(origin acm.Address, returnCode []byte, txid *[]byte) if !bytes.Equal(ret, returnCode) { return true, fmt.Errorf("call did not return correctly. Got %x, expected %x", ret, returnCode) } - if !bytes.Equal(data.TxID, *txid) { + if !bytes.Equal(data.TxHash, *txid) { return true, fmt.Errorf("TxIDs do not match up! Got %x, expected %x", - data.TxID, *txid) + data.TxHash, *txid) } return true, nil } diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go index 719f8cd1e4c927a6e98c6649db5b77327eff61fd..27753d43d6f98b86803fe911cfb74a0d87165d53 100644 --- a/rpc/tm/methods.go +++ b/rpc/tm/methods.go @@ -73,14 +73,14 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*gorpc.R }, "tx"), SignTx: gorpc.NewRPCFunc(func(tx txs.Tx, concretePrivateAccounts []*acm.ConcretePrivateAccount) (*rpc.ResultSignTx, error) { - tx, err := service.Transactor().SignTx(tx, acm.PrivateAccounts(concretePrivateAccounts)) + tx, err := service.Transactor().SignTx(tx, acm.SigningAccounts(concretePrivateAccounts)) return &rpc.ResultSignTx{Tx: txs.Wrap(tx)}, err }, "tx,privAccounts"), // Simulated call Call: gorpc.NewRPCFunc(func(fromAddress, toAddress acm.Address, data []byte) (*rpc.ResultCall, error) { - call, err := service.Transactor().Call(fromAddress, toAddress, data) + call, err := service.Transactor().Call(service.Accounts(), fromAddress, toAddress, data) if err != nil { return nil, err } @@ -88,7 +88,7 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*gorpc.R }, "fromAddress,toAddress,data"), CallCode: gorpc.NewRPCFunc(func(fromAddress acm.Address, code, data []byte) (*rpc.ResultCall, error) { - call, err := service.Transactor().CallCode(fromAddress, code, data) + call, err := service.Transactor().CallCode(service.Accounts(), fromAddress, code, data) if err != nil { return nil, err } diff --git a/rpc/v0/client.go b/rpc/v0/client.go new file mode 100644 index 0000000000000000000000000000000000000000..5bf27c5ff14a27fd8da0456f96f5a6f8879980a2 --- /dev/null +++ b/rpc/v0/client.go @@ -0,0 +1,127 @@ +package v0 + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "time" + + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/txs" +) + +type V0Client struct { + url string + codec rpc.Codec + client *http.Client +} + +type RPCResponse struct { + Result json.RawMessage `json:"result"` + Error *rpc.RPCError `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` +} + +func NewV0Client(url string) *V0Client { + return &V0Client{ + url: url, + codec: NewTCodec(), + client: &http.Client{ + Timeout: 1000 * time.Second, + }, + } +} + +func (vc *V0Client) Transact(param TransactParam) (*txs.Receipt, error) { + receipt := new(txs.Receipt) + err := vc.CallMethod(TRANSACT, param, receipt) + if err != nil { + return nil, err + } + return receipt, nil +} + +func (vc *V0Client) TransactAndHold(param TransactParam) (*events.EventDataCall, error) { + eventDataCall := new(events.EventDataCall) + err := vc.CallMethod(TRANSACT_AND_HOLD, param, eventDataCall) + if err != nil { + return nil, err + } + return eventDataCall, nil +} + +func (vc *V0Client) Send(param SendParam) (*txs.Receipt, error) { + receipt := new(txs.Receipt) + err := vc.CallMethod(SEND, param, receipt) + if err != nil { + return nil, err + } + return receipt, nil +} + +func (vc *V0Client) SendAndHold(param SendParam) (*txs.Receipt, error) { + receipt := new(txs.Receipt) + err := vc.CallMethod(SEND_AND_HOLD, param, receipt) + if err != nil { + return nil, err + } + return receipt, nil +} + +func (vc *V0Client) Call(param CallParam) (*execution.Call, error) { + call := new(execution.Call) + err := vc.CallMethod(CALL, param, call) + if err != nil { + return nil, err + } + return call, nil +} + +func (vc *V0Client) CallCode(param CallCodeParam) (*execution.Call, error) { + call := new(execution.Call) + err := vc.CallMethod(CALL_CODE, param, call) + if err != nil { + return nil, err + } + return call, nil +} + +func (vc *V0Client) CallMethod(method string, param interface{}, result interface{}) error { + // Marhsal into JSONRPC request object + bs, err := vc.codec.EncodeBytes(param) + if err != nil { + return err + } + request := rpc.NewRPCRequest("test", method, bs) + bs, err = json.Marshal(request) + if err != nil { + return err + } + // Post to JSONService + resp, err := vc.client.Post(vc.url, "application/json", bytes.NewBuffer(bs)) + if err != nil { + return err + } + // Marshal into JSONRPC response object + bs, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + rpcResponse := new(RPCResponse) + err = json.Unmarshal(bs, rpcResponse) + if err != nil { + return err + } + if rpcResponse.Error != nil { + return rpcResponse.Error + } + vc.codec.DecodeBytes(result, rpcResponse.Result) + if err != nil { + return err + } + return nil +} diff --git a/rpc/v0/integration/main_test.go b/rpc/v0/integration/main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5a0375653533e0e49b14512aca4d1517115783fc --- /dev/null +++ b/rpc/v0/integration/main_test.go @@ -0,0 +1,42 @@ +// +build integration + +// Space above here matters +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "os" + "testing" + "time" + + "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/core/integration" +) + +var privateAccounts = integration.MakePrivateAccounts(5) // make keys +var genesisDoc = integration.TestGenesisDoc(privateAccounts) +var kernel *core.Kernel + +// Needs to be in a _test.go file to be picked up +func TestMain(m *testing.M) { + returnValue := integration.TestWrapper(privateAccounts, genesisDoc, func(kern *core.Kernel) int { + kernel = kern + return m.Run() + }) + + time.Sleep(3 * time.Second) + os.Exit(returnValue) +} diff --git a/rpc/v0/integration/strange_loop.go b/rpc/v0/integration/strange_loop.go new file mode 100644 index 0000000000000000000000000000000000000000..f8137df9465c72ab725616c6463092a4006d9db0 --- /dev/null +++ b/rpc/v0/integration/strange_loop.go @@ -0,0 +1,3 @@ +package integration + +const strangeLoopBytecode = "60606040526017600055602260015560116002556001600360006101000a81548160ff021916908315150217905550341561003957600080fd5b6102c9806100486000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ebb384dd14610046575b600080fd5b341561005157600080fd5b61005961006f565b6040518082815260200191505060405180910390f35b60006002549050600360009054906101000a900460ff16156101cf57600154600254121561012e5760026000815480929190600101919050555060025490503073ffffffffffffffffffffffffffffffffffffffff1663ebb384dd6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561011157600080fd5b5af1151561011e57600080fd5b50505060405180519050506101ca565b6000600360006101000a81548160ff02191690831515021790555060025490503073ffffffffffffffffffffffffffffffffffffffff1663ebb384dd6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156101b157600080fd5b5af115156101be57600080fd5b50505060405180519050505b610299565b6000546002541315610273576002600081548092919060019003919050555060025490503073ffffffffffffffffffffffffffffffffffffffff1663ebb384dd6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561025657600080fd5b5af1151561026357600080fd5b5050506040518051905050610298565b6001600360006101000a81548160ff021916908315150217905550600254905061029a565b5b5b905600a165627a7a7230582071446a8de59540361bd59bb4f5a84f884006f53e50c1c89d2bfbdb72f92fd4700029" diff --git a/rpc/v0/integration/strange_loop.sh b/rpc/v0/integration/strange_loop.sh new file mode 100755 index 0000000000000000000000000000000000000000..d0cf04eecf86779b63ccdfb604aeee15792a52ec --- /dev/null +++ b/rpc/v0/integration/strange_loop.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +echo -e "package integration\n\nconst strangeLoopBytecode = \"$(solc --bin rpc/v0/integration/strange_loop.sol | tail -1)\"" > "${script_dir}/strange_loop.go" diff --git a/rpc/v0/integration/strange_loop.sol b/rpc/v0/integration/strange_loop.sol new file mode 100644 index 0000000000000000000000000000000000000000..2a3e07f242d5da224a6e5a9e502c8df38e308a39 --- /dev/null +++ b/rpc/v0/integration/strange_loop.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.4.16; + +contract StrangeLoop { + int top = 23; + int bottom = 34; + int depth = 17; + bool down = true; + + function UpsieDownsie() public returns (int i) { + i = depth; + if (down) { + if (depth < bottom) { + depth++; + i = depth; + this.UpsieDownsie(); + } else { + down = false; + i = depth; + this.UpsieDownsie(); + } + } else if (depth > top) { + depth--; + i = depth; + this.UpsieDownsie(); + } else { + down = true; + i = depth; + return; + } + } +} \ No newline at end of file diff --git a/rpc/v0/integration/v0_test.go b/rpc/v0/integration/v0_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b5229d74f927db974663f47ad71fea5619a7dd3e --- /dev/null +++ b/rpc/v0/integration/v0_test.go @@ -0,0 +1,240 @@ +// +build integration + +// Space above here matters +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integration + +import ( + "encoding/hex" + "fmt" + "testing" + + "context" + + "sync" + + "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/consensus/tendermint" + "github.com/hyperledger/burrow/execution/evm/abi" + "github.com/hyperledger/burrow/rpc/v0" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" +) + +func TestTransactCallNoCode(t *testing.T) { + cli := v0.NewV0Client("http://localhost:1337/rpc") + + // Flip flops between sending private key and input address to test private key and address based signing + privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) + toAddress := privateAccounts[2].Address() + + numCreates := 1000 + countCh := committedTxCount(t) + for i := 0; i < numCreates; i++ { + receipt, err := cli.Transact(v0.TransactParam{ + PrivKey: privKey(i), + InputAddress: inputAddress(i), + Address: toAddress.Bytes(), + Data: []byte{}, + Fee: 2, + GasLimit: 10000 + uint64(i), + }) + require.NoError(t, err) + assert.False(t, receipt.CreatesContract) + assert.Equal(t, toAddress, receipt.ContractAddress) + } + require.Equal(t, numCreates, <-countCh) +} + +func TestTransactCreate(t *testing.T) { + numGoroutines := 100 + numCreates := 10 + wg := new(sync.WaitGroup) + wg.Add(numGoroutines) + cli := v0.NewV0Client("http://localhost:1337/rpc") + // Flip flops between sending private key and input address to test private key and address based signing + privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) + bc, err := hex.DecodeString(strangeLoopBytecode) + require.NoError(t, err) + countCh := committedTxCount(t) + for i := 0; i < numGoroutines; i++ { + go func() { + for j := 0; j < numCreates; j++ { + create, err := cli.Transact(v0.TransactParam{ + PrivKey: privKey(j), + InputAddress: inputAddress(j), + Address: nil, + Data: bc, + Fee: 2, + GasLimit: 10000, + }) + if assert.NoError(t, err) { + assert.True(t, create.CreatesContract) + } + } + wg.Done() + }() + } + wg.Wait() + + require.Equal(t, numGoroutines*numCreates, <-countCh) +} + +func BenchmarkTransactCreateContract(b *testing.B) { + cli := v0.NewV0Client("http://localhost:1337/rpc") + + privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) + bc, err := hex.DecodeString(strangeLoopBytecode) + require.NoError(b, err) + for i := 0; i < b.N; i++ { + create, err := cli.Transact(v0.TransactParam{ + PrivKey: privKey(i), + InputAddress: inputAddress(i), + Address: nil, + Data: bc, + Fee: 2, + GasLimit: 10000, + }) + require.NoError(b, err) + assert.True(b, create.CreatesContract) + } +} + +func TestTransactAndHold(t *testing.T) { + cli := v0.NewV0Client("http://localhost:1337/rpc") + bc, err := hex.DecodeString(strangeLoopBytecode) + require.NoError(t, err) + privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) + + numGoroutines := 5 + numRuns := 2 + countCh := committedTxCount(t) + for i := 0; i < numGoroutines; i++ { + for j := 0; j < numRuns; j++ { + create, err := cli.TransactAndHold(v0.TransactParam{ + PrivKey: privKey(j), + InputAddress: inputAddress(j), + Address: nil, + Data: bc, + Fee: 2, + GasLimit: 10000, + }) + require.NoError(t, err) + assert.Equal(t, 0, create.StackDepth) + functionID := abi.FunctionID("UpsieDownsie()") + call, err := cli.TransactAndHold(v0.TransactParam{ + PrivKey: privKey(j), + InputAddress: inputAddress(j), + Address: create.CallData.Callee.Bytes(), + Data: functionID[:], + Fee: 2, + GasLimit: 10000, + }) + require.NoError(t, err) + depth := binary.Uint64FromWord256(binary.LeftPadWord256(call.Return)) + // Would give 23 if taken from wrong frame + assert.Equal(t, 18, int(depth)) + } + } + require.Equal(t, numGoroutines*numRuns*2, <-countCh) +} + +func TestSend(t *testing.T) { + cli := v0.NewV0Client("http://localhost:1337/rpc") + + numSends := 1000 + privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) + countCh := committedTxCount(t) + for i := 0; i < numSends; i++ { + send, err := cli.Send(v0.SendParam{ + PrivKey: privKey(i), + InputAddress: inputAddress(i), + Amount: 2003, + ToAddress: privateAccounts[3].Address().Bytes(), + }) + require.NoError(t, err) + assert.Equal(t, false, send.CreatesContract) + } + require.Equal(t, numSends, <-countCh) +} + +func TestSendAndHold(t *testing.T) { + cli := v0.NewV0Client("http://localhost:1337/rpc") + + privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) + + for i := 0; i < 2; i++ { + send, err := cli.SendAndHold(v0.SendParam{ + PrivKey: privKey(i), + InputAddress: inputAddress(i), + Amount: 2003, + ToAddress: privateAccounts[3].Address().Bytes(), + }) + require.NoError(t, err) + assert.Equal(t, false, send.CreatesContract) + } +} + +var committedTxCountIndex = 0 + +func committedTxCount(t *testing.T) chan int { + var numTxs int64 + emptyBlocks := 0 + maxEmptyBlocks := 2 + outCh := make(chan int) + ch := make(chan *types.EventDataNewBlock) + ctx := context.Background() + subscriber := fmt.Sprintf("committedTxCount_%v", committedTxCountIndex) + committedTxCountIndex++ + require.NoError(t, tendermint.SubscribeNewBlock(ctx, kernel.Emitter, subscriber, ch)) + + go func() { + for ed := range ch { + if ed.Block.NumTxs == 0 { + emptyBlocks++ + } else { + emptyBlocks = 0 + } + if emptyBlocks > maxEmptyBlocks { + break + } + numTxs += ed.Block.NumTxs + t.Logf("Total TXs committed at block %v: %v (+%v)\n", ed.Block.Height, numTxs, ed.Block.NumTxs) + } + require.NoError(t, kernel.Emitter.UnsubscribeAll(ctx, subscriber)) + outCh <- int(numTxs) + }() + return outCh +} + +// Returns a pair of functions that mutually exclusively return the private key bytes or input address bytes of a +// private account in the same iteration of a loop indexed by an int +func privKeyInputAddressAlternator(privateAccount account.PrivateAccount) (func(int) []byte, func(int) []byte) { + privKey := privateAccount.PrivateKey().RawBytes() + inputAddress := privateAccount.Address().Bytes() + return alternator(privKey, 0), alternator(inputAddress, 1) +} + +func alternator(ret []byte, res int) func(int) []byte { + return func(i int) []byte { + if i%2 == res { + return ret + } + return nil + } +} diff --git a/rpc/v0/json_service.go b/rpc/v0/json_service.go index cac693d2a0bd5960aed26ec0cd223e5687ef858f..d075eeddcf586832a9d62e28301c2ea57fdf8530 100644 --- a/rpc/v0/json_service.go +++ b/rpc/v0/json_service.go @@ -52,8 +52,7 @@ func NewJSONServer(service server.HttpService) *JsonRpcServer { } // Start adds the rpc path to the router. -func (jrs *JsonRpcServer) Start(config *server.ServerConfig, - router *gin.Engine) { +func (jrs *JsonRpcServer) Start(config *server.ServerConfig, router *gin.Engine) { router.POST(config.HTTP.JsonRpcEndpoint, jrs.handleFunc) jrs.running = true } diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go index ef994e04dc76916301f9428fdf6255948c7cf2c2..346b53148a780d307473f31c106f3a2ab796edf5 100644 --- a/rpc/v0/methods.go +++ b/rpc/v0/methods.go @@ -15,6 +15,8 @@ package v0 import ( + "fmt" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/logging" @@ -66,7 +68,6 @@ type RequestHandlerFunc func(request *rpc.RPCRequest, requester interface{}) (in func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) map[string]RequestHandlerFunc { accountFilterFactory := filters.NewAccountFilterFactory() nameRegFilterFactory := filters.NewNameRegFilterFactory() - return map[string]RequestHandlerFunc{ // Accounts GET_ACCOUNTS: func(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) { @@ -172,7 +173,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - call, err := service.Transactor().Call(from, address, param.Data) + call, err := service.Transactor().Call(service.MempoolAccounts(), from, address, param.Data) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -188,7 +189,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - call, err := service.Transactor().CallCode(from, param.Code, param.Data) + call, err := service.Transactor().CallCode(service.MempoolAccounts(), from, param.Code, param.Data) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -213,7 +214,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - txRet, err := service.Transactor().SignTx(param.Tx, acm.PrivateAccounts(param.PrivAccounts)) + txRet, err := service.Transactor().SignTx(param.Tx, acm.SigningAccounts(param.PrivAccounts)) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -229,7 +230,12 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - receipt, err := service.Transactor().Transact(param.PrivKey, address, param.Data, param.GasLimit, param.Fee) + // Use mempool state so that transact can generate a run of sequence numbers when formulating transactions + inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + if err != nil { + return nil, rpc.INVALID_PARAMS, err + } + receipt, err := service.Transactor().Transact(inputAccount, address, param.Data, param.GasLimit, param.Fee) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -245,7 +251,11 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - ce, err := service.Transactor().TransactAndHold(param.PrivKey, address, param.Data, param.GasLimit, param.Fee) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + if err != nil { + return nil, rpc.INVALID_PARAMS, err + } + ce, err := service.Transactor().TransactAndHold(inputAccount, address, param.Data, param.GasLimit, param.Fee) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -261,7 +271,12 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - receipt, err := service.Transactor().Send(param.PrivKey, toAddress, param.Amount) + // Run Send against mempool state + inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + if err != nil { + return nil, rpc.INVALID_PARAMS, err + } + receipt, err := service.Transactor().Send(inputAccount, toAddress, param.Amount) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -277,7 +292,12 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - rec, err := service.Transactor().SendAndHold(param.PrivKey, toAddress, param.Amount) + // Run Send against mempool state + inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + if err != nil { + return nil, rpc.INVALID_PARAMS, err + } + rec, err := service.Transactor().SendAndHold(inputAccount, toAddress, param.Amount) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -289,7 +309,11 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - receipt, err := service.Transactor().TransactNameReg(param.PrivKey, param.Name, param.Data, param.Amount, param.Fee) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + if err != nil { + return nil, rpc.INVALID_PARAMS, err + } + receipt, err := service.Transactor().TransactNameReg(inputAccount, param.Name, param.Data, param.Amount, param.Fee) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -418,3 +442,19 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m }, } } + +// Gets signing account from onr of private key or address - failing if both are provided +func signingAccount(accounts *execution.Accounts, privKey, addressBytes []byte) (*execution.SequentialSigningAccount, error) { + if len(addressBytes) > 0 { + if len(privKey) > 0 { + return nil, fmt.Errorf("privKey and address provided but only one or the other should be given") + } + address, err := acm.AddressFromBytes(addressBytes) + if err != nil { + return nil, err + } + return accounts.SequentialSigningAccount(address), nil + } + + return accounts.SequentialSigningAccountFromPrivateKey(privKey) +} diff --git a/rpc/v0/params.go b/rpc/v0/params.go index f3cc8efc05ed9e2f5ed108b68ad4eb315d7357fb..2635308ef25aad3cb2bf01331bfca58763d34709 100644 --- a/rpc/v0/params.go +++ b/rpc/v0/params.go @@ -88,18 +88,20 @@ type ( // Used when sending a transaction to be created and signed on the server // (using the private key). This only uses the standard key type for now. TransactParam struct { - PrivKey []byte `json:"priv_key"` - Data []byte `json:"data"` - Address []byte `json:"address"` - Fee uint64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` + PrivKey []byte `json:"priv_key"` + InputAddress []byte `json:"input_account"` + Data []byte `json:"data"` + Address []byte `json:"address"` + Fee uint64 `json:"fee"` + GasLimit uint64 `json:"gas_limit"` } // Used when sending a 'Send' transaction. SendParam struct { - PrivKey []byte `json:"priv_key"` - ToAddress []byte `json:"to_address"` - Amount uint64 `json:"amount"` + PrivKey []byte `json:"priv_key"` + InputAddress []byte `json:"input_account"` + ToAddress []byte `json:"to_address"` + Amount uint64 `json:"amount"` } NameRegEntryParam struct { @@ -109,10 +111,11 @@ type ( // Used when sending a namereg transaction to be created and signed on the server // (using the private key). This only uses the standard key type for now. TransactNameRegParam struct { - PrivKey []byte `json:"priv_key"` - Name string `json:"name"` - Data string `json:"data"` - Fee uint64 `json:"fee"` - Amount uint64 `json:"amount"` + PrivKey []byte `json:"priv_key"` + InputAddress []byte `json:"input_account"` + Name string `json:"name"` + Data string `json:"data"` + Fee uint64 `json:"fee"` + Amount uint64 `json:"amount"` } ) diff --git a/rpc/v0/server/server.go b/rpc/v0/server/server.go index 9edc900f03d42fe5640e2edf8108dc1a11b2cfdc..5a6240013cdd6baa6402740bf864dedadccf934d 100644 --- a/rpc/v0/server/server.go +++ b/rpc/v0/server/server.go @@ -64,7 +64,7 @@ type ServeProcess struct { // Initializes all the servers and starts listening for connections. func (serveProcess *ServeProcess) Start() error { router := gin.New() - + gin.SetMode(gin.ReleaseMode) config := serveProcess.config ch := NewCORSMiddleware(config.CORS) diff --git a/sync/ring_mutex.go b/sync/ring_mutex.go new file mode 100644 index 0000000000000000000000000000000000000000..50240956eda7e38e5f9eca423d0c5a5a5982f5a0 --- /dev/null +++ b/sync/ring_mutex.go @@ -0,0 +1,92 @@ +package sync + +import ( + "sync" + + "hash" + + "encoding/binary" + + "github.com/OneOfOne/xxhash" +) + +type RingMutex struct { + mtxs []sync.RWMutex + hash func(address []byte) uint64 + mutexCount uint64 +} + +// Create a RW mutex that provides a pseudo-independent set of mutexes for addresses +// where the address space is mapped into possibly much smaller set of backing +// mutexes using the xxhash (non-cryptographic) +// hash function // modulo size. If some addresses collide modulo size they will be unnecessary +// contention between those addresses, but you can trade space against contention +// as desired. +func NewRingMutex(mutexCount int, hashMaker func() hash.Hash64) *RingMutex { + ringMutex := &RingMutex{ + mutexCount: uint64(mutexCount), + // max slice length is bounded by max(int) thus the argument type + mtxs: make([]sync.RWMutex, mutexCount, mutexCount), + hash: func(address []byte) uint64 { + buf := make([]byte, 8) + copy(buf, address) + return binary.LittleEndian.Uint64(buf) + }, + } + if hashMaker != nil { + hasherPool := &sync.Pool{ + New: func() interface{} { + return hashMaker() + }, + } + ringMutex.hash = func(address []byte) uint64 { + h := hasherPool.Get().(hash.Hash64) + defer func() { + h.Reset() + hasherPool.Put(h) + }() + h.Write(address) + return h.Sum64() + } + } + return ringMutex +} + +func NewRingMutexNoHash(mutexCount int) *RingMutex { + return NewRingMutex(mutexCount, nil) +} + +func NewRingMutexXXHash(mutexCount int) *RingMutex { + return NewRingMutex(mutexCount, func() hash.Hash64 { + return xxhash.New64() + }) +} + +func (mtx *RingMutex) Lock(address []byte) { + mtx.Mutex(address).Lock() +} + +func (mtx *RingMutex) Unlock(address []byte) { + mtx.Mutex(address).Unlock() +} + +func (mtx *RingMutex) RLock(address []byte) { + mtx.Mutex(address).RLock() +} + +func (mtx *RingMutex) RUnlock(address []byte) { + mtx.Mutex(address).RUnlock() +} + +// Return the size of the underlying array of mutexes +func (mtx *RingMutex) MutexCount() uint64 { + return mtx.mutexCount +} + +func (mtx *RingMutex) Mutex(address []byte) *sync.RWMutex { + return &mtx.mtxs[mtx.index(address)] +} + +func (mtx *RingMutex) index(address []byte) uint64 { + return mtx.hash(address) % mtx.mutexCount +} diff --git a/sync/ring_mutex_test.go b/sync/ring_mutex_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b6d8f40dc7c180d84a31b63556325653897c67cb --- /dev/null +++ b/sync/ring_mutex_test.go @@ -0,0 +1,98 @@ +package sync + +import ( + "encoding/base64" + "testing" + + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRingMutexXXHash_Lock(t *testing.T) { + mutexCount := 10 + numAddresses := byte(20) + mtxs := []*RingMutex{NewRingMutexXXHash(mutexCount)} + + for _, mtx := range mtxs { + // Using fewer mutexes than addresses to lock against should cause contention + writeCh := make(chan []byte) + checksum := 0 + + // We'll try to acquire a locks on all of our unique addresses, knowing that + // some of them will share an underlying RWMutex + for i := byte(0); i < numAddresses; i++ { + address := []byte{i} + go func() { + mtx.Lock(address) + writeCh <- address + }() + } + + // We should receive a message from all of those addresses for which we could + // acquire a lock, this should be almost surely deterministic since we are + // launching our goroutines sequentially from a single goroutine (if this bit + // breaks we can add a short pause between the 'go' statements above, for the + // purposes of the predictability of this test) + addresses := receiveAddresses(writeCh) + checksum += len(addresses) + // we hit lock contention on the tenth address so get 9 back + assert.Equal(t, 9, len(addresses)) + // Unlock the 9 locked mutexes + unlockAddresses(mtx, addresses) + + // Which should trigger another batch to make progress + addresses = receiveAddresses(writeCh) + checksum += len(addresses) + // Again the number we get back (but not the order) should be deterministic + // because we are unlocking sequentially from a single goroutine + assert.Equal(t, 7, len(addresses)) + unlockAddresses(mtx, addresses) + + // And again + addresses = receiveAddresses(writeCh) + checksum += len(addresses) + assert.Equal(t, 3, len(addresses)) + unlockAddresses(mtx, addresses) + + // And so on + addresses = receiveAddresses(writeCh) + checksum += len(addresses) + assert.Equal(t, 1, len(addresses)) + unlockAddresses(mtx, addresses) + + // Until we have unblocked all of the goroutines we released + addresses = receiveAddresses(writeCh) + checksum += len(addresses) + assert.Equal(t, 0, len(addresses)) + unlockAddresses(mtx, addresses) + checksum += len(addresses) + + // Check we've heard back from all of them + assert.EqualValues(t, numAddresses, checksum) + } +} + +func TestRingMutex_XXHash(t *testing.T) { + mtx := NewRingMutexXXHash(10) + address, err := base64.StdEncoding.DecodeString("/+ulTkCzpYg2ePaZtqS8dycJBLY9387yZPst8LX5YL0=") + assert.NoError(t, err) + assert.EqualValues(t, 8509033946529530334, mtx.hash(address)) +} + +func receiveAddresses(returnCh chan []byte) [][]byte { + var addresses [][]byte + for { + select { + case address := <-returnCh: + addresses = append(addresses, address) + case <-time.After(50 * time.Millisecond): + return addresses + } + } +} +func unlockAddresses(mtx *RingMutex, addresses [][]byte) { + for _, address := range addresses { + mtx.Unlock(address) + } +} diff --git a/txs/README.md b/txs/README.md deleted file mode 100644 index f99294b007dd8ac4b3a453f0256d9c0aeb43d247..0000000000000000000000000000000000000000 --- a/txs/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# `tendermint/block` - -## Block - -TODO: document - -### Header - -### Validation - -### Data - -## PartSet - -PartSet is used to split a byteslice of data into parts (pieces) for transmission. -By splitting data into smaller parts and computing a Merkle root hash on the list, -you can verify that a part is legitimately part of the complete data, and the -part can be forwarded to other peers before all the parts are known. In short, -it's a fast way to propagate a large file over a gossip network. - -PartSet was inspired by the LibSwift project. - -Usage: - -```Go -data := RandBytes(2 << 20) // Something large - -partSet := NewPartSetFromData(data) -partSet.Total() // Total number of 4KB parts -partSet.Count() // Equal to the Total, since we already have all the parts -partSet.Hash() // The Merkle root hash -partSet.BitArray() // A BitArray of partSet.Total() 1's - -header := partSet.Header() // Send this to the peer -header.Total // Total number of parts -header.Hash // The merkle root hash - -// Now we'll reconstruct the data from the parts -partSet2 := NewPartSetFromHeader(header) -partSet2.Total() // Same total as partSet.Total() -partSet2.Count() // Zero, since this PartSet doesn't have any parts yet. -partSet2.Hash() // Same hash as in partSet.Hash() -partSet2.BitArray() // A BitArray of partSet.Total() 0's - -// In a gossip network the parts would arrive in arbitrary order, perhaps -// in response to explicit requests for parts, or optimistically in response -// to the receiving peer's partSet.BitArray(). -for !partSet2.IsComplete() { - part := receivePartFromGossipNetwork() - added, err := partSet2.AddPart(part) - if err != nil { - // A wrong part, - // the merkle trail does not hash to partSet2.Hash() - } else if !added { - // A duplicate part already received - } -} - -data2, _ := ioutil.ReadAll(partSet2.GetReader()) -bytes.Equal(data, data2) // true -``` diff --git a/txs/bond_tx.go b/txs/bond_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..aa6aecea7d64472134e3a87ce99f17f3ed5c3824 --- /dev/null +++ b/txs/bond_tx.go @@ -0,0 +1,111 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +type BondTx struct { + PubKey acm.PublicKey + Signature acm.Signature + Inputs []*TxInput + UnbondTo []*TxOutput + txHashMemoizer +} + +var _ Tx = &BondTx{} + +func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) { + return &BondTx{ + PubKey: pubkey, + Inputs: []*TxInput{}, + UnbondTo: []*TxOutput{}, + }, nil +} + +func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err) + for i, in := range tx.Inputs { + in.WriteSignBytes(w, n, err) + if i != len(tx.Inputs)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err) + wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err) + wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err) + for i, out := range tx.UnbondTo { + out.WriteSignBytes(w, n, err) + if i != len(tx.UnbondTo)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(`]}]}`), w, n, err) +} + +func (tx *BondTx) GetInputs() []TxInput { + return copyInputs(tx.Inputs) +} + +func (tx *BondTx) String() string { + return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo) +} + +func (tx *BondTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} + +func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { + addr := pubkey.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return err + } + if acc == nil { + return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey) + } + return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1)) +} + +func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { + tx.Inputs = append(tx.Inputs, &TxInput{ + Address: pubkey.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: pubkey, + }) + return nil +} + +func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error { + tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ + Address: addr, + Amount: amt, + }) + return nil +} + +func (tx *BondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != len(tx.Inputs)+1 { + return fmt.Errorf("BondTx expects %v SigningAccounts but got %v", len(tx.Inputs)+1, + len(signingAccounts)) + } + var err error + tx.Signature, err = acm.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + for i := 1; i <= len(signingAccounts); i++ { + tx.Inputs[i].PublicKey = signingAccounts[i].PublicKey() + tx.Inputs[i].Signature, err = acm.ChainSign(signingAccounts[i], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign tx %v input %v: %v", tx, tx.Inputs[i], err) + } + } + return nil +} diff --git a/txs/call_tx.go b/txs/call_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..a3d0f1b849db203f34c55739bfd827ede46af135 --- /dev/null +++ b/txs/call_tx.go @@ -0,0 +1,90 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +type CallTx struct { + Input *TxInput + // Pointer since CallTx defines unset 'to' address as inducing account creation + Address *acm.Address + GasLimit uint64 + Fee uint64 + Data []byte + txHashMemoizer +} + +var _ Tx = &CallTx{} + +func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte, + amt, gasLimit, fee uint64) (*CallTx, error) { + + addr := from.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil +} + +func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte, + amt, gasLimit, fee, sequence uint64) *CallTx { + input := &TxInput{ + Address: from.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: from, + } + + return &CallTx{ + Input: input, + Address: to, + GasLimit: gasLimit, + Fee: fee, + Data: data, + } +} + +func (tx *CallTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("CallTx expects a single AddressableSigner for its single Input but %v were provieded", + len(signingAccounts)) + } + var err error + tx.Input.PublicKey = signingAccounts[0].PublicKey() + tx.Input.Signature, err = acm.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *CallTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *CallTx) String() string { + return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) +} + +func (tx *CallTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/name_tx.go b/txs/name_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..76ef57dceb4b4d568e1f5da46d9ce5d4575e5480 --- /dev/null +++ b/txs/name_tx.go @@ -0,0 +1,123 @@ +package txs + +import ( + "fmt" + "io" + + "regexp" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +// Name should be file system lik +// Data should be anything permitted in JSON +var regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$") +var regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`) + +type NameTx struct { + Input *TxInput + Name string + Data string + Fee uint64 + txHashMemoizer +} + +var _ Tx = &NameTx{} + +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 { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil +} + +func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx { + input := &TxInput{ + Address: from.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: from, + } + + return &NameTx{ + Input: input, + Name: name, + Data: data, + Fee: fee, + } +} + +func (tx *NameTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("NameTx expects a single AddressableSigner for its single Input but %v were provieded", + len(signingAccounts)) + } + var err error + tx.Input.PublicKey = signingAccounts[0].PublicKey() + tx.Input.Signature, err = acm.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err) + wire.WriteTo([]byte(`,"input":`), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *NameTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *NameTx) ValidateStrings() error { + if len(tx.Name) == 0 { + return ErrTxInvalidString{"Name must not be empty"} + } + if len(tx.Name) > MaxNameLength { + return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)} + } + if len(tx.Data) > MaxDataLength { + return ErrTxInvalidString{fmt.Sprintf("Data is too long. Max %d bytes", MaxDataLength)} + } + + if !validateNameRegEntryName(tx.Name) { + return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)} + } + + if !validateNameRegEntryData(tx.Data) { + return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)} + } + + return nil +} + +func (tx *NameTx) String() string { + return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) +} + +func (tx *NameTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} + +// filter strings +func validateNameRegEntryName(name string) bool { + return regexpAlphaNum.Match([]byte(name)) +} + +func validateNameRegEntryData(data string) bool { + return regexpJSON.Match([]byte(data)) +} diff --git a/txs/names.go b/txs/names.go index 23c693f0a1a8db9a6c6c559f30f601092482db99..3b0bceff26e0807dc549db8d198155af544a3e41 100644 --- a/txs/names.go +++ b/txs/names.go @@ -14,10 +14,6 @@ package txs -import ( - "regexp" -) - var ( MinNameRegistrationPeriod uint64 = 5 @@ -31,22 +27,8 @@ var ( MaxNameLength = 64 MaxDataLength = 1 << 16 - - // Name should be file system lik - // Data should be anything permitted in JSON - regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$") - regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`) ) -// filter strings -func validateNameRegEntryName(name string) bool { - return regexpAlphaNum.Match([]byte(name)) -} - -func validateNameRegEntryData(data string) bool { - return regexpJSON.Match([]byte(data)) -} - // base cost is "effective" number of bytes func NameBaseCost(name, data string) uint64 { return uint64(len(data) + 32) diff --git a/txs/permission_tx.go b/txs/permission_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..c2dad89bf1750cd35ab217fa613e591ce64305f1 --- /dev/null +++ b/txs/permission_tx.go @@ -0,0 +1,82 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/permission/snatives" + "github.com/tendermint/go-wire" +) + +type PermissionsTx struct { + Input *TxInput + PermArgs snatives.PermArgs + txHashMemoizer +} + +var _ Tx = &PermissionsTx{} + +func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) { + addr := from.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewPermissionsTxWithSequence(from, args, sequence), nil +} + +func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx { + input := &TxInput{ + Address: from.Address(), + Amount: 1, // NOTE: amounts can't be 0 ... + Sequence: sequence, + PublicKey: from, + } + + return &PermissionsTx{ + Input: input, + PermArgs: args, + } +} + +func (tx *PermissionsTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("PermissionsTx expects a single AddressableSigner for its single Input but %v were provieded", + len(signingAccounts)) + } + var err error + tx.Input.PublicKey = signingAccounts[0].PublicKey() + tx.Input.Signature, err = acm.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err) + wire.WriteJSON(&tx.PermArgs, w, n, err) + wire.WriteTo([]byte(`","input":`), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *PermissionsTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *PermissionsTx) String() string { + return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs) +} + +func (tx *PermissionsTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/rebond_tx.go b/txs/rebond_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..b5b22ff4b0f87e123b6b7101f2049b28d47405e5 --- /dev/null +++ b/txs/rebond_tx.go @@ -0,0 +1,55 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type RebondTx struct { + Address acm.Address + Height int + Signature acm.Signature + txHashMemoizer +} + +var _ Tx = &RebondTx{} + +func NewRebondTx(addr acm.Address, height int) *RebondTx { + return &RebondTx{ + Address: addr, + Height: height, + } +} + +func (tx *RebondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("RebondTx expects a single AddressableSigner for its signature but %v were provieded", + len(signingAccounts)) + } + var err error + tx.Signature, err = acm.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err) +} + +func (tx *RebondTx) GetInputs() []TxInput { + return nil +} + +func (tx *RebondTx) String() string { + return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) +} + +func (tx *RebondTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/send_tx.go b/txs/send_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..e9212fd8042ff5511a643b6b2ea7c32a94065978 --- /dev/null +++ b/txs/send_tx.go @@ -0,0 +1,103 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +type SendTx struct { + Inputs []*TxInput + Outputs []*TxOutput + txHashMemoizer +} + +var _ Tx = &SendTx{} + +func NewSendTx() *SendTx { + return &SendTx{ + Inputs: []*TxInput{}, + Outputs: []*TxOutput{}, + } +} + +func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) + for i, in := range tx.Inputs { + in.WriteSignBytes(w, n, err) + if i != len(tx.Inputs)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(`],"outputs":[`), w, n, err) + for i, out := range tx.Outputs { + out.WriteSignBytes(w, n, err) + if i != len(tx.Outputs)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(`]}]}`), w, n, err) +} + +func (tx *SendTx) GetInputs() []TxInput { + return copyInputs(tx.Inputs) +} + +func (tx *SendTx) String() string { + return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) +} + +func (tx *SendTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} + +func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { + addr := pubkey.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return err + } + if acc == nil { + return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey) + } + return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1) +} + +func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { + addr := pubkey.Address() + tx.Inputs = append(tx.Inputs, &TxInput{ + Address: addr, + Amount: amt, + Sequence: sequence, + PublicKey: pubkey, + }) + return nil +} + +func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error { + tx.Outputs = append(tx.Outputs, &TxOutput{ + Address: addr, + Amount: amt, + }) + return nil +} + +func (tx *SendTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != len(tx.Inputs) { + return fmt.Errorf("SendTx has %v Inputs but was provided with %v SigningAccounts", len(tx.Inputs), + len(signingAccounts)) + } + var err error + for i, signingAccount := range signingAccounts { + tx.Inputs[i].PublicKey = signingAccount.PublicKey() + tx.Inputs[i].Signature, err = acm.ChainSign(signingAccount, chainID, tx) + if err != nil { + return fmt.Errorf("could not sign tx %v input %v: %v", tx, tx.Inputs[i], err) + } + } + return nil +} diff --git a/txs/tx.go b/txs/tx.go index e79c1e457fdc625f83b42aad39974237e97111db..dec958e22c3f19420a819d81a2f0e10d795bc2f8 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -21,8 +21,6 @@ import ( "io" acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/permission/snatives" - "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" "golang.org/x/crypto/ripemd160" ) @@ -37,23 +35,6 @@ var ( ErrTxInvalidSignature = errors.New("error invalid signature") ) -type ErrTxInvalidString struct { - Msg string -} - -func (e ErrTxInvalidString) Error() string { - return e.Msg -} - -type ErrTxInvalidSequence struct { - Got uint64 - Expected uint64 -} - -func (e ErrTxInvalidSequence) Error() string { - return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected) -} - /* Tx (Transaction) is an atomic operation on the ledger state. @@ -95,104 +76,35 @@ var mapper = data.NewMapper(Wrapper{}). RegisterImplementation(&RebondTx{}, "rebond_tx", TxTypeRebond). RegisterImplementation(&PermissionsTx{}, "permissions_tx", TxTypePermissions) -//----------------------------------------------------------------------------- - -type ( - // TODO: replace with sum-type struct like ResultEvent - Tx interface { - WriteSignBytes(chainID string, w io.Writer, n *int, err *error) - String() string - GetInputs() []TxInput - Hash(chainID string) []byte - } - - Wrapper struct { - Tx `json:"unwrap"` - } + //----------------------------------------------------------------------------- - Encoder interface { - EncodeTx(tx Tx) ([]byte, error) - } - - Decoder interface { - DecodeTx(txBytes []byte) (Tx, error) - } - - TxInput struct { - Address acm.Address - Amount uint64 - Sequence uint64 - Signature acm.Signature - PublicKey acm.PublicKey - } - - TxOutput struct { - Address acm.Address - Amount uint64 - } - - // BroadcastTx or Transact - Receipt struct { - TxHash []byte - CreatesContract bool - ContractAddress acm.Address - } - - //------------------- - // Transaction Types - SendTx struct { - Inputs []*TxInput - Outputs []*TxOutput - txHashMemoizer - } - - NameTx struct { - Input *TxInput - Name string - Data string - Fee uint64 - txHashMemoizer - } - - CallTx struct { - Input *TxInput - // Pointer since CallTx defines unset 'to' address as inducing account creation - Address *acm.Address - GasLimit uint64 - Fee uint64 - Data []byte - txHashMemoizer - } +// TODO: replace with sum-type struct like ResultEvent +type Tx interface { + WriteSignBytes(chainID string, w io.Writer, n *int, err *error) + String() string + GetInputs() []TxInput + Hash(chainID string) []byte + Sign(chainID string, signingAccounts ...acm.AddressableSigner) error +} - PermissionsTx struct { - Input *TxInput - PermArgs snatives.PermArgs - txHashMemoizer - } +type Encoder interface { + EncodeTx(tx Tx) ([]byte, error) +} - // Out of service - BondTx struct { - PubKey acm.PublicKey - Signature acm.Signature - Inputs []*TxInput - UnbondTo []*TxOutput - txHashMemoizer - } +type Decoder interface { + DecodeTx(txBytes []byte) (Tx, error) +} - UnbondTx struct { - Address acm.Address - Height int - Signature acm.Signature - txHashMemoizer - } +// BroadcastTx or Transact +type Receipt struct { + TxHash []byte + CreatesContract bool + ContractAddress acm.Address +} - RebondTx struct { - Address acm.Address - Height int - Signature acm.Signature - txHashMemoizer - } -) +type Wrapper struct { + Tx `json:"unwrap"` +} // Wrap the Tx in a struct that allows for go-wire JSON serialisation func Wrap(tx Tx) Wrapper { @@ -226,242 +138,6 @@ func (txw *Wrapper) Unwrap() Tx { return txw.Tx } -func (txIn *TxInput) ValidateBasic() error { - if len(txIn.Address) != 20 { - return ErrTxInvalidAddress - } - if txIn.Amount == 0 { - return ErrTxInvalidAmount - } - return nil -} - -func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err) -} - -func (txIn *TxInput) String() string { - return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PublicKey) -} - -//----------------------------------------------------------------------------- - -func (txOut *TxOutput) ValidateBasic() error { - if len(txOut.Address) != 20 { - return ErrTxInvalidAddress - } - if txOut.Amount == 0 { - return ErrTxInvalidAmount - } - return nil -} - -func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err) -} - -func (txOut *TxOutput) String() string { - return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount) -} - -//----------------------------------------------------------------------------- - -func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) - for i, in := range tx.Inputs { - in.WriteSignBytes(w, n, err) - if i != len(tx.Inputs)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(`],"outputs":[`), w, n, err) - for i, out := range tx.Outputs { - out.WriteSignBytes(w, n, err) - if i != len(tx.Outputs)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(`]}]}`), w, n, err) -} - -func (tx *SendTx) GetInputs() []TxInput { - return copyInputs(tx.Inputs) -} - -func (tx *SendTx) String() string { - return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) -} - -func (tx *SendTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) - tx.Input.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(`}]}`), w, n, err) -} - -func (tx *CallTx) GetInputs() []TxInput { - return []TxInput{*tx.Input} -} - -func (tx *CallTx) String() string { - return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) -} - -func (tx *CallTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err) - wire.WriteTo([]byte(`,"input":`), w, n, err) - tx.Input.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err) - wire.WriteTo([]byte(`}]}`), w, n, err) -} - -func (tx *NameTx) GetInputs() []TxInput { - return []TxInput{*tx.Input} -} - -func (tx *NameTx) ValidateStrings() error { - if len(tx.Name) == 0 { - return ErrTxInvalidString{"Name must not be empty"} - } - if len(tx.Name) > MaxNameLength { - return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)} - } - if len(tx.Data) > MaxDataLength { - return ErrTxInvalidString{fmt.Sprintf("Data is too long. Max %d bytes", MaxDataLength)} - } - - if !validateNameRegEntryName(tx.Name) { - return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)} - } - - if !validateNameRegEntryData(tx.Data) { - return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)} - } - - return nil -} - -func (tx *NameTx) String() string { - return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) -} - -func (tx *NameTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err) - for i, in := range tx.Inputs { - in.WriteSignBytes(w, n, err) - if i != len(tx.Inputs)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err) - wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err) - wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err) - for i, out := range tx.UnbondTo { - out.WriteSignBytes(w, n, err) - if i != len(tx.UnbondTo)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(`]}]}`), w, n, err) -} - -func (tx *BondTx) GetInputs() []TxInput { - return copyInputs(tx.Inputs) -} - -func (tx *BondTx) String() string { - return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo) -} - -func (tx *BondTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err) -} - -func (tx *UnbondTx) GetInputs() []TxInput { - return nil -} - -func (tx *UnbondTx) String() string { - return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) -} - -func (tx *UnbondTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err) -} - -func (tx *RebondTx) GetInputs() []TxInput { - return nil -} - -func (tx *RebondTx) String() string { - return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) -} - -func (tx *RebondTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err) - wire.WriteJSON(&tx.PermArgs, w, n, err) - wire.WriteTo([]byte(`","input":`), w, n, err) - tx.Input.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(`}]}`), w, n, err) -} - -func (tx *PermissionsTx) GetInputs() []TxInput { - return []TxInput{*tx.Input} -} - -func (tx *PermissionsTx) String() string { - return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs) -} - -func (tx *PermissionsTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - // Avoid re-hashing the same in-memory Tx type txHashMemoizer struct { txHashBytes []byte @@ -499,6 +175,23 @@ func GenerateReceipt(chainId string, tx Tx) Receipt { return receipt } +type ErrTxInvalidString struct { + Msg string +} + +func (e ErrTxInvalidString) Error() string { + return e.Msg +} + +type ErrTxInvalidSequence struct { + Got uint64 + Expected uint64 +} + +func (e ErrTxInvalidSequence) Error() string { + return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected) +} + //-------------------------------------------------------------------------------- func copyInputs(inputs []*TxInput) []TxInput { diff --git a/txs/tx_input.go b/txs/tx_input.go new file mode 100644 index 0000000000000000000000000000000000000000..8ed9059da1b7d1827b310f8b0b58a77c1ea6ef1d --- /dev/null +++ b/txs/tx_input.go @@ -0,0 +1,35 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type TxInput struct { + Address acm.Address + Amount uint64 + Sequence uint64 + Signature acm.Signature + PublicKey acm.PublicKey +} + +func (txIn *TxInput) ValidateBasic() error { + if len(txIn.Address) != 20 { + return ErrTxInvalidAddress + } + if txIn.Amount == 0 { + return ErrTxInvalidAmount + } + return nil +} + +func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err) +} + +func (txIn *TxInput) String() string { + return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PublicKey) +} diff --git a/txs/tx_output.go b/txs/tx_output.go new file mode 100644 index 0000000000000000000000000000000000000000..79c17b4953aa5e7ba4c41119e6c7546f4dd8c5a3 --- /dev/null +++ b/txs/tx_output.go @@ -0,0 +1,32 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type TxOutput struct { + Address acm.Address + Amount uint64 +} + +func (txOut *TxOutput) ValidateBasic() error { + if len(txOut.Address) != 20 { + return ErrTxInvalidAddress + } + if txOut.Amount == 0 { + return ErrTxInvalidAmount + } + return nil +} + +func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err) +} + +func (txOut *TxOutput) String() string { + return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount) +} diff --git a/txs/tx_utils.go b/txs/tx_utils.go deleted file mode 100644 index dd5ddae2455d1e718e05535d07b8376aa4081b56..0000000000000000000000000000000000000000 --- a/txs/tx_utils.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package txs - -import ( - "fmt" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/account/state" - "github.com/hyperledger/burrow/permission/snatives" -) - -//---------------------------------------------------------------------------- -// SendTx interface for adding inputs/outputs and adding signatures - -func NewSendTx() *SendTx { - return &SendTx{ - Inputs: []*TxInput{}, - Outputs: []*TxOutput{}, - } -} - -func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { - addr := pubkey.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return err - } - if acc == nil { - return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey) - } - return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1) -} - -func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { - addr := pubkey.Address() - tx.Inputs = append(tx.Inputs, &TxInput{ - Address: addr, - Amount: amt, - Sequence: sequence, - PublicKey: pubkey, - }) - return nil -} - -func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error { - tx.Outputs = append(tx.Outputs, &TxOutput{ - Address: addr, - Amount: amt, - }) - return nil -} - -func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.PrivateAccount) error { - if i >= len(tx.Inputs) { - return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs)) - } - tx.Inputs[i].PublicKey = privAccount.PublicKey() - tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) - return nil -} - -//---------------------------------------------------------------------------- -// CallTx interface for creating tx - -func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte, - amt, gasLimit, fee uint64) (*CallTx, error) { - - addr := from.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return nil, err - } - if acc == nil { - return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from) - } - - sequence := acc.Sequence() + 1 - return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil -} - -func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte, - amt, gasLimit, fee, sequence uint64) *CallTx { - input := &TxInput{ - Address: from.Address(), - Amount: amt, - Sequence: sequence, - PublicKey: from, - } - - return &CallTx{ - Input: input, - Address: to, - GasLimit: gasLimit, - Fee: fee, - Data: data, - } -} - -func (tx *CallTx) Sign(chainID string, privAccount acm.PrivateAccount) { - tx.Input.PublicKey = privAccount.PublicKey() - tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------------- -// NameTx interface for creating tx - -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 { - return nil, err - } - if acc == nil { - return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) - } - - sequence := acc.Sequence() + 1 - return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil -} - -func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx { - input := &TxInput{ - Address: from.Address(), - Amount: amt, - Sequence: sequence, - PublicKey: from, - } - - return &NameTx{ - Input: input, - Name: name, - Data: data, - Fee: fee, - } -} - -func (tx *NameTx) Sign(chainID string, privAccount acm.PrivateAccount) { - tx.Input.PublicKey = privAccount.PublicKey() - tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------------- -// BondTx interface for adding inputs/outputs and adding signatures - -func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) { - return &BondTx{ - PubKey: pubkey, - Inputs: []*TxInput{}, - UnbondTo: []*TxOutput{}, - }, nil -} - -func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { - addr := pubkey.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return err - } - if acc == nil { - return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey) - } - return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1)) -} - -func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { - tx.Inputs = append(tx.Inputs, &TxInput{ - Address: pubkey.Address(), - Amount: amt, - Sequence: sequence, - PublicKey: pubkey, - }) - return nil -} - -func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error { - tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ - Address: addr, - Amount: amt, - }) - return nil -} - -func (tx *BondTx) SignBond(chainID string, privAccount acm.PrivateAccount) error { - tx.Signature = acm.ChainSign(privAccount, chainID, tx) - return nil -} - -func (tx *BondTx) SignInput(chainID string, i int, privAccount acm.PrivateAccount) error { - if i >= len(tx.Inputs) { - return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs)) - } - tx.Inputs[i].PublicKey = privAccount.PublicKey() - tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) - return nil -} - -//---------------------------------------------------------------------- -// UnbondTx interface for creating tx - -func NewUnbondTx(addr acm.Address, height int) *UnbondTx { - return &UnbondTx{ - Address: addr, - Height: height, - } -} - -func (tx *UnbondTx) Sign(chainID string, privAccount acm.PrivateAccount) { - tx.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------- -// RebondTx interface for creating tx - -func NewRebondTx(addr acm.Address, height int) *RebondTx { - return &RebondTx{ - Address: addr, - Height: height, - } -} - -func (tx *RebondTx) Sign(chainID string, privAccount acm.PrivateAccount) { - tx.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------------- -// PermissionsTx interface for creating tx - -func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) { - addr := from.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return nil, err - } - if acc == nil { - return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) - } - - sequence := acc.Sequence() + 1 - return NewPermissionsTxWithSequence(from, args, sequence), nil -} - -func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx { - input := &TxInput{ - Address: from.Address(), - Amount: 1, // NOTE: amounts can't be 0 ... - Sequence: sequence, - PublicKey: from, - } - - return &PermissionsTx{ - Input: input, - PermArgs: args, - } -} - -func (tx *PermissionsTx) Sign(chainID string, privAccount acm.PrivateAccount) { - tx.Input.PublicKey = privAccount.PublicKey() - tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) -} diff --git a/txs/unbond_tx.go b/txs/unbond_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..786530295ff2596aea475ea8b534953fa53d0986 --- /dev/null +++ b/txs/unbond_tx.go @@ -0,0 +1,55 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type UnbondTx struct { + Address acm.Address + Height int + Signature acm.Signature + txHashMemoizer +} + +var _ Tx = &UnbondTx{} + +func NewUnbondTx(addr acm.Address, height int) *UnbondTx { + return &UnbondTx{ + Address: addr, + Height: height, + } +} + +func (tx *UnbondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("UnbondTx expects a single AddressableSigner for its signature but %v were provided", + len(signingAccounts)) + } + var err error + tx.Signature, err = acm.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err) +} + +func (tx *UnbondTx) GetInputs() []TxInput { + return nil +} + +func (tx *UnbondTx) String() string { + return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) +} + +func (tx *UnbondTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/vendor/github.com/OneOfOne/xxhash/LICENSE b/vendor/github.com/OneOfOne/xxhash/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9e30b4f342e115f0954c080f9609542fcc23a331 --- /dev/null +++ b/vendor/github.com/OneOfOne/xxhash/LICENSE @@ -0,0 +1,187 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. diff --git a/vendor/github.com/OneOfOne/xxhash/xxhash.go b/vendor/github.com/OneOfOne/xxhash/xxhash.go new file mode 100644 index 0000000000000000000000000000000000000000..817783d1a309dca1dd9e44b27fffc7b26a406e55 --- /dev/null +++ b/vendor/github.com/OneOfOne/xxhash/xxhash.go @@ -0,0 +1,191 @@ +package xxhash + +const ( + prime32x1 uint32 = 2654435761 + prime32x2 uint32 = 2246822519 + prime32x3 uint32 = 3266489917 + prime32x4 uint32 = 668265263 + prime32x5 uint32 = 374761393 + + prime64x1 uint64 = 11400714785074694791 + prime64x2 uint64 = 14029467366897019727 + prime64x3 uint64 = 1609587929392839161 + prime64x4 uint64 = 9650029242287828579 + prime64x5 uint64 = 2870177450012600261 + + maxInt32 int32 = (1<<31 - 1) +) + +// Checksum32 returns the checksum of the input data with the seed set to 0. +func Checksum32(in []byte) uint32 { + return Checksum32S(in, 0) +} + +// ChecksumString32 returns the checksum of the input data, without creating a copy, with the seed set to 0. +func ChecksumString32(s string) uint32 { + return ChecksumString32S(s, 0) +} + +type XXHash32 struct { + mem [16]byte + ln, memIdx int32 + v1, v2, v3, v4 uint32 + seed uint32 +} + +// Size returns the number of bytes Sum will return. +func (xx *XXHash32) Size() int { + return 4 +} + +// BlockSize returns the hash's underlying block size. +// The Write method must be able to accept any amount +// of data, but it may operate more efficiently if all writes +// are a multiple of the block size. +func (xx *XXHash32) BlockSize() int { + return 16 +} + +// NewS32 creates a new hash.Hash32 computing the 32bit xxHash checksum starting with the specific seed. +func NewS32(seed uint32) (xx *XXHash32) { + xx = &XXHash32{ + seed: seed, + } + xx.Reset() + return +} + +// New32 creates a new hash.Hash32 computing the 32bit xxHash checksum starting with the seed set to 0. +func New32() *XXHash32 { + return NewS32(0) +} + +func (xx *XXHash32) Reset() { + xx.v1 = xx.seed + prime32x1 + prime32x2 + xx.v2 = xx.seed + prime32x2 + xx.v3 = xx.seed + xx.v4 = xx.seed - prime32x1 + xx.ln, xx.memIdx = 0, 0 +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (xx *XXHash32) Sum(in []byte) []byte { + s := xx.Sum32() + return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) +} + +// Checksum64 an alias for Checksum64S(in, 0) +func Checksum64(in []byte) uint64 { + return Checksum64S(in, 0) +} + +// ChecksumString64 returns the checksum of the input data, without creating a copy, with the seed set to 0. +func ChecksumString64(s string) uint64 { + return ChecksumString64S(s, 0) +} + +type XXHash64 struct { + v1, v2, v3, v4 uint64 + seed uint64 + ln uint64 + mem [32]byte + memIdx int8 +} + +var zeroVs64 = [...]uint64{ + // workaround static overflow checker + func(s uint64) uint64 { return s + prime64x1 + prime64x2 }(0), + prime64x2, + 0, + func(s uint64) uint64 { return s - prime64x1 }(0), +} + +// Size returns the number of bytes Sum will return. +func (xx *XXHash64) Size() int { + return 8 +} + +// BlockSize returns the hash's underlying block size. +// The Write method must be able to accept any amount +// of data, but it may operate more efficiently if all writes +// are a multiple of the block size. +func (xx *XXHash64) BlockSize() int { + return 32 +} + +// NewS64 creates a new hash.Hash64 computing the 64bit xxHash checksum starting with the specific seed. +func NewS64(seed uint64) (xx *XXHash64) { + xx = &XXHash64{ + seed: seed, + } + xx.Reset() + return +} + +// New64 creates a new hash.Hash64 computing the 64bit xxHash checksum starting with the seed set to 0x0. +func New64() *XXHash64 { + return NewS64(0) +} + +func (xx *XXHash64) Reset() { + xx.ln, xx.memIdx = 0, 0 + xx.v1, xx.v2, xx.v3, xx.v4 = resetVs64(xx.seed) +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (xx *XXHash64) Sum(in []byte) []byte { + s := xx.Sum64() + return append(in, byte(s>>56), byte(s>>48), byte(s>>40), byte(s>>32), byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) +} + +// force the compiler to use ROTL instructions + +func rotl32_1(x uint32) uint32 { return (x << 1) | (x >> (32 - 1)) } +func rotl32_7(x uint32) uint32 { return (x << 7) | (x >> (32 - 7)) } +func rotl32_11(x uint32) uint32 { return (x << 11) | (x >> (32 - 11)) } +func rotl32_12(x uint32) uint32 { return (x << 12) | (x >> (32 - 12)) } +func rotl32_13(x uint32) uint32 { return (x << 13) | (x >> (32 - 13)) } +func rotl32_17(x uint32) uint32 { return (x << 17) | (x >> (32 - 17)) } +func rotl32_18(x uint32) uint32 { return (x << 18) | (x >> (32 - 18)) } + +func rotl64_1(x uint64) uint64 { return (x << 1) | (x >> (64 - 1)) } +func rotl64_7(x uint64) uint64 { return (x << 7) | (x >> (64 - 7)) } +func rotl64_11(x uint64) uint64 { return (x << 11) | (x >> (64 - 11)) } +func rotl64_12(x uint64) uint64 { return (x << 12) | (x >> (64 - 12)) } +func rotl64_18(x uint64) uint64 { return (x << 18) | (x >> (64 - 18)) } +func rotl64_23(x uint64) uint64 { return (x << 23) | (x >> (64 - 23)) } +func rotl64_27(x uint64) uint64 { return (x << 27) | (x >> (64 - 27)) } +func rotl64_31(x uint64) uint64 { return (x << 31) | (x >> (64 - 31)) } + +func mix64(h uint64) uint64 { + h ^= h >> 33 + h *= prime64x2 + h ^= h >> 29 + h *= prime64x3 + h ^= h >> 32 + return h +} + +func resetVs64(seed uint64) (v1, v2, v3, v4 uint64) { + if seed == 0 { + return zeroVs64[0], zeroVs64[1], zeroVs64[2], zeroVs64[3] + } + return (seed + prime64x1 + prime64x2), (seed + prime64x2), (seed), (seed - prime64x1) +} + +// borrowed from cespare +func round64(h, v uint64) uint64 { + h += v * prime64x2 + h = rotl64_31(h) + h *= prime64x1 + return h +} + +func mergeRound64(h, v uint64) uint64 { + v = round64(0, v) + h ^= v + h = h*prime64x1 + prime64x4 + return h +} diff --git a/vendor/github.com/OneOfOne/xxhash/xxhash_go17.go b/vendor/github.com/OneOfOne/xxhash/xxhash_go17.go new file mode 100644 index 0000000000000000000000000000000000000000..d4ebc0530777c9533c3f42f0062cad4b57b0ba17 --- /dev/null +++ b/vendor/github.com/OneOfOne/xxhash/xxhash_go17.go @@ -0,0 +1,160 @@ +package xxhash + +func u32(in []byte) uint32 { + return uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24 +} +func u64(in []byte) uint64 { + return uint64(in[0]) | uint64(in[1])<<8 | uint64(in[2])<<16 | uint64(in[3])<<24 | uint64(in[4])<<32 | uint64(in[5])<<40 | uint64(in[6])<<48 | uint64(in[7])<<56 +} + +// Checksum32S returns the checksum of the input bytes with the specific seed. +func Checksum32S(in []byte, seed uint32) (h uint32) { + var i int + + if len(in) > 15 { + var ( + v1 = seed + prime32x1 + prime32x2 + v2 = seed + prime32x2 + v3 = seed + 0 + v4 = seed - prime32x1 + ) + for ; i < len(in)-15; i += 16 { + in := in[i : i+16 : len(in)] + v1 += u32(in[0:4:len(in)]) * prime32x2 + v1 = rotl32_13(v1) * prime32x1 + + v2 += u32(in[4:8:len(in)]) * prime32x2 + v2 = rotl32_13(v2) * prime32x1 + + v3 += u32(in[8:12:len(in)]) * prime32x2 + v3 = rotl32_13(v3) * prime32x1 + + v4 += u32(in[12:16:len(in)]) * prime32x2 + v4 = rotl32_13(v4) * prime32x1 + } + + h = rotl32_1(v1) + rotl32_7(v2) + rotl32_12(v3) + rotl32_18(v4) + + } else { + h = seed + prime32x5 + } + + h += uint32(len(in)) + for ; i <= len(in)-4; i += 4 { + in := in[i : i+4 : len(in)] + h += u32(in[0:4:len(in)]) * prime32x3 + h = rotl32_17(h) * prime32x4 + } + + for ; i < len(in); i++ { + h += uint32(in[i]) * prime32x5 + h = rotl32_11(h) * prime32x1 + } + + h ^= h >> 15 + h *= prime32x2 + h ^= h >> 13 + h *= prime32x3 + h ^= h >> 16 + + return +} + +func (xx *XXHash32) Write(in []byte) (n int, err error) { + i, ml := 0, int(xx.memIdx) + n = len(in) + xx.ln += int32(n) + + if d := 16 - ml; ml > 0 && ml+len(in) > 16 { + xx.memIdx += int32(copy(xx.mem[xx.memIdx:], in[:d])) + ml, in = 16, in[d:len(in):len(in)] + } else if ml+len(in) < 16 { + xx.memIdx += int32(copy(xx.mem[xx.memIdx:], in)) + return + } + + if ml > 0 { + i += 16 - ml + xx.memIdx += int32(copy(xx.mem[xx.memIdx:len(xx.mem):len(xx.mem)], in)) + in := xx.mem[:16:len(xx.mem)] + + xx.v1 += u32(in[0:4:len(in)]) * prime32x2 + xx.v1 = rotl32_13(xx.v1) * prime32x1 + + xx.v2 += u32(in[4:8:len(in)]) * prime32x2 + xx.v2 = rotl32_13(xx.v2) * prime32x1 + + xx.v3 += u32(in[8:12:len(in)]) * prime32x2 + xx.v3 = rotl32_13(xx.v3) * prime32x1 + + xx.v4 += u32(in[12:16:len(in)]) * prime32x2 + xx.v4 = rotl32_13(xx.v4) * prime32x1 + + xx.memIdx = 0 + } + + for ; i <= len(in)-16; i += 16 { + in := in[i : i+16 : len(in)] + xx.v1 += u32(in[0:4:len(in)]) * prime32x2 + xx.v1 = rotl32_13(xx.v1) * prime32x1 + + xx.v2 += u32(in[4:8:len(in)]) * prime32x2 + xx.v2 = rotl32_13(xx.v2) * prime32x1 + + xx.v3 += u32(in[8:12:len(in)]) * prime32x2 + xx.v3 = rotl32_13(xx.v3) * prime32x1 + + xx.v4 += u32(in[12:16:len(in)]) * prime32x2 + xx.v4 = rotl32_13(xx.v4) * prime32x1 + } + + if len(in)-i != 0 { + xx.memIdx += int32(copy(xx.mem[xx.memIdx:], in[i:len(in):len(in)])) + } + + return +} + +func (xx *XXHash32) Sum32() (h uint32) { + var i int32 + if xx.ln > 15 { + h = rotl32_1(xx.v1) + rotl32_7(xx.v2) + rotl32_12(xx.v3) + rotl32_18(xx.v4) + } else { + h = xx.seed + prime32x5 + } + + h += uint32(xx.ln) + + if xx.memIdx > 0 { + for ; i < xx.memIdx-3; i += 4 { + in := xx.mem[i : i+4 : len(xx.mem)] + h += u32(in[0:4:len(in)]) * prime32x3 + h = rotl32_17(h) * prime32x4 + } + + for ; i < xx.memIdx; i++ { + h += uint32(xx.mem[i]) * prime32x5 + h = rotl32_11(h) * prime32x1 + } + } + h ^= h >> 15 + h *= prime32x2 + h ^= h >> 13 + h *= prime32x3 + h ^= h >> 16 + + return +} + +// Checksum64S returns the 64bit xxhash checksum for a single input +func Checksum64S(in []byte, seed uint64) uint64 { + if len(in) == 0 && seed == 0 { + return 0xef46db3751d8e999 + } + + if len(in) > 31 { + return checksum64(in, seed) + } + + return checksum64Short(in, seed) +} diff --git a/vendor/github.com/OneOfOne/xxhash/xxhash_safe.go b/vendor/github.com/OneOfOne/xxhash/xxhash_safe.go new file mode 100644 index 0000000000000000000000000000000000000000..5252b774dd19771ce822432c677ebd3a81dfd8be --- /dev/null +++ b/vendor/github.com/OneOfOne/xxhash/xxhash_safe.go @@ -0,0 +1,183 @@ +// +build appengine safe + +package xxhash + +// Backend returns the current version of xxhash being used. +const Backend = "GoSafe" + +func ChecksumString32S(s string, seed uint32) uint32 { + return Checksum32S([]byte(s), seed) +} + +func (xx *XXHash32) WriteString(s string) (int, error) { + if len(s) == 0 { + return 0, nil + } + return xx.Write([]byte(s)) +} + +func ChecksumString64S(s string, seed uint64) uint64 { + return Checksum64S([]byte(s), seed) +} + +func (xx *XXHash64) WriteString(s string) (int, error) { + if len(s) == 0 { + return 0, nil + } + return xx.Write([]byte(s)) +} + +func checksum64(in []byte, seed uint64) (h uint64) { + var ( + v1, v2, v3, v4 = resetVs64(seed) + + i int + ) + + for ; i < len(in)-31; i += 32 { + in := in[i : i+32 : len(in)] + v1 = round64(v1, u64(in[0:8:len(in)])) + v2 = round64(v2, u64(in[8:16:len(in)])) + v3 = round64(v3, u64(in[16:24:len(in)])) + v4 = round64(v4, u64(in[24:32:len(in)])) + } + + h = rotl64_1(v1) + rotl64_7(v2) + rotl64_12(v3) + rotl64_18(v4) + + h = mergeRound64(h, v1) + h = mergeRound64(h, v2) + h = mergeRound64(h, v3) + h = mergeRound64(h, v4) + + h += uint64(len(in)) + + for ; i < len(in)-7; i += 8 { + h ^= round64(0, u64(in[i:len(in):len(in)])) + h = rotl64_27(h)*prime64x1 + prime64x4 + } + + for ; i < len(in)-3; i += 4 { + h ^= uint64(u32(in[i:len(in):len(in)])) * prime64x1 + h = rotl64_23(h)*prime64x2 + prime64x3 + } + + for ; i < len(in); i++ { + h ^= uint64(in[i]) * prime64x5 + h = rotl64_11(h) * prime64x1 + } + + return mix64(h) +} + +func checksum64Short(in []byte, seed uint64) uint64 { + var ( + h = seed + prime64x5 + uint64(len(in)) + i int + ) + + for ; i < len(in)-7; i += 8 { + k := u64(in[i : i+8 : len(in)]) + h ^= round64(0, k) + h = rotl64_27(h)*prime64x1 + prime64x4 + } + + for ; i < len(in)-3; i += 4 { + h ^= uint64(u32(in[i:i+4:len(in)])) * prime64x1 + h = rotl64_23(h)*prime64x2 + prime64x3 + } + + for ; i < len(in); i++ { + h ^= uint64(in[i]) * prime64x5 + h = rotl64_11(h) * prime64x1 + } + + return mix64(h) +} + +func (xx *XXHash64) Write(in []byte) (n int, err error) { + var ( + ml = int(xx.memIdx) + d = 32 - ml + ) + + n = len(in) + xx.ln += uint64(n) + + if ml+len(in) < 32 { + xx.memIdx += int8(copy(xx.mem[xx.memIdx:len(xx.mem):len(xx.mem)], in)) + return + } + + i, v1, v2, v3, v4 := 0, xx.v1, xx.v2, xx.v3, xx.v4 + if ml > 0 && ml+len(in) > 32 { + xx.memIdx += int8(copy(xx.mem[xx.memIdx:len(xx.mem):len(xx.mem)], in[:d:len(in)])) + in = in[d:len(in):len(in)] + + in := xx.mem[0:32:len(xx.mem)] + + v1 = round64(v1, u64(in[0:8:len(in)])) + v2 = round64(v2, u64(in[8:16:len(in)])) + v3 = round64(v3, u64(in[16:24:len(in)])) + v4 = round64(v4, u64(in[24:32:len(in)])) + + xx.memIdx = 0 + } + + for ; i < len(in)-31; i += 32 { + in := in[i : i+32 : len(in)] + v1 = round64(v1, u64(in[0:8:len(in)])) + v2 = round64(v2, u64(in[8:16:len(in)])) + v3 = round64(v3, u64(in[16:24:len(in)])) + v4 = round64(v4, u64(in[24:32:len(in)])) + } + + if len(in)-i != 0 { + xx.memIdx += int8(copy(xx.mem[xx.memIdx:], in[i:len(in):len(in)])) + } + + xx.v1, xx.v2, xx.v3, xx.v4 = v1, v2, v3, v4 + + return +} + +func (xx *XXHash64) Sum64() (h uint64) { + var i int + if xx.ln > 31 { + v1, v2, v3, v4 := xx.v1, xx.v2, xx.v3, xx.v4 + h = rotl64_1(v1) + rotl64_7(v2) + rotl64_12(v3) + rotl64_18(v4) + + h = mergeRound64(h, v1) + h = mergeRound64(h, v2) + h = mergeRound64(h, v3) + h = mergeRound64(h, v4) + } else { + h = xx.seed + prime64x5 + } + + h += uint64(xx.ln) + if xx.memIdx > 0 { + in := xx.mem[:xx.memIdx] + for ; i < int(xx.memIdx)-7; i += 8 { + in := in[i : i+8 : len(in)] + k := u64(in[0:8:len(in)]) + k *= prime64x2 + k = rotl64_31(k) + k *= prime64x1 + h ^= k + h = rotl64_27(h)*prime64x1 + prime64x4 + } + + for ; i < int(xx.memIdx)-3; i += 4 { + in := in[i : i+4 : len(in)] + h ^= uint64(u32(in[0:4:len(in)])) * prime64x1 + h = rotl64_23(h)*prime64x2 + prime64x3 + } + + for ; i < int(xx.memIdx); i++ { + h ^= uint64(in[i]) * prime64x5 + h = rotl64_11(h) * prime64x1 + } + } + + return mix64(h) +} diff --git a/vendor/github.com/OneOfOne/xxhash/xxhash_unsafe.go b/vendor/github.com/OneOfOne/xxhash/xxhash_unsafe.go new file mode 100644 index 0000000000000000000000000000000000000000..59661249b1e51914cf2861d8f8ee436be8401b76 --- /dev/null +++ b/vendor/github.com/OneOfOne/xxhash/xxhash_unsafe.go @@ -0,0 +1,234 @@ +// +build !safe +// +build !appengine + +package xxhash + +import ( + "reflect" + "unsafe" +) + +// Backend returns the current version of xxhash being used. +const Backend = "GoUnsafe" + +// ChecksumString32S returns the checksum of the input data, without creating a copy, with the specific seed. +func ChecksumString32S(s string, seed uint32) uint32 { + if len(s) == 0 { + return Checksum32S(nil, seed) + } + ss := (*reflect.StringHeader)(unsafe.Pointer(&s)) + return Checksum32S((*[maxInt32]byte)(unsafe.Pointer(ss.Data))[:len(s):len(s)], seed) +} + +func (xx *XXHash32) WriteString(s string) (int, error) { + if len(s) == 0 { + return 0, nil + } + + ss := (*reflect.StringHeader)(unsafe.Pointer(&s)) + return xx.Write((*[maxInt32]byte)(unsafe.Pointer(ss.Data))[:len(s):len(s)]) +} + +// ChecksumString64S returns the checksum of the input data, without creating a copy, with the specific seed. +func ChecksumString64S(s string, seed uint64) uint64 { + if len(s) == 0 { + return Checksum64S(nil, seed) + } + + ss := (*reflect.StringHeader)(unsafe.Pointer(&s)) + return Checksum64S((*[maxInt32]byte)(unsafe.Pointer(ss.Data))[:len(s):len(s)], seed) +} + +func (xx *XXHash64) WriteString(s string) (int, error) { + if len(s) == 0 { + return 0, nil + } + ss := (*reflect.StringHeader)(unsafe.Pointer(&s)) + return xx.Write((*[maxInt32]byte)(unsafe.Pointer(ss.Data))[:len(s)]) +} + +func checksum64(in []byte, seed uint64) uint64 { + var ( + wordsLen = len(in) >> 3 + words = ((*[maxInt32 / 8]uint64)(unsafe.Pointer(&in[0])))[:wordsLen:wordsLen] + + h uint64 = prime64x5 + + v1, v2, v3, v4 = resetVs64(seed) + + i int + ) + + for ; i < len(words)-3; i += 4 { + words := (*[4]uint64)(unsafe.Pointer(&words[i])) + + v1 = round64(v1, words[0]) + v2 = round64(v2, words[1]) + v3 = round64(v3, words[2]) + v4 = round64(v4, words[3]) + } + + h = rotl64_1(v1) + rotl64_7(v2) + rotl64_12(v3) + rotl64_18(v4) + + h = mergeRound64(h, v1) + h = mergeRound64(h, v2) + h = mergeRound64(h, v3) + h = mergeRound64(h, v4) + + h += uint64(len(in)) + + for _, k := range words[i:] { + h ^= round64(0, k) + h = rotl64_27(h)*prime64x1 + prime64x4 + } + + if in = in[wordsLen<<3 : len(in) : len(in)]; len(in) > 3 { + words := (*[1]uint32)(unsafe.Pointer(&in[0])) + h ^= uint64(words[0]) * prime64x1 + h = rotl64_23(h)*prime64x2 + prime64x3 + + in = in[4:len(in):len(in)] + } + + for _, b := range in { + h ^= uint64(b) * prime64x5 + h = rotl64_11(h) * prime64x1 + } + + return mix64(h) +} + +func checksum64Short(in []byte, seed uint64) uint64 { + var ( + h = seed + prime64x5 + uint64(len(in)) + i int + ) + + if len(in) > 7 { + var ( + wordsLen = len(in) >> 3 + words = ((*[maxInt32 / 8]uint64)(unsafe.Pointer(&in[0])))[:wordsLen:wordsLen] + ) + + for i := range words { + h ^= round64(0, words[i]) + h = rotl64_27(h)*prime64x1 + prime64x4 + } + + i = wordsLen << 3 + } + + if in = in[i:len(in):len(in)]; len(in) > 3 { + words := (*[1]uint32)(unsafe.Pointer(&in[0])) + h ^= uint64(words[0]) * prime64x1 + h = rotl64_23(h)*prime64x2 + prime64x3 + + in = in[4:len(in):len(in)] + } + + for _, b := range in { + h ^= uint64(b) * prime64x5 + h = rotl64_11(h) * prime64x1 + } + + return mix64(h) +} + +func (xx *XXHash64) Write(in []byte) (n int, err error) { + mem, idx := xx.mem[:], int(xx.memIdx) + + xx.ln, n = xx.ln+uint64(len(in)), len(in) + + if idx+len(in) < 32 { + xx.memIdx += int8(copy(mem[idx:len(mem):len(mem)], in)) + return + } + + var ( + v1, v2, v3, v4 = xx.v1, xx.v2, xx.v3, xx.v4 + + i int + ) + + if d := 32 - int(idx); d > 0 && int(idx)+len(in) > 31 { + copy(mem[idx:len(mem):len(mem)], in[:len(in):len(in)]) + + words := (*[4]uint64)(unsafe.Pointer(&mem[0])) + + v1 = round64(v1, words[0]) + v2 = round64(v2, words[1]) + v3 = round64(v3, words[2]) + v4 = round64(v4, words[3]) + + if in, xx.memIdx = in[d:len(in):len(in)], 0; len(in) == 0 { + goto RET + } + } + + for ; i < len(in)-31; i += 32 { + words := (*[4]uint64)(unsafe.Pointer(&in[i])) + + v1 = round64(v1, words[0]) + v2 = round64(v2, words[1]) + v3 = round64(v3, words[2]) + v4 = round64(v4, words[3]) + } + + if len(in)-i != 0 { + xx.memIdx += int8(copy(mem[xx.memIdx:len(mem):len(mem)], in[i:len(in):len(in)])) + } + +RET: + xx.v1, xx.v2, xx.v3, xx.v4 = v1, v2, v3, v4 + + return +} + +func (xx *XXHash64) Sum64() (h uint64) { + if seed := xx.seed; xx.ln > 31 { + v1, v2, v3, v4 := xx.v1, xx.v2, xx.v3, xx.v4 + h = rotl64_1(v1) + rotl64_7(v2) + rotl64_12(v3) + rotl64_18(v4) + + h = mergeRound64(h, v1) + h = mergeRound64(h, v2) + h = mergeRound64(h, v3) + h = mergeRound64(h, v4) + } else if seed == 0 { + h = prime64x5 + } else { + h = seed + prime64x5 + } + + h += uint64(xx.ln) + + if xx.memIdx == 0 { + return mix64(h) + } + + var ( + in = xx.mem[:xx.memIdx:xx.memIdx] + wordsLen = len(in) >> 3 + words = ((*[maxInt32 / 8]uint64)(unsafe.Pointer(&in[0])))[:wordsLen:wordsLen] + ) + + for _, k := range words { + h ^= round64(0, k) + h = rotl64_27(h)*prime64x1 + prime64x4 + } + + if in = in[wordsLen<<3 : len(in) : len(in)]; len(in) > 3 { + words := (*[1]uint32)(unsafe.Pointer(&in[0])) + + h ^= uint64(words[0]) * prime64x1 + h = rotl64_23(h)*prime64x2 + prime64x3 + + in = in[4:len(in):len(in)] + } + + for _, b := range in { + h ^= uint64(b) * prime64x5 + h = rotl64_11(h) * prime64x1 + } + + return mix64(h) +}