Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions mocks/Application.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 96 additions & 5 deletions node/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestAggregatorMode(t *testing.T) {
app.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{})
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})

key, _, _ := crypto.GenerateEd25519Key(rand.Reader)
signingKey, _, _ := crypto.GenerateEd25519Key(rand.Reader)
Expand Down Expand Up @@ -89,7 +90,7 @@ func TestTxGossipingAndAggregation(t *testing.T) {

var wg sync.WaitGroup
clientNodes := 4
nodes, apps := createNodes(clientNodes+1, &wg, t)
nodes, apps := createNodes(false, clientNodes+1, &wg, t)

wg.Add((clientNodes + 1) * clientNodes)
for _, n := range nodes {
Expand Down Expand Up @@ -159,7 +160,88 @@ func TestTxGossipingAndAggregation(t *testing.T) {
}
}

func createNodes(num int, wg *sync.WaitGroup, t *testing.T) ([]*Node, []*mocks.Application) {
func TestFraudProofTrigger(t *testing.T) {
assert := assert.New(t)
require := require.New(t)

var wg sync.WaitGroup
clientNodes := 4
nodes, apps := createNodes(true, clientNodes+1, &wg, t)

wg.Add((clientNodes + 1) * clientNodes)
for _, n := range nodes {
require.NoError(n.Start())
}

time.Sleep(1 * time.Second)

for i := 1; i < len(nodes); i++ {
data := strconv.Itoa(i) + time.Now().String()
require.NoError(nodes[i].P2P.GossipTx(context.TODO(), []byte(data)))
}

timeout := time.NewTimer(time.Second * 30)
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
wg.Wait()
}()
select {
case <-doneChan:
case <-timeout.C:
t.FailNow()
}

for _, n := range nodes {
require.NoError(n.Stop())
}
aggApp := apps[0]
apps = apps[1:]

aggApp.AssertNumberOfCalls(t, "DeliverTx", clientNodes)
aggApp.AssertExpectations(t)

for i, app := range apps {
app.AssertNumberOfCalls(t, "DeliverTx", clientNodes)
app.AssertExpectations(t)

// assert that we have most of the blocks from aggregator
beginCnt := 0
endCnt := 0
commitCnt := 0
triggerFraudProofGenerationModeCnt := 0
for _, call := range app.Calls {
switch call.Method {
case "BeginBlock":
beginCnt++
case "EndBlock":
endCnt++
case "Commit":
commitCnt++
case "TriggerFraudProofGenerationMode":
triggerFraudProofGenerationModeCnt++
}
}
aggregatorHeight := nodes[0].Store.Height()
adjustedHeight := int(aggregatorHeight - 3) // 3 is completely arbitrary
assert.GreaterOrEqual(beginCnt, adjustedHeight)
assert.GreaterOrEqual(endCnt, adjustedHeight)
assert.GreaterOrEqual(commitCnt, adjustedHeight)

assert.Equal(triggerFraudProofGenerationModeCnt, beginCnt+endCnt+clientNodes)

// assert that all blocks known to node are same as produced by aggregator
for h := uint64(1); h <= nodes[i].Store.Height(); h++ {
nodeBlock, err := nodes[i].Store.LoadBlock(h)
require.NoError(err)
aggBlock, err := nodes[0].Store.LoadBlock(h)
require.NoError(err)
assert.Equal(aggBlock, nodeBlock)
}
}
}

func createNodes(isMalicious bool, num int, wg *sync.WaitGroup, t *testing.T) ([]*Node, []*mocks.Application) {
t.Helper()

// create keys first, as they are required for P2P connections
Expand All @@ -173,15 +255,15 @@ func createNodes(num int, wg *sync.WaitGroup, t *testing.T) ([]*Node, []*mocks.A
dalc := &mockda.MockDataAvailabilityLayerClient{}
_ = dalc.Init(nil, store.NewDefaultInMemoryKVStore(), log.TestingLogger())
_ = dalc.Start()
nodes[0], apps[0] = createNode(0, true, dalc, keys, wg, t)
nodes[0], apps[0] = createNode(0, isMalicious, true, dalc, keys, wg, t)
for i := 1; i < num; i++ {
nodes[i], apps[i] = createNode(i, false, dalc, keys, wg, t)
nodes[i], apps[i] = createNode(i, isMalicious, false, dalc, keys, wg, t)
}

return nodes, apps
}

func createNode(n int, aggregator bool, dalc da.DataAvailabilityLayerClient, keys []crypto.PrivKey, wg *sync.WaitGroup, t *testing.T) (*Node, *mocks.Application) {
func createNode(n int, isMalicious bool, aggregator bool, dalc da.DataAvailabilityLayerClient, keys []crypto.PrivKey, wg *sync.WaitGroup, t *testing.T) (*Node, *mocks.Application) {
t.Helper()
require := require.New(t)
// nodes will listen on consecutive ports on local interface
Expand Down Expand Up @@ -211,6 +293,15 @@ func createNode(n int, aggregator bool, dalc da.DataAvailabilityLayerClient, key
app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
if isMalicious && aggregator {
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{AppHash: []byte{9, 8, 7, 6}})
} else {
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{AppHash: []byte{1, 2, 3, 4}})
}

if isMalicious && !aggregator {
app.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{})
}
app.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{}).Run(func(args mock.Arguments) {
wg.Done()
})
Expand Down
88 changes: 74 additions & 14 deletions proto/tendermint/abci/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ message Request {
RequestOfferSnapshot offer_snapshot = 13;
RequestLoadSnapshotChunk load_snapshot_chunk = 14;
RequestApplySnapshotChunk apply_snapshot_chunk = 15;
RequestGetAppHash get_app_hash = 16;
RequestGenerateFraudProof generate_fraud_proof = 17;
RequestTriggerFraudProofGenerationMode trigger_fraud_proof_generation_mode = 18;
}
}

Expand Down Expand Up @@ -102,8 +105,7 @@ message RequestEndBlock {
message RequestCommit {}

// lists available snapshots
message RequestListSnapshots {
}
message RequestListSnapshots {}

// offers a snapshot to the application
message RequestOfferSnapshot {
Expand All @@ -125,6 +127,15 @@ message RequestApplySnapshotChunk {
string sender = 3;
}

// Gets the current appHash
message RequestGetAppHash {}

// Generates a fraud proof
message RequestGenerateFraudProof {}

// Triggers fraud proof generation mode
message RequestTriggerFraudProofGenerationMode {}

//----------------------------------------
// Response types

Expand All @@ -146,6 +157,9 @@ message Response {
ResponseOfferSnapshot offer_snapshot = 14;
ResponseLoadSnapshotChunk load_snapshot_chunk = 15;
ResponseApplySnapshotChunk apply_snapshot_chunk = 16;
ResponseGetAppHash get_app_hash = 17;
ResponseGenerateFraudProof generate_fraud_proof = 18;
ResponseTriggerFraudProofGenerationMode trigger_fraud_proof_generation_mode = 19;
}
}

Expand Down Expand Up @@ -212,6 +226,12 @@ message ResponseCheckTx {
repeated Event events = 7
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
string codespace = 8;
string sender = 9;
int64 priority = 10;

// mempool_error is set by Tendermint.
// ABCI applictions creating a ResponseCheckTX should not set mempool_error.
string mempool_error = 11;
}

message ResponseDeliverTx {
Expand All @@ -221,16 +241,17 @@ message ResponseDeliverTx {
string info = 4; // nondeterministic
int64 gas_wanted = 5 [json_name = "gas_wanted"];
int64 gas_used = 6 [json_name = "gas_used"];
repeated Event events = 7
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic
repeated Event events = 7 [
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "events,omitempty"
]; // nondeterministic
string codespace = 8;
}

message ResponseEndBlock {
repeated ValidatorUpdate validator_updates = 1
[(gogoproto.nullable) = false];
ConsensusParams consensus_param_updates = 2;
repeated Event events = 3
repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable) = false];
ConsensusParams consensus_param_updates = 2;
repeated Event events = 3
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
}

Expand Down Expand Up @@ -276,6 +297,18 @@ message ResponseApplySnapshotChunk {
}
}

message ResponseGetAppHash {
bytes app_hash = 1;
}

message ResponseGenerateFraudProof {
FraudProof fraudProof = 1;
}

message ResponseTriggerFraudProofGenerationMode {
bool success = 1;
}

//----------------------------------------
// Misc.

Expand Down Expand Up @@ -364,10 +397,8 @@ message Evidence {
// The height when the offense occurred
int64 height = 3;
// The corresponding time where the offense occurred
google.protobuf.Timestamp time = 4 [
(gogoproto.nullable) = false,
(gogoproto.stdtime) = true
];
google.protobuf.Timestamp time = 4
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// Total voting power of the validator set in case the ABCI application does
// not store historical validators.
// https://github.com/tendermint/tendermint/issues/4581
Expand All @@ -385,6 +416,30 @@ message Snapshot {
bytes metadata = 5; // Arbitrary application metadata
}

// Represents a single-round fraudProof
message FraudProof {
int64 block_height = 1;
bytes appHash = 2;
map<string, StateWitness> stateWitness = 3;
}

// State witness with a list of all witness data
message StateWitness {
// store level proof
tendermint.crypto.ProofOp proof_op = 1;
// substore level hash
bytes rootHash = 2;
// List of witness data
repeated WitnessData witnessData = 3;
}

// Witness data containing a key/value pair and a SMT proof for said key/value pair
message WitnessData {
bytes key = 1;
bytes value = 2;
tendermint.crypto.ProofOp proof = 3;
}

//----------------------------------------
// Service Definition

Expand All @@ -402,6 +457,11 @@ service ABCIApplication {
rpc EndBlock(RequestEndBlock) returns (ResponseEndBlock);
rpc ListSnapshots(RequestListSnapshots) returns (ResponseListSnapshots);
rpc OfferSnapshot(RequestOfferSnapshot) returns (ResponseOfferSnapshot);
rpc LoadSnapshotChunk(RequestLoadSnapshotChunk) returns (ResponseLoadSnapshotChunk);
rpc ApplySnapshotChunk(RequestApplySnapshotChunk) returns (ResponseApplySnapshotChunk);
rpc LoadSnapshotChunk(RequestLoadSnapshotChunk)
returns (ResponseLoadSnapshotChunk);
rpc ApplySnapshotChunk(RequestApplySnapshotChunk)
returns (ResponseApplySnapshotChunk);
rpc GetAppHash(RequestGetAppHash) returns (ResponseGetAppHash);
rpc GenerateFraudProof(RequestGenerateFraudProof) returns (ResponseGenerateFraudProof);
rpc TriggerFraudProofGenerationMode(RequestTriggerFraudProofGenerationMode) returns (ResponseTriggerFraudProofGenerationMode);
}
4 changes: 4 additions & 0 deletions rpc/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ func TestTx(t *testing.T) {
mockApp.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
mockApp.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{})
mockApp.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{})
mockApp.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})
mockApp.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{})

err = rpc.node.Start()
require.NoError(err)
Expand Down Expand Up @@ -633,6 +635,8 @@ func TestValidatorSetHandling(t *testing.T) {
app.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{})
app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})
app.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{})

key, _, _ := crypto.GenerateEd25519Key(crand.Reader)
signingKey, _, _ := crypto.GenerateEd25519Key(crand.Reader)
Expand Down
2 changes: 2 additions & 0 deletions rpc/json/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ func getRPC(t *testing.T) (*mocks.Application, *client.Client) {
app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{})
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{})
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{})
app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{})
app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{})
app.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{
GasWanted: 1000,
GasUsed: 1000,
Expand Down
Loading