Skip to content
This repository was archived by the owner on May 13, 2022. It is now read-only.

Commit 314357e

Browse files
author
Silas Davis
committed
This fixes two issues with our state handling when querying state via
`CallSim` and `CallCodeSim`. State is separated into a mutable write side and an immutable read side (from last commit/height) via RWTree and then via MutableForest in an MVCC style manner. Reads should hit the last committed version. However there were two bugs in TransactServer's `CallTxSim` and `CallCodeSim`: 1. During the execution of a simulated call if a block is committed reads can be made from consecutive blocks and so may not see a consistent view of the state. 2. `ImmutableForest`'s `loadOrCreateTree` was unsynchronised and committed a multitude of sins: creating and returning multiple trees for the same prefix when it should ensure one instance exists in cache and most problematically allowing access to uninitialised trees by pushing into LRU cache before `Load`ing the tree from DB. The fix for 1 is to pull a snapshot `ImmutableForest` when Call*Sim are called which is fixed to the latest height at the time of the call. The fix for 2 was to synchronise `loadOrCreateTree` and only push initialised trees into the `treeCache`. simulated_call_test.go adds a test that triggers the second bug (requires a cold cache and a prefix with previous existing versions) Additionally: - Make MutableForest and ImmutableForest fully thread-safe on reads and writes. Signed-off-by: Silas Davis <[email protected]>
1 parent aad850b commit 314357e

34 files changed

+676
-306
lines changed

acm/acmstate/state_cache.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,26 @@ func (cache *Cache) get(address crypto.Address) (*accountInfo, error) {
262262
cache.RLock()
263263
accInfo := cache.accounts[address]
264264
cache.RUnlock()
265-
if accInfo == nil {
266-
cache.Lock()
267-
defer cache.Unlock()
268-
accInfo = cache.accounts[address]
269-
if accInfo == nil {
270-
account, err := cache.backend.GetAccount(address)
271-
if err != nil {
272-
return nil, err
273-
}
274-
accInfo = &accountInfo{
275-
account: account,
276-
storage: make(map[binary.Word256][]byte),
277-
}
278-
cache.accounts[address] = accInfo
279-
}
265+
if accInfo != nil {
266+
return accInfo, nil
267+
}
268+
// Take write lock to fill cache
269+
cache.Lock()
270+
defer cache.Unlock()
271+
// Check for an interleaved cache fill
272+
accInfo = cache.accounts[address]
273+
if accInfo != nil {
274+
return accInfo, nil
275+
}
276+
// Pull from backend
277+
account, err := cache.backend.GetAccount(address)
278+
if err != nil {
279+
return nil, err
280+
}
281+
accInfo = &accountInfo{
282+
account: account,
283+
storage: make(map[binary.Word256][]byte),
280284
}
285+
cache.accounts[address] = accInfo
281286
return accInfo, nil
282287
}

cmd/burrow/commands/errors.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package commands
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/hyperledger/burrow/execution/errors"
7+
cli "github.com/jawher/mow.cli"
8+
)
9+
10+
func Errors(output Output) func(cmd *cli.Cmd) {
11+
return func(cmd *cli.Cmd) {
12+
13+
jsonOpt := cmd.BoolOpt("j json", false, "output errors as a JSON object")
14+
15+
cmd.Spec = "[ --json ]"
16+
17+
cmd.Action = func() {
18+
if *jsonOpt {
19+
bs, err := json.MarshalIndent(errors.Codes, "", "\t")
20+
if err != nil {
21+
output.Fatalf("Could not marshal error codes: %w", err)
22+
}
23+
output.Printf(string(bs))
24+
} else {
25+
output.Printf(errors.Codes.String())
26+
}
27+
}
28+
}
29+
}

cmd/burrow/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func burrow(output commands.Output) *cli.Cli {
8787
app.Command("compile", "Compile solidity files embedding the compilation results as a fixture in a Go file",
8888
commands.Compile(output))
8989

90+
app.Command("errors", "Print error codes",
91+
commands.Errors(output))
9092
return app
9193
}
9294

core/processes.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"net/http"
77
"time"
88

9+
"github.com/hyperledger/burrow/acm/acmstate"
10+
911
"github.com/hyperledger/burrow/bcm"
1012
"github.com/hyperledger/burrow/consensus/abci"
1113
"github.com/hyperledger/burrow/execution"
@@ -320,7 +322,9 @@ func GRPCLauncher(kern *Kernel, conf *rpc.ServerConfig, keyConfig *keys.KeysConf
320322

321323
txCodec := txs.NewProtobufCodec()
322324
rpctransact.RegisterTransactServer(grpcServer,
323-
rpctransact.NewTransactServer(kern.State, kern.Blockchain, kern.Transactor, txCodec, kern.Logger))
325+
rpctransact.NewTransactServer(func() (acmstate.Reader, error) {
326+
return kern.State.AtLatestVersion()
327+
}, kern.Blockchain, kern.Transactor, txCodec, kern.Logger))
324328

325329
rpcevents.RegisterExecutionEventsServer(grpcServer, rpcevents.NewExecutionEventsServer(kern.State,
326330
kern.Emitter, kern.Blockchain, kern.Logger))

dump/dump.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (ds *Dumper) Transmit(sink Sink, startHeight, endHeight uint64, options Opt
7070
if endHeight == 0 || endHeight > lastHeight {
7171
endHeight = lastHeight
7272
}
73-
st, err := ds.state.LoadHeight(endHeight)
73+
st, err := ds.state.AtHeight(endHeight)
7474
if err != nil {
7575
return err
7676
}

execution/accounts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func NewAccounts(reader acmstate.Reader, keyClient keys.KeyClient, mutexCount in
3636
keyClient: keyClient,
3737
}
3838
}
39+
3940
func (accs *Accounts) SigningAccount(address crypto.Address) (*SigningAccount, error) {
4041
signer, err := keys.AddressableSigner(accs.keyClient, address)
4142
if err != nil {

execution/errors/codes.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"encoding/json"
55
"fmt"
66
"reflect"
7+
"strconv"
8+
"strings"
79
)
810

911
type codes struct {
@@ -89,3 +91,16 @@ func (es *codes) Get(number uint32) *Code {
8991
}
9092
return es.codes[number]
9193
}
94+
95+
func (es *codes) String() string {
96+
sb := new(strings.Builder)
97+
for _, c := range es.codes {
98+
sb.WriteString(strconv.FormatUint(uint64(c.Number), 10))
99+
sb.WriteString(": ")
100+
sb.WriteString(c.Name)
101+
sb.WriteString(" - ")
102+
sb.WriteString(c.Description)
103+
sb.WriteRune('\n')
104+
}
105+
return sb.String()
106+
}

execution/errors/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Source interface {
2121
Error() error
2222
}
2323

24-
var Codes = codes{
24+
var Codes = &codes{
2525
None: code("none"),
2626
UnknownAddress: code("unknown address"),
2727
InsufficientBalance: code("insufficient balance"),

execution/errors/errors_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ func TestCode_String(t *testing.T) {
2525
err := Codes.CodeOutOfBounds
2626
fmt.Println(err.Error())
2727
}
28+
29+
func TestPrintCodes(t *testing.T) {
30+
fmt.Printf("%v", Codes)
31+
}

execution/execution.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type ExecutorState interface {
5454
proposal.Reader
5555
validator.IterableReader
5656
}
57+
5758
type BatchExecutor interface {
5859
// Provides access to write lock for a BatchExecutor so reads can be prevented for the duration of a commit
5960
sync.Locker
@@ -284,7 +285,7 @@ func (exe *executor) validateInputsAndStorePublicKeys(txEnv *txs.Envelope) error
284285
for s, in := range txEnv.Tx.GetInputs() {
285286
err := exe.updateSignatory(txEnv.Signatories[s])
286287
if err != nil {
287-
return fmt.Errorf("failed to update public key for input %v: %v", in.Address, err)
288+
return fmt.Errorf("failed to update public key for input %v: %w", in.Address, err)
288289
}
289290
acc, err := exe.stateCache.GetAccount(in.Address)
290291
if err != nil {

0 commit comments

Comments
 (0)